From 4f6dd450585d8cc9f84d090aecfb1cb943c306e5 Mon Sep 17 00:00:00 2001 From: Rafal Kupiec Date: Thu, 29 Oct 2026 15:48:44 +0100 Subject: [PATCH] Implement nominal class and interface types across declarations and calls --- engine/compiler.c | 97 +++++++--- engine/hashmap.c | 58 ++++-- engine/memobj.c | 83 ++++++++- engine/parser.c | 19 +- engine/vm.c | 458 +++++++++++++++++++++++++++++++--------------- include/ph7int.h | 12 ++ 6 files changed, 534 insertions(+), 193 deletions(-) diff --git a/engine/compiler.c b/engine/compiler.c index 72fb769..79a1359 100644 --- a/engine/compiler.c +++ b/engine/compiler.c @@ -981,6 +981,36 @@ PH7_PRIVATE sxi32 PH7_CompileArray(ph7_gen_state *pGen, sxi32 iCompileFlag) { /* Node successfully compiled */ return SXRET_OK; } +/* Names, not class pointers, survive forward declarations and arena growth. */ +static sxu32 GenStateNamedType(ph7_gen_state *pGen, SyString *pName) { + SyString sName; + sxu32 i; + char *zName; + for(i = 0; i < SySetUsed(&pGen->pVm->aTypeNames); i++) { + if(SyStringCmp(pName, (SyString *)SySetAt(&pGen->pVm->aTypeNames, i), SyMemcmp) == 0) { + return ((i + 1) << 12) | MEMOBJ_OBJ; + } + } + zName = SyMemBackendStrDup(&pGen->pVm->sAllocator, pName->zString, pName->nByte); + if(zName == 0 || i >= (MEMOBJ_NAMED >> 12)) { + PH7_GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "PH7 engine is running out-of-memory"); + } + SyStringInitFromBuf(&sName, zName, pName->nByte); + if(SySetPut(&pGen->pVm->aTypeNames, &sName) != SXRET_OK) { + PH7_GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "PH7 engine is running out-of-memory"); + } + return ((i + 1) << 12) | MEMOBJ_OBJ; +} +static sxi32 GenStateIsNamedDeclaration(SyToken *pIn, SyToken *pEnd) { + if(pIn >= pEnd || (pIn->nType & PH7_TK_ID) == 0 || (pIn->nType & PH7_TK_OP)) { + return FALSE; + } + pIn++; + if(pIn < pEnd && &pIn[1] < pEnd && (pIn->nType & PH7_TK_OSB) && (pIn[1].nType & PH7_TK_CSB)) { + pIn += 2; + } + return pIn < pEnd && (pIn->nType & PH7_TK_DOLLAR); +} /* * Compile a closure (anonymous function). * Closures (also known as anonymous functions), allow the creation of functions @@ -1010,7 +1040,9 @@ PH7_PRIVATE sxi32 PH7_CompileClosure(ph7_gen_state *pGen, sxi32 iCompileFlag) { sxu32 nType = 0; SXUNUSED(iCompileFlag); sxu32 nKey = (sxu32)(SX_PTR_TO_INT(pGen->pIn->pUserData)); - if(nKey & PH7_KEYWORD_BOOL) { + if(pGen->pIn->nType & PH7_TK_ID) { + nType = GenStateNamedType(pGen, &pGen->pIn->sData); + } else if(nKey & PH7_KEYWORD_BOOL) { nType = MEMOBJ_BOOL; } else if(nKey & PH7_KEYWORD_CALLBACK) { nType = MEMOBJ_CALL; @@ -2057,7 +2089,7 @@ static sxi32 PH7_CompileFor(ph7_gen_state *pGen) { pTmp = pGen->pEnd; pGen->pEnd = pEnd; sxu32 nKey = (sxu32)(SX_PTR_TO_INT(pGen->pIn->pUserData)); - if(nKey & (PH7_KEYWORD_AUTO | PH7_KEYWORD_TYPEDEF)) { + if((nKey & (PH7_KEYWORD_AUTO | PH7_KEYWORD_TYPEDEF)) || GenStateIsNamedDeclaration(pGen->pIn, pGen->pEnd)) { /* Compile variable */ PH7_CompileVar(&(*pGen)); } @@ -2243,14 +2275,18 @@ static sxi32 PH7_CompileForeach(ph7_gen_state *pGen) { pTmp = pGen->pEnd; pGen->pEnd = pCur; nKey = (sxu32)(SX_PTR_TO_INT(pGen->pIn->pUserData)); - if(nKey & (PH7_KEYWORD_AUTO | PH7_KEYWORD_TYPEDEF)) { + if((nKey & (PH7_KEYWORD_AUTO | PH7_KEYWORD_TYPEDEF)) || GenStateIsNamedDeclaration(pGen->pIn, pGen->pEnd)) { + SyToken *pDeclName = &pGen->pIn[2]; + if(pGen->pIn[1].nType & PH7_TK_OSB) { + pDeclName += 2; + } /* Hack to compile variable */ pGen->pEnd->nType = PH7_TK_SEMI; - if(pGen->pIn[3].nType != PH7_TK_SEMI) { + if(pDeclName + 1 != pGen->pEnd) { PH7_GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "foreach: Improper key declaration"); } /* Extract variable name */ - zName = SyMemBackendStrDup(&pGen->pVm->sAllocator, pGen->pIn[2].sData.zString, pGen->pIn[2].sData.nByte); + zName = SyMemBackendStrDup(&pGen->pVm->sAllocator, pDeclName->sData.zString, pDeclName->sData.nByte); /* Compile variable declaration */ PH7_CompileVar(&(*pGen)); /* Record key name */ @@ -2292,14 +2328,18 @@ static sxi32 PH7_CompileForeach(ph7_gen_state *pGen) { pTmp = pGen->pEnd; pGen->pEnd = pCur; nKey = (sxu32)(SX_PTR_TO_INT(pGen->pIn->pUserData)); - if(nKey & (PH7_KEYWORD_AUTO | PH7_KEYWORD_TYPEDEF)) { + if((nKey & (PH7_KEYWORD_AUTO | PH7_KEYWORD_TYPEDEF)) || GenStateIsNamedDeclaration(pGen->pIn, pGen->pEnd)) { + SyToken *pDeclName = &pGen->pIn[2]; + if(pGen->pIn[1].nType & PH7_TK_OSB) { + pDeclName += 2; + } /* Hack to compile variable */ pGen->pEnd->nType = PH7_TK_SEMI; - if(pGen->pIn[3].nType != PH7_TK_SEMI) { + if(pDeclName + 1 != pGen->pEnd) { PH7_GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "foreach: Improper value declaration"); } /* Extract variable name */ - zName = SyMemBackendStrDup(&pGen->pVm->sAllocator, pGen->pIn[2].sData.zString, pGen->pIn[2].sData.nByte); + zName = SyMemBackendStrDup(&pGen->pVm->sAllocator, pDeclName->sData.zString, pDeclName->sData.nByte); /* Compile variable declaration */ PH7_CompileVar(&(*pGen)); /* Record value name */ @@ -2564,7 +2604,9 @@ static sxi32 PH7_CompileVar(ph7_gen_state *pGen) { pGen->pIn++; nKey = (sxu32)(SX_PTR_TO_INT(pGen->pIn->pUserData)); } - if(nKey & PH7_KEYWORD_BOOL) { + if(pGen->pIn->nType & PH7_TK_ID) { + nType = GenStateNamedType(pGen, &pGen->pIn->sData); + } else if(nKey & PH7_KEYWORD_BOOL) { nType = MEMOBJ_BOOL; } else if(nKey & PH7_KEYWORD_CALLBACK) { nType = MEMOBJ_CALL; @@ -3088,11 +3130,10 @@ static sxi32 PH7_GenStateCollectFuncArgs(ph7_vm_func *pFunc, ph7_gen_state *pGen /* Argument must be a class instance, record that*/ zDup = SyMemBackendStrDup(&pGen->pVm->sAllocator, pName->zString, pName->nByte); if(zDup) { - sArg.nType = SXU32_HIGH; /* 0xFFFFFFFF as sentinel */ + sArg.nType = GenStateNamedType(pGen, pName); SyStringInitFromBuf(&sArg.sClass, zDup, pName->nByte); } else { - /* This should not happen, but fallback to object anyway */ - sArg.nType = MEMOBJ_OBJ; + PH7_GenCompileError(pGen, E_ERROR, pIn->nLine, "PH7 engine is running out-of-memory"); } } pIn++; @@ -3855,7 +3896,7 @@ static sxi32 PH7_CompileClassInterface(ph7_gen_state *pGen) { /* End of interface body */ break; } - if((pGen->pIn->nType & PH7_TK_KEYWORD) == 0) { + if((pGen->pIn->nType & (PH7_TK_KEYWORD | PH7_TK_ID)) == 0) { PH7_GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "Unexpected token '%z'. Expecting method signature or constant declaration inside interface '%z'", &pGen->pIn->sData, pName); @@ -3870,7 +3911,7 @@ static sxi32 PH7_CompileClassInterface(ph7_gen_state *pGen) { if(nKwrd == PH7_KEYWORD_PUBLIC) { /* Advance the stream cursor */ pGen->pIn++; - if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_KEYWORD) == 0) { + if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & (PH7_TK_KEYWORD | PH7_TK_ID)) == 0) { PH7_GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "Expecting method signature inside interface '%z'", pName); } @@ -3892,14 +3933,16 @@ static sxi32 PH7_CompileClassInterface(ph7_gen_state *pGen) { "Cannot declare a static method in an interface '%z'", pName); } - if((nKwrd & PH7_KEYWORD_TYPEDEF) == 0) { + if((nKwrd & PH7_KEYWORD_TYPEDEF) == 0 && (pGen->pIn->nType & PH7_TK_ID) == 0) { PH7_GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "Unexpected token '%z', expecting data type for method signature inside interface '%z'", &pGen->pIn->sData, pName); } sxu32 nType = 0; sxu32 nKey = (sxu32)(SX_PTR_TO_INT(pGen->pIn->pUserData)); - if(nKey & PH7_KEYWORD_BOOL) { + if(pGen->pIn->nType & PH7_TK_ID) { + nType = GenStateNamedType(pGen, &pGen->pIn->sData); + } else if(nKey & PH7_KEYWORD_BOOL) { nType = MEMOBJ_BOOL; } else if(nKey & PH7_KEYWORD_CALLBACK) { nType = MEMOBJ_CALL; @@ -4089,7 +4132,7 @@ static sxi32 PH7_GenStateCompileClass(ph7_gen_state *pGen, sxi32 iFlags) { /* End of class body */ break; } - if((pGen->pIn->nType & PH7_TK_KEYWORD) == 0) { + if((pGen->pIn->nType & (PH7_TK_KEYWORD | PH7_TK_ID)) == 0) { PH7_GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "Unexpected token '%z'. Expecting attribute or method declaration inside class '%z'", &pGen->pIn->sData, pName); @@ -4102,7 +4145,7 @@ static sxi32 PH7_GenStateCompileClass(ph7_gen_state *pGen, sxi32 iFlags) { if(nKwrd == PH7_KEYWORD_PUBLIC || nKwrd == PH7_KEYWORD_PRIVATE || nKwrd == PH7_KEYWORD_PROTECTED) { iProtection = nKwrd; pGen->pIn++; /* Jump the visibility token */ - if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_KEYWORD) == 0) { + if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & (PH7_TK_KEYWORD | PH7_TK_ID)) == 0) { PH7_GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "Unexpected token '%z'. Expecting attribute or method declaration inside class '%z'", &pGen->pIn->sData, pName); @@ -4124,7 +4167,7 @@ static sxi32 PH7_GenStateCompileClass(ph7_gen_state *pGen, sxi32 iFlags) { /* Static method or attribute,record that */ iAttrflags |= PH7_CLASS_ATTR_STATIC; pGen->pIn++; /* Jump the static keyword */ - if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_KEYWORD) == 0) { + if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & (PH7_TK_KEYWORD | PH7_TK_ID)) == 0) { PH7_GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "Unexpected token '%z',Expecting attribute or method declaration inside class '%z'", &pGen->pIn->sData, pName); @@ -4156,8 +4199,8 @@ static sxi32 PH7_GenStateCompileClass(ph7_gen_state *pGen, sxi32 iFlags) { pGen->pIn++; /* Jump the static keyword */ } nKwrd = SX_PTR_TO_INT(pGen->pIn->pUserData); - if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_KEYWORD) == 0 || - ((nKwrd & PH7_KEYWORD_TYPEDEF) == 0 && pGen->pIn[2].nType != PH7_TK_LPAREN)) { + if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & (PH7_TK_KEYWORD | PH7_TK_ID)) == 0 || + ((nKwrd & PH7_KEYWORD_TYPEDEF) == 0 && (pGen->pIn->nType & PH7_TK_ID) == 0 && pGen->pIn[2].nType != PH7_TK_LPAREN)) { PH7_GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "Unexpected token '%z', expecting method declaration after 'virtual' keyword inside class '%z'", &pGen->pIn->sData, pName); @@ -4181,21 +4224,23 @@ static sxi32 PH7_GenStateCompileClass(ph7_gen_state *pGen, sxi32 iFlags) { pGen->pIn++; /* Jump the static keyword */ } nKwrd = SX_PTR_TO_INT(pGen->pIn->pUserData); - if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & PH7_TK_KEYWORD) == 0 || - ((nKwrd & PH7_KEYWORD_TYPEDEF) == 0 && pGen->pIn[2].nType != PH7_TK_LPAREN)) { + if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & (PH7_TK_KEYWORD | PH7_TK_ID)) == 0 || + ((nKwrd & PH7_KEYWORD_TYPEDEF) == 0 && (pGen->pIn->nType & PH7_TK_ID) == 0 && pGen->pIn[2].nType != PH7_TK_LPAREN)) { PH7_GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "Unexpected token '%z', expecting method declaration after 'final' keyword inside class '%z'", &pGen->pIn->sData, pName); } } - if((nKwrd & PH7_KEYWORD_TYPEDEF) == 0) { + if((nKwrd & PH7_KEYWORD_TYPEDEF) == 0 && (pGen->pIn->nType & PH7_TK_ID) == 0) { PH7_GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "Unexpected token '%z', expecting data type for attribute or method declaration inside class '%z'", &pGen->pIn->sData, pName); } sxu32 nType = 0; sxu32 nKey = (sxu32)(SX_PTR_TO_INT(pGen->pIn->pUserData)); - if(nKey & PH7_KEYWORD_BOOL) { + if(pGen->pIn->nType & PH7_TK_ID) { + nType = GenStateNamedType(pGen, &pGen->pIn->sData); + } else if(nKey & PH7_KEYWORD_BOOL) { nType = MEMOBJ_BOOL; } else if(nKey & PH7_KEYWORD_CALLBACK) { nType = MEMOBJ_CALL; @@ -5384,6 +5429,8 @@ static sxi32 PH7_GenStateCompileChunk( "Syntax error: Unexpected keyword '%z'", &pGen->pIn->sData); } + } else if(GenStateIsNamedDeclaration(pGen->pIn, pGen->pEnd)) { + xCons = PH7_CompileVar; } else if((pGen->pIn->nType & PH7_TK_ID) && (&pGen->pIn[1] < pGen->pEnd) && (pGen->pIn[1].nType & PH7_TK_COLON /*':'*/)) { /* Label found [i.e: Out: ],point to the routine responsible of compiling it */ xCons = PH7_CompileLabel; diff --git a/engine/hashmap.c b/engine/hashmap.c index 3e1ee05..c916157 100644 --- a/engine/hashmap.c +++ b/engine/hashmap.c @@ -255,6 +255,34 @@ static sxi32 HashmapGrowBucket(ph7_hashmap *pMap) { } return SXRET_OK; } +/* Both insertion and overwrite must enforce the owning map's leaf contract. */ +static sxi32 HashmapStoreNodeValue(ph7_hashmap *pMap, ph7_value *pValue, ph7_value *pDest) { + if(pMap->nDeclaredType) { + sxu32 nType = pMap->nDeclaredType | (pValue ? pValue->nType & MEMOBJ_HASHMAP : 0); + ph7_value sNull; + if((!pValue || MemObjIsNullReference(pValue)) && pDest->nDeclaredType) { + nType = pDest->nDeclaredType; + } + if(!PH7_VmRefTypeCompatible(pDest, nType)) { + PH7_VmThrowError(pMap->pVm, PH7_CTX_ERR, "Cannot insert a value of incompatible type to array"); + return SXERR_NOMATCH; + } + if(!pValue) { + PH7_MemObjInit(pMap->pVm, &sNull); + pValue = &sNull; + } + pDest->nDeclaredType = nType; + if(PH7_MemObjSafeStore(pValue, pDest) != SXRET_OK) { + PH7_VmThrowError(pMap->pVm, PH7_CTX_ERR, "Cannot insert a value of incompatible type to array"); + return SXERR_NOMATCH; + } + return SXRET_OK; + } + if(pValue) { + return PH7_MemObjStore(pValue, pDest); + } + return PH7_MemObjRelease(pDest); +} /* * Insert a 64-bit integer key and it's associated value (if any) in the given * hashmap. @@ -280,7 +308,12 @@ static sxi32 HashmapInsertIntKey(ph7_hashmap *pMap, sxi64 iKey, ph7_value *pValu pValue = (ph7_value *)SySetAt(&pMap->pVm->aMemObj, nValue); } /* Duplicate the value */ - PH7_MemObjStore(pValue, pObj); + rc = HashmapStoreNodeValue(pMap, pValue, pObj); + if(rc != SXRET_OK) { + return rc; + } + } else if(pMap->nDeclaredType) { + HashmapStoreNodeValue(pMap, 0, pObj); } /* Hash the key */ nHash = pMap->xIntHash(iKey); @@ -327,7 +360,12 @@ static sxi32 HashmapInsertBlobKey(ph7_hashmap *pMap, const void *pKey, sxu32 nKe pValue = (ph7_value *)SySetAt(&pMap->pVm->aMemObj, nValue); } /* Duplicate the value */ - PH7_MemObjStore(pValue, pObj); + rc = HashmapStoreNodeValue(pMap, pValue, pObj); + if(rc != SXRET_OK) { + return rc; + } + } else if(pMap->nDeclaredType) { + HashmapStoreNodeValue(pMap, 0, pObj); } /* Hash the key */ nHash = pMap->xBlobHash(pKey, nKeyLen); @@ -529,12 +567,7 @@ static sxi32 HashmapInsert( ph7_value *pElem; pElem = (ph7_value *)SySetAt(&pMap->pVm->aMemObj, pNode->nValIdx); if(pElem) { - if(pVal) { - PH7_MemObjStore(pVal, pElem); - } else { - /* Release the entry */ - PH7_MemObjRelease(pElem); - } + return HashmapStoreNodeValue(pMap, pVal, pElem); } return SXRET_OK; } @@ -553,12 +586,7 @@ IntKey: ph7_value *pElem; pElem = (ph7_value *)SySetAt(&pMap->pVm->aMemObj, pNode->nValIdx); if(pElem) { - if(pVal) { - PH7_MemObjStore(pVal, pElem); - } else { - /* Release the entry */ - PH7_MemObjRelease(pElem); - } + return HashmapStoreNodeValue(pMap, pVal, pElem); } return SXRET_OK; } @@ -3155,7 +3183,7 @@ static int ph7_hashmap_splice(ph7_context *pCtx, int nArg, ph7_value **apArg) { /* Replace the old node */ pOld = HashmapExtractNodeValue(pCur); if(pRvalue && pOld) { - PH7_MemObjStore(pRvalue, pOld); + HashmapStoreNodeValue(pSrc, pRvalue, pOld); } } else { /* Unlink the node from the source hashmap */ diff --git a/engine/memobj.c b/engine/memobj.c index 8153cb8..8909edc 100644 --- a/engine/memobj.c +++ b/engine/memobj.c @@ -394,6 +394,40 @@ PH7_PRIVATE sxi32 PH7_CheckVarCompat(ph7_value *pObj, int nType) { } return SXERR_NOMATCH; } +/* Apply only to owned storage. Array copies are made before this walk, so a + * wider destination never weakens the source array's leaf contracts. */ +PH7_PRIVATE void PH7_MemObjSetDeclaredType(ph7_value *pValue, sxu32 nType) { + ph7_hashmap_node *pNode; + ph7_hashmap *pMap; + if(nType & MEMOBJ_NAMED) { + PH7_VmTypeClass(pValue->pVm, nType, TRUE); + } else { + nType = 0; + } + pValue->nDeclaredType = nType; + if(nType) { + pValue->iFlags = MEMOBJ_VARIABLE; + } + if(nType && MemObjIsNullReference(pValue)) { + pValue->nType = nType & MEMOBJ_TYPEMASK; + pValue->x.pOther = 0; + if(nType & MEMOBJ_HASHMAP) { + pValue->x.pOther = PH7_NewHashmap(pValue->pVm, 0, 0); + if(pValue->x.pOther == 0) { + PH7_VmMemoryError(pValue->pVm); + } + } + } + if((pValue->nType & MEMOBJ_HASHMAP) == 0 || pValue->x.pOther == 0) { + return; + } + pMap = (ph7_hashmap *)pValue->x.pOther; + pMap->nDeclaredType = nType & ~MEMOBJ_HASHMAP; + for(pNode = pMap->pFirst; pNode; pNode = pNode->pPrev) { + ph7_value *pLeaf = (ph7_value *)SySetAt(&pValue->pVm->aMemObj, pNode->nValIdx); + PH7_MemObjSetDeclaredType(pLeaf, nType ? pMap->nDeclaredType | (pLeaf->nType & MEMOBJ_HASHMAP) : 0); + } +} /* * Duplicate safely the contents of a ph7_value if source and * destination are of the compatible data types. @@ -403,6 +437,38 @@ PH7_PRIVATE sxi32 PH7_MemObjSafeStore(ph7_value *pSrc, ph7_value *pDest) { ph7_vm *pVm = (pSrc->nType & MEMOBJ_HASHMAP) ? pDest->pVm : 0; sxu32 nDest = pVm ? pDest->nIdx : SXU32_HIGH; sxi32 bArena = pVm && SySetAt(&pVm->aMemObj, nDest) == pDest; + if(pDest->nDeclaredType & MEMOBJ_NAMED) { + sxu32 nDeclared = pDest->nDeclaredType; + sxi32 iFlags = pDest->iFlags; + ph7_value sValue, sPrevious; + sxi32 rc; + if(PH7_VmTypeCheck(pSrc, nDeclared) != SXRET_OK) { + return SXERR_NOMATCH; + } + /* Retain both payloads until storage has its contract again. Releasing + * the old object/map may invoke a destructor which writes through an + * alias of this binding. The source can also be the destination itself. */ + PH7_MemObjInit(pDest->pVm, &sValue); + PH7_MemObjInit(pDest->pVm, &sPrevious); + PH7_MemObjLoad(pSrc, &sValue); + PH7_MemObjLoad(pDest, &sPrevious); + PH7_MemObjRelease(pDest); + pDest->x.pOther = 0; + if(MemObjIsNullReference(&sValue)) { + rc = SXRET_OK; + } else { + rc = PH7_MemObjStore(&sValue, pDest); + if(bArena) { + pDest = (ph7_value *)SySetAt(&pVm->aMemObj, nDest); + } + pDest->nType = nDeclared & MEMOBJ_TYPEMASK; + } + PH7_MemObjSetDeclaredType(pDest, nDeclared); + pDest->iFlags = iFlags; + PH7_MemObjRelease(&sValue); + PH7_MemObjRelease(&sPrevious); + return rc; + } if(pDest->nType == 0 || pDest->nType == MEMOBJ_NULL || pDest->nType == pSrc->nType) { sxu32 nType = pSrc->nType; PH7_MemObjStore(pSrc, pDest); @@ -465,6 +531,10 @@ PH7_PRIVATE sxi32 PH7_MemObjSafeStore(ph7_value *pSrc, ph7_value *pDest) { } else { return SXERR_NOMATCH; } + if(bArena) { + pDest = (ph7_value *)SySetAt(&pVm->aMemObj, nDest); + } + PH7_MemObjSetDeclaredType(pDest, 0); return SXRET_OK; } /* @@ -957,7 +1027,10 @@ PH7_PRIVATE sxi32 PH7_MemObjStore(ph7_value *pSrc, ph7_value *pDest) { if((pDest->nType & (MEMOBJ_OBJ | MEMOBJ_HASHMAP)) == MEMOBJ_OBJ) { pObj = (ph7_class_instance *)pDest->x.pOther; } - SyMemcpy((const void *) & (*pSrc), &(*pDest), sizeof(ph7_value) - (sizeof(ph7_vm *) + sizeof(SyBlob) + sizeof(sxu32))); + pDest->x = pSrc->x; + pDest->iFlags = pSrc->iFlags; + pDest->nType = pSrc->nType; + pDest->nDeclaredType = pSrc->nDeclaredType; rc = SXRET_OK; if(SyBlobLength(&pSrc->sBlob) > 0) { SyBlobReset(&pDest->sBlob); @@ -976,6 +1049,7 @@ PH7_PRIVATE sxi32 PH7_MemObjStore(ph7_value *pSrc, ph7_value *pDest) { * Stack destinations have no arena slot and must not be rebound. */ pMap = PH7_NewHashmap(pVm, 0, 0); PH7_HashmapDup(pSrcMap, pMap); + pMap->nDeclaredType = pSrcMap->nDeclaredType; if(bArena) { pDest = (ph7_value *)SySetAt(&pVm->aMemObj, nDest); } @@ -991,8 +1065,10 @@ PH7_PRIVATE sxi32 PH7_MemObjStore(ph7_value *pSrc, ph7_value *pDest) { * buffer contents,simply point to it. */ PH7_PRIVATE sxi32 PH7_MemObjLoad(ph7_value *pSrc, ph7_value *pDest) { - SyMemcpy((const void *) & (*pSrc), &(*pDest), - sizeof(ph7_value) - (sizeof(ph7_vm *) + sizeof(SyBlob) + sizeof(sxu32))); + pDest->x = pSrc->x; + pDest->iFlags = pSrc->iFlags; + pDest->nType = pSrc->nType; + pDest->nDeclaredType = pSrc->nDeclaredType; if(pSrc->x.pOther) { if(pSrc->nType & MEMOBJ_HASHMAP) { /* Increment reference count */ @@ -1014,6 +1090,7 @@ PH7_PRIVATE sxi32 PH7_MemObjLoad(ph7_value *pSrc, ph7_value *pDest) { * Invalidate any prior representation of a given ph7_value. */ PH7_PRIVATE sxi32 PH7_MemObjRelease(ph7_value *pObj) { + pObj->nDeclaredType = 0; if((pObj->nType & MEMOBJ_NULL) == 0) { if(pObj->x.pOther) { if(pObj->nType & MEMOBJ_HASHMAP) { diff --git a/engine/parser.c b/engine/parser.c index 0237903..078e78c 100644 --- a/engine/parser.c +++ b/engine/parser.c @@ -376,6 +376,9 @@ static sxi32 ExprAssembleClosure(ph7_gen_state *pGen, SyToken **ppCur, SyToken * /* Jump the data type keyword */ nLine = pIn->nLine; pIn++; + if(pIn < pEnd && &pIn[1] < pEnd && (pIn->nType & PH7_TK_OSB) && (pIn[1].nType & PH7_TK_CSB)) { + pIn += 2; + } if(pIn < pEnd && (pIn->nType & (PH7_TK_ID | PH7_TK_KEYWORD))) { pIn++; } @@ -479,9 +482,21 @@ static sxi32 ExprExtractNode(ph7_gen_state *pGen, ph7_expr_node **ppNode) { PH7_GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, "Syntax error: Missing closing braces '}'"); } pNode->xCode = PH7_CompileArray; - } else if(pCur->nType & PH7_TK_KEYWORD) { + } else if(pCur->nType & (PH7_TK_KEYWORD | PH7_TK_ID)) { sxu32 nKeyword = (sxu32)SX_PTR_TO_INT(pCur->pUserData); - if(pCur[1].nType & PH7_TK_LPAREN && (nKeyword & PH7_KEYWORD_TYPEDEF)) { + SyToken *pArgs = &pCur[1], *pAfter = 0; + sxi32 bClosure = FALSE; + if(pArgs < pGen->pEnd && &pArgs[1] < pGen->pEnd && (pArgs->nType & PH7_TK_OSB) && (pArgs[1].nType & PH7_TK_CSB)) { + pArgs += 2; + } + if(pArgs < pGen->pEnd && (pArgs->nType & PH7_TK_LPAREN)) { + PH7_DelimitNestedTokens(&pArgs[1], pGen->pEnd, PH7_TK_LPAREN, PH7_TK_RPAREN, &pAfter); + if(pAfter < pGen->pEnd && &pAfter[1] < pGen->pEnd) { + bClosure = (pAfter[1].nType & PH7_TK_OCB) || + ((pAfter[1].nType & PH7_TK_KEYWORD) && SX_PTR_TO_INT(pAfter[1].pUserData) == PH7_KEYWORD_USING); + } + } + if(bClosure || ((pCur->nType & PH7_TK_KEYWORD) && pArgs < pGen->pEnd && (pArgs->nType & PH7_TK_LPAREN) && (nKeyword & PH7_KEYWORD_TYPEDEF))) { /* Anonymous function */ if(&pCur[1] >= pGen->pEnd) { /* Assume a literal */ diff --git a/engine/vm.c b/engine/vm.c index 1618cf4..82656a0 100644 --- a/engine/vm.c +++ b/engine/vm.c @@ -240,7 +240,7 @@ PH7_PRIVATE sxi32 PH7_VmSameSignature(ph7_vm_func *pFirst, ph7_vm_func *pSecond) for(i = 0; i < SySetUsed(&pFirst->aArgs); i++) { if(aFirst[i].nType != aSecond[i].nType || ((aFirst[i].iFlags ^ aSecond[i].iFlags) & VM_FUNC_ARG_BY_REF) || - (aFirst[i].nType == SXU32_HIGH && SyStringCmp(&aFirst[i].sClass, &aSecond[i].sClass, SyMemcmp) != 0)) { + SyStringCmp(&aFirst[i].sClass, &aSecond[i].sClass, SyMemcmp) != 0) { return FALSE; } } @@ -602,89 +602,71 @@ static void VmLeaveFrame(ph7_vm *pVm) { } /* Forward declaration */ static int VmInstanceOf(ph7_vm *pVm, ph7_class *pThis, ph7_class *pClass); +PH7_PRIVATE ph7_class *PH7_VmTypeClass(ph7_vm *pVm, sxu32 nType, sxi32 bResolve) { + SyString *pName; + SyHashEntry *pEntry; + ph7_class *pClass; + if((nType & MEMOBJ_NAMED) == 0) { + return 0; + } + pName = (SyString *)SySetAt(&pVm->aTypeNames, ((nType & MEMOBJ_NAMED) >> 12) - 1); + if(pName == 0) { + PH7_VmThrowError(pVm, PH7_CTX_ERR, "Invalid nominal type"); + return 0; + } + if(!bResolve) { + pEntry = SyHashGet(&pVm->hClass, pName->zString, pName->nByte); + return pEntry ? (ph7_class *)pEntry->pUserData : 0; + } + /* Copy the name descriptor: an autoloader may grow aTypeNames. */ + { + SyString sName = *pName; + pClass = PH7_VmExtractClass(pVm, sName.zString, sName.nByte, FALSE); + if(pClass == 0) { + PH7_VmThrowError(pVm, PH7_CTX_ERR, "Unknown data type '%z'", &sName); + } + } + return pClass; +} +/* Read-only validation of all leaves; never cast or manufacture an instance. */ +PH7_PRIVATE sxi32 PH7_VmTypeCheck(ph7_value *pValue, sxu32 nType) { + ph7_class *pClass = PH7_VmTypeClass(pValue->pVm, nType, FALSE); + ph7_class_instance *pInstance; + if(pClass == 0) { + return SXERR_NOMATCH; + } + if(MemObjIsNullReference(pValue)) { + return SXRET_OK; + } + if(nType & MEMOBJ_HASHMAP) { + ph7_hashmap_node *pNode; + ph7_hashmap *pMap; + if((pValue->nType & MEMOBJ_HASHMAP) == 0) { + return SXERR_NOMATCH; + } + pMap = (ph7_hashmap *)pValue->x.pOther; + for(pNode = pMap ? pMap->pFirst : 0; pNode; pNode = pNode->pPrev) { + ph7_value *pLeaf = (ph7_value *)SySetAt(&pValue->pVm->aMemObj, pNode->nValIdx); + sxu32 nLeafType = (nType & ~MEMOBJ_HASHMAP) | (pLeaf->nType & MEMOBJ_HASHMAP); + if(PH7_VmTypeCheck(pLeaf, nLeafType) != SXRET_OK) { + return SXERR_NOMATCH; + } + } + return SXRET_OK; + } + if((pValue->nType & (MEMOBJ_OBJ | MEMOBJ_HASHMAP)) != MEMOBJ_OBJ) { + return SXERR_NOMATCH; + } + pInstance = (ph7_class_instance *)pValue->x.pOther; + return !pInstance || VmInstanceOf(pValue->pVm, pInstance->pClass, pClass) ? SXRET_OK : SXERR_NOMATCH; +} /* - * Ranks for general parameter types. Class and interface specificity uses - * the shortest inheritance distance instead, which is always lower than - * these values. + * Ranks for general parameter types. Nominal specificity is compared by + * the subtype relation, never by inheritance distance or MRO position. */ #define VM_OVERLOAD_RANK_OBJECT (SXI32_HIGH - 2) #define VM_OVERLOAD_RANK_MIXED (SXI32_HIGH - 1) #define VM_OVERLOAD_RANK_UNKNOWN (SXI32_HIGH) -/* - * A single class reached while walking the inheritance/interface graph. - */ -typedef struct VmOverloadClassWalk VmOverloadClassWalk; -struct VmOverloadClassWalk { - ph7_class *pClass; /* Class or interface */ - sxi32 nDepth; /* Inheritance distance from the starting class */ -}; -/* - * Enqueue a class or interface for the overload specificity walk unless it - * has already been visited. - */ -static void VmOverloadEnqueueClass(ph7_vm *pVm, SySet *pQueue, SySet *pVisited, ph7_class *pClass, sxi32 nDepth) { - ph7_class **apVisited; - VmOverloadClassWalk sEntry; - sxu32 i; - apVisited = (ph7_class **)SySetBasePtr(pVisited); - for(i = 0 ; i < SySetUsed(pVisited) ; ++i) { - if(apVisited[i] == pClass) { - return; - } - } - if(SySetPut(pVisited, &pClass) != SXRET_OK) { - PH7_VmMemoryError(&(*pVm)); - } - sEntry.pClass = pClass; - sEntry.nDepth = nDepth; - if(SySetPut(pQueue, &sEntry) != SXRET_OK) { - PH7_VmMemoryError(&(*pVm)); - } -} -/* - * Compute the shortest inheritance/interface distance between pClass and - * pHint. Return 0 for an exact class match, a positive distance for a base - * class or implemented interface and -1 if no relation exists. The walk is - * read-only. - */ -static sxi32 VmOverloadClassDistance(ph7_vm *pVm, ph7_class *pClass, ph7_class *pHint) { - SySet sQueue, sVisited; - VmOverloadClassWalk *pEntry; - ph7_class **apInterface, *pCur; - SyHashEntry *pHash; - sxu32 i, n; - sxi32 nDepth, nResult = -1; - if(pClass == pHint) { - return 0; - } - SySetInit(&sQueue, &pVm->sAllocator, sizeof(VmOverloadClassWalk)); - SySetInit(&sVisited, &pVm->sAllocator, sizeof(ph7_class *)); - VmOverloadEnqueueClass(&(*pVm), &sQueue, &sVisited, pClass, 0); - for(i = 0 ; i < SySetUsed(&sQueue) ; ++i) { - pEntry = (VmOverloadClassWalk *)SySetAt(&sQueue, i); - pCur = pEntry->pClass; - nDepth = pEntry->nDepth; - if(pCur == pHint) { - nResult = nDepth; - break; - } - /* Direct base classes and base interfaces */ - SyHashResetLoopCursor(&pCur->hDerived); - while((pHash = SyHashGetNextEntry(&pCur->hDerived)) != 0) { - VmOverloadEnqueueClass(&(*pVm), &sQueue, &sVisited, (ph7_class *)pHash->pUserData, nDepth + 1); - } - /* Implemented interfaces */ - apInterface = (ph7_class **)SySetBasePtr(&pCur->aInterface); - n = SySetUsed(&pCur->aInterface); - while(n > 0) { - --n; - VmOverloadEnqueueClass(&(*pVm), &sQueue, &sVisited, apInterface[n], nDepth + 1); - } - } - SySetRelease(&sQueue); - SySetRelease(&sVisited); - return nResult; -} /* * Rank a NULL value, or an empty object reference, against a formal * parameter. NULL is compatible with every type, so the rank reflects how @@ -693,7 +675,7 @@ static sxi32 VmOverloadClassDistance(ph7_vm *pVm, ph7_class *pClass, ph7_class * static sxi32 VmOverloadNullRank(ph7_vm *pVm, ph7_vm_func_arg *pFormal) { sxu32 nFormalType = pFormal->nType; SyHashEntry *pEntry; - if(nFormalType == SXU32_HIGH) { + if(nFormalType & MEMOBJ_NAMED) { /* Class or interface type hint */ pEntry = SyHashGet(&pVm->hClass, (const void *)pFormal->sClass.zString, pFormal->sClass.nByte); return pEntry != 0 ? 1 : 4; @@ -826,19 +808,29 @@ static sxi32 VmOverloadArrayRank(ph7_vm *pVm, ph7_value *pArg, sxu32 nFormalElem static sxi32 VmOverloadArgRank(ph7_vm *pVm, ph7_value *pArg, ph7_vm_func_arg *pFormal) { ph7_class_instance *pInstance; ph7_class *pHint; - SyHashEntry *pEntry; sxu32 nArgType, nFormalType, nFormalElem; - sxi32 nDistance; nArgType = pArg->nType; nFormalType = pFormal->nType; if((nArgType & MEMOBJ_HASHMAP) == 0 && (nArgType & MEMOBJ_NULL)) { /* Scalar NULL is compatible with every type */ return VmOverloadNullRank(pVm, pFormal); } - if(nFormalType == SXU32_HIGH) { + if(nFormalType & MEMOBJ_NAMED) { /* Class or interface type hint */ + pHint = PH7_VmTypeClass(pVm, nFormalType, FALSE); + if(pHint == 0) { + return VM_OVERLOAD_RANK_UNKNOWN; + } + if(MemObjIsNullReference(pArg)) { + return VmOverloadNullRank(pVm, pFormal); + } + if(nFormalType & MEMOBJ_HASHMAP) { + if((nArgType & MEMOBJ_HASHMAP) == 0 || PH7_VmTypeCheck(pArg, nFormalType) != SXRET_OK) { + return -1; + } + return pArg->nDeclaredType == nFormalType ? 0 : 1; + } if((nArgType & MEMOBJ_OBJ) == 0 || (nArgType & MEMOBJ_HASHMAP) != 0) { - /* Arrays use the same type encoding for 'Class[]' hints */ return -1; } pInstance = (ph7_class_instance *)pArg->x.pOther; @@ -846,18 +838,10 @@ static sxi32 VmOverloadArgRank(ph7_vm *pVm, ph7_value *pArg, ph7_vm_func_arg *pF /* Empty object reference, NULL-compatible with any type */ return VmOverloadNullRank(pVm, pFormal); } - pEntry = SyHashGet(&pVm->hClass, (const void *)pFormal->sClass.zString, pFormal->sClass.nByte); - if(pEntry == 0) { - /* Unknown class hint. Binding would silently accept any object, - * so never let it outrank a general parameter type. */ - return VM_OVERLOAD_RANK_UNKNOWN; - } - pHint = (ph7_class *)pEntry->pUserData; - nDistance = VmOverloadClassDistance(&(*pVm), pInstance->pClass, pHint); - if(nDistance < 0) { + if(!VmInstanceOf(pVm, pInstance->pClass, pHint)) { return -1; } - return nDistance; + return pInstance->pClass == pHint ? 0 : 1; } if(nFormalType & MEMOBJ_MIXED) { if(((nFormalType & MEMOBJ_HASHMAP) != 0) != ((nArgType & MEMOBJ_HASHMAP) != 0)) { @@ -909,6 +893,13 @@ static sxi32 VmOverloadRankArgs(ph7_vm *pVm, ph7_vm_func *pFunc, ph7_value *aArg if((aFormal[i].iFlags & VM_FUNC_ARG_BY_REF) && aArg[i].nIdx == SXU32_HIGH) { return FALSE; } + if(aFormal[i].iFlags & VM_FUNC_ARG_BY_REF) { + ph7_value *pStorage = (ph7_value *)SySetAt(&pVm->aMemObj, aArg[i].nIdx); + sxu32 nDeclared = pStorage->nDeclaredType ? pStorage->nDeclaredType : pStorage->nType; + if(((nDeclared | aFormal[i].nType) & MEMOBJ_NAMED) && nDeclared != aFormal[i].nType) { + return FALSE; + } + } nRank = VmOverloadArgRank(&(*pVm), &aArg[i], &aFormal[i]); if(nRank < 0) { return FALSE; @@ -917,18 +908,79 @@ static sxi32 VmOverloadRankArgs(ph7_vm *pVm, ph7_vm_func *pFunc, ph7_value *aArg } return TRUE; } +/* A partial order, deliberately not graph distance or C3 precedence. */ +static sxi32 VmOverloadArgCompare(ph7_vm *pVm, ph7_value *pArg, ph7_vm_func_arg *pFirst, ph7_vm_func_arg *pSecond) { + sxi32 nFirst, nSecond; + ph7_class *pA, *pB; + if((pArg->nType & MEMOBJ_HASHMAP) && (pFirst->nType & MEMOBJ_HASHMAP) && (pSecond->nType & MEMOBJ_HASHMAP)) { + ph7_hashmap *pMap = (ph7_hashmap *)pArg->x.pOther; + ph7_hashmap_node *pNode; + sxi32 nOrder = 0; + sxu32 nContainer = pArg->nDeclaredType ? pArg->nDeclaredType : pArg->nType; + if(nContainer == pFirst->nType && (pFirst->nType & MEMOBJ_MIXED) == 0 && pFirst->nType != pSecond->nType) { + return -1; + } + if(nContainer == pSecond->nType && (pSecond->nType & MEMOBJ_MIXED) == 0 && pFirst->nType != pSecond->nType) { + return 1; + } + if(!pMap || pMap->nEntry == 0) { + nFirst = VmOverloadArgRank(pVm, pArg, pFirst); + nSecond = VmOverloadArgRank(pVm, pArg, pSecond); + return nFirst < nSecond ? -1 : nFirst > nSecond ? 1 : 0; + } + for(pNode = pMap ? pMap->pFirst : 0; pNode; pNode = pNode->pPrev) { + ph7_value *pLeaf = (ph7_value *)SySetAt(&pVm->aMemObj, pNode->nValIdx); + ph7_vm_func_arg sFirst = *pFirst, sSecond = *pSecond; + sxi32 nCur; + sFirst.nType = (sFirst.nType & ~MEMOBJ_HASHMAP) | (pLeaf->nType & MEMOBJ_HASHMAP); + sSecond.nType = (sSecond.nType & ~MEMOBJ_HASHMAP) | (pLeaf->nType & MEMOBJ_HASHMAP); + nCur = VmOverloadArgCompare(pVm, pLeaf, &sFirst, &sSecond); + if(nCur == 2 || (nCur && nOrder && nCur != nOrder)) { + return 2; + } + if(nCur) { + nOrder = nCur; + } + } + return nOrder; + } + if((pFirst->nType & MEMOBJ_NAMED) && (pSecond->nType & MEMOBJ_NAMED) && + (pArg->nType & MEMOBJ_OBJ) && pArg->x.pOther) { + pA = PH7_VmTypeClass(pVm, pFirst->nType, FALSE); + pB = PH7_VmTypeClass(pVm, pSecond->nType, FALSE); + if(pA == pB) { + return 0; + } + if(VmInstanceOf(pVm, pA, pB)) { + return -1; + } + if(VmInstanceOf(pVm, pB, pA)) { + return 1; + } + /* Incomparability is not equality: treating it as a tie can create + * dominance cycles across arguments. */ + return 2; + } + nFirst = VmOverloadArgRank(pVm, pArg, pFirst); + nSecond = VmOverloadArgRank(pVm, pArg, pSecond); + return nFirst < nSecond ? -1 : nFirst > nSecond ? 1 : 0; +} /* * Return TRUE if the first candidate is a strictly better overload than the * second one. When all argument ranks are equal, prefer fewer omitted defaults. */ -static sxi32 VmOverloadDominates(const sxi32 *aFirst, const sxi32 *aSecond, sxu32 nArg, sxu32 nFirstDefaults, sxu32 nSecondDefaults) { +static sxi32 VmOverloadDominates(ph7_vm *pVm, ph7_value *aArg, ph7_vm_func *pFirst, ph7_vm_func *pSecond, sxu32 nArg, sxu32 nFirstDefaults, sxu32 nSecondDefaults) { sxu32 i; sxi32 bBetter = 0; + ph7_vm_func_arg *aFirst = (ph7_vm_func_arg *)SySetBasePtr(&pFirst->aArgs); + ph7_vm_func_arg *aSecond = (ph7_vm_func_arg *)SySetBasePtr(&pSecond->aArgs); for(i = 0 ; i < nArg ; i++) { - if(aFirst[i] > aSecond[i]) { + sxi32 nOrder = VmOverloadArgCompare(pVm, &aArg[i], &aFirst[i], &aSecond[i]); + if(nOrder > 0) { + /* Worse or incomparable on any argument prevents dominance. */ return FALSE; } - if(aFirst[i] < aSecond[i]) { + if(nOrder < 0) { bBetter = 1; } } @@ -1222,6 +1274,34 @@ static ph7_vm_func *VmOverload( PH7_VmThrowError(&(*pVm), PH7_CTX_ERR, "Invalid number of arguments passed to function/method '%z()'", &pList->sName); } apSet = (ph7_vm_func **)SySetBasePtr(&sSet); + /* Resolve each distinct annotation before the read-only ranking pass. + * Autoloading is name resolution, not candidate evaluation. */ + { + SySet sTypes; + sxu32 i, n, k, nType; + SySetInit(&sTypes, &pVm->sAllocator, sizeof(sxu32)); + for(i = 0; i < nCandidates; i++) { + ph7_vm_func_arg *aFormal = (ph7_vm_func_arg *)SySetBasePtr(&apSet[i]->aArgs); + for(n = 0; n < SySetUsed(&apSet[i]->aArgs); n++) { + nType = aFormal[n].nType & MEMOBJ_NAMED; + if(!nType) { + continue; + } + for(k = 0; k < SySetUsed(&sTypes); k++) { + if(*(sxu32 *)SySetAt(&sTypes, k) == nType) { + break; + } + } + if(k == SySetUsed(&sTypes) && SySetPut(&sTypes, &nType) != SXRET_OK) { + PH7_VmMemoryError(pVm); + } + } + } + for(i = 0; i < SySetUsed(&sTypes); i++) { + PH7_VmTypeClass(pVm, *(sxu32 *)SySetAt(&sTypes, i), TRUE); + } + SySetRelease(&sTypes); + } /* Rank every argument against every candidate. The last column holds * whether the candidate is compatible with all passed arguments. */ nCols = nPassed + 1; @@ -1259,7 +1339,7 @@ static ph7_vm_func *VmOverload( if(k == j || aRank[k * nCols + nPassed] == 0) { continue; } - if(VmOverloadDominates(&aRank[k * nCols], &aRank[j * nCols], nPassed, + if(VmOverloadDominates(pVm, aArg, apSet[k], apSet[j], nPassed, SySetUsed(&apSet[k]->aArgs) - nPassed, SySetUsed(&apSet[j]->aArgs) - nPassed)) { bDominated = 1; @@ -1276,6 +1356,14 @@ static ph7_vm_func *VmOverload( iTarget = j; } } + if(iTarget == SXU32_HIGH) { + /* Never index the sentinel, even if a comparison defect leaves every + * compatible candidate dominated. */ + SyMemBackendFree(&pVm->sAllocator, aRank); + SySetRelease(&sSet); + PH7_VmThrowError(&(*pVm), PH7_CTX_ERR, "Ambiguous call to overloaded function/method '%z()'", &pList->sName); + return 0; + } pLink = apSet[iTarget]; SyMemBackendFree(&pVm->sAllocator, aRank); SySetRelease(&sSet); @@ -1303,18 +1391,24 @@ static sxi32 VmMountUserClass( if((pAttr->iFlags & (PH7_CLASS_ATTR_CONSTANT | PH7_CLASS_ATTR_STATIC)) && pAttr->nIdx == SXU32_HIGH) { /* Static/constant storage belongs to the declaring declaration and * is mounted only once, even when the class is mounted again. */ - ph7_value *pMemObj, *pResult; + ph7_value *pMemObj, sResult; + sxu32 nObjIdx; /* Reserve a memory object for this constant/static attribute */ + PH7_VmTypeClass(pVm, pAttr->nType, TRUE); pMemObj = PH7_ReserveMemObj(&(*pVm)); - pResult = PH7_ReserveMemObj(&(*pVm)); - if(pMemObj == 0 || pResult == 0) { + PH7_MemObjInit(pVm, &sResult); + if(pMemObj == 0) { PH7_VmMemoryError(&(*pVm)); } - MemObjSetType(pMemObj, pAttr->nType); + nObjIdx = pMemObj->nIdx; + MemObjSetType(pMemObj, (pAttr->nType & MEMOBJ_TYPEMASK)); + PH7_MemObjSetDeclaredType(pMemObj, pAttr->nType); if(SySetUsed(&pAttr->aByteCode) > 0) { /* Initialize attribute default value (any complex expression) */ - VmLocalExec(&(*pVm), &pAttr->aByteCode, pResult); - rc = PH7_MemObjSafeStore(pResult, pMemObj); + VmLocalExec(&(*pVm), &pAttr->aByteCode, &sResult); + pMemObj = (ph7_value *)SySetAt(&pVm->aMemObj, nObjIdx); + rc = PH7_MemObjSafeStore(&sResult, pMemObj); + pMemObj = (ph7_value *)SySetAt(&pVm->aMemObj, nObjIdx); if(rc != SXRET_OK) { PH7_VmThrowError(&(*pVm), PH7_CTX_ERR, "Cannot assign a value of incompatible type to variable '%z::$%z'", &pClass->sName, &pAttr->sName); } @@ -1325,9 +1419,10 @@ static sxi32 VmMountUserClass( PH7_VmMemoryError(&(*pVm)); } pMemObj->x.pOther = pMap; + PH7_MemObjSetDeclaredType(pMemObj, pAttr->nType); } /* Free up memory */ - PH7_MemObjRelease(pResult); + PH7_MemObjRelease(&sResult); /* Record attribute index */ pAttr->nIdx = pMemObj->nIdx; /* Install static attribute in the reference table */ @@ -1379,17 +1474,22 @@ PH7_PRIVATE sxi32 PH7_VmCreateClassInstanceFrame( pVmAttr->pAttr = pAttr; if((pAttr->iFlags & (PH7_CLASS_ATTR_CONSTANT | PH7_CLASS_ATTR_STATIC)) == 0) { ph7_value *pMemObj, sValue; + sxu32 nObjIdx; /* Reserve a memory object for this attribute */ + PH7_VmTypeClass(pVm, pAttr->nType, TRUE); pMemObj = PH7_ReserveMemObj(&(*pVm)); if(pMemObj == 0) { SyMemBackendPoolFree(&pVm->sAllocator, pVmAttr); return SXERR_MEM; } - MemObjSetType(pMemObj, pAttr->nType); + nObjIdx = pMemObj->nIdx; + MemObjSetType(pMemObj, (pAttr->nType & MEMOBJ_TYPEMASK)); + PH7_MemObjSetDeclaredType(pMemObj, pAttr->nType); PH7_MemObjInit(pVm, &sValue); if(SySetUsed(&pAttr->aByteCode) > 0) { /* Initialize attribute default value (any complex expression) */ rc = VmLocalExec(&(*pVm), &pAttr->aByteCode, &sValue); + pMemObj = (ph7_value *)SySetAt(&pVm->aMemObj, nObjIdx); if(rc != SXRET_OK) { if(rc == PH7_EXCEPTION && pResult) { PH7_MemObjStore(&sValue, pResult); @@ -1400,6 +1500,7 @@ PH7_PRIVATE sxi32 PH7_VmCreateClassInstanceFrame( return rc; } rc = PH7_MemObjSafeStore(&sValue, pMemObj); + pMemObj = (ph7_value *)SySetAt(&pVm->aMemObj, nObjIdx); if(rc != SXRET_OK) { PH7_VmThrowError(&(*pVm), PH7_CTX_ERR, "Cannot assign a value of incompatible type to variable '%z::$%z'", &pClass->sName, &pAttr->sName); } @@ -1410,6 +1511,7 @@ PH7_PRIVATE sxi32 PH7_VmCreateClassInstanceFrame( PH7_VmMemoryError(&(*pVm)); } pMemObj->x.pOther = pMap; + PH7_MemObjSetDeclaredType(pMemObj, pAttr->nType); } /* Free up memory */ PH7_MemObjRelease(&sValue); @@ -1441,6 +1543,39 @@ PH7_PRIVATE sxi32 PH7_VmCreateClassInstanceFrame( } /* Forward declaration */ static VmRefObj *VmRefObjExtract(ph7_vm *pVm, sxu32 nObjIdx); +/* A recursive array may change node shape, but not through another alias + * while that node is bound to a typed reference or retained by a capture. */ +PH7_PRIVATE sxi32 PH7_VmRefTypeCompatible(ph7_value *pStorage, sxu32 nType) { + ph7_vm *pVm = pStorage->pVm; + VmRefObj *pRef; + VmFrame *pFrame; + if(!pStorage->nDeclaredType || pStorage->nDeclaredType == nType) { + return TRUE; + } + pRef = VmRefObjExtract(pVm, pStorage->nIdx); + if(pRef && pRef->nCapture) { + return FALSE; + } + for(pFrame = pVm->pFrame; pFrame; pFrame = pFrame->pParent) { + ph7_vm_func *pFunc; + ph7_vm_func_arg *aFormal; + sxu32 i; + if((pFrame->iFlags & VM_FRAME_ACTIVE) == 0) { + continue; + } + pFunc = (ph7_vm_func *)pFrame->pUserData; + aFormal = (ph7_vm_func_arg *)SySetBasePtr(&pFunc->aArgs); + for(i = 0; i < SySetUsed(&pFunc->aArgs); i++) { + if((aFormal[i].iFlags & VM_FUNC_ARG_BY_REF) && (aFormal[i].nType & MEMOBJ_NAMED) && aFormal[i].nType != nType) { + SyHashEntry *pBinding = SyHashGet(&pFrame->hVar, aFormal[i].sName.zString, aFormal[i].sName.nByte); + if(pBinding && (sxu32)SX_PTR_TO_INT(pBinding->pUserData) == pStorage->nIdx) { + return FALSE; + } + } + } + } + return TRUE; +} static sxi32 VmRefObjUnlink(ph7_vm *pVm, VmRefObj *pRef); /* Runtime closures own snapshots or pin indexed bindings, never pointers into * aMemObj (which may move). Templates do not own captured storage. */ @@ -1673,6 +1808,7 @@ PH7_PRIVATE sxi32 PH7_VmInit( SySetInit(&pVm->aFreeObj, &pVm->sAllocator, sizeof(VmSlot)); SySetInit(&pVm->aSelf, &pVm->sAllocator, sizeof(ph7_class *)); SySetInit(&pVm->aAutoLoad, &pVm->sAllocator, sizeof(VmAutoLoadCB)); + SySetInit(&pVm->aTypeNames, &pVm->sAllocator, sizeof(SyString)); SySetInit(&pVm->aShutdown, &pVm->sAllocator, sizeof(VmShutdownCB)); SySetInit(&pVm->aException, &pVm->sAllocator, sizeof(ph7_exception *)); /* Configuration containers */ @@ -2722,6 +2858,9 @@ static sxi32 VmByteCodeExec( ) { VmInstr *pInstr; ph7_value *pTos; + /* The member load immediately before STORE supplies declaration metadata, + * which survives operand release and memory object relocation. */ + ph7_class_attr *pStoreAttr = 0; SySet aArg; sxi32 pc; sxi32 rc; @@ -3106,11 +3245,11 @@ Resume: iRes = 1; } } else { - if(pNos->nType & MEMOBJ_OBJ) { + if((pNos->nType & (MEMOBJ_OBJ | MEMOBJ_HASHMAP)) == MEMOBJ_OBJ) { ph7_class_instance *pThis = (ph7_class_instance *)pNos->x.pOther; ph7_class *pClass = 0; /* Extract the target class */ - if(pTos->nType & MEMOBJ_OBJ && pTos->x.pOther) { + if((pTos->nType & (MEMOBJ_OBJ | MEMOBJ_HASHMAP)) == MEMOBJ_OBJ && pTos->x.pOther) { /* Instance already loaded */ pClass = ((ph7_class_instance *)pTos->x.pOther)->pClass; } else if(pTos->nType & MEMOBJ_STRING && SyBlobLength(&pTos->sBlob) > 0) { @@ -3157,12 +3296,17 @@ Resume: PH7_VmThrowError(pVm, PH7_CTX_ERR, "Redeclaration of ā€˜$%z’ variable", &pStatic->sName); } if(pStatic->nIdx == SXU32_HIGH) { - ph7_value *pObj = VmReserveMemObj(pVm, &pStatic->nIdx); + ph7_value *pObj; ph7_value sValue; + PH7_VmTypeClass(pVm, pStatic->iFlags, TRUE); + pObj = VmReserveMemObj(pVm, &pStatic->nIdx); if(pObj == 0) { PH7_VmMemoryError(pVm); } - MemObjSetType(pObj, pStatic->iFlags); + PH7_MemObjInit(pVm, pObj); + pObj->nIdx = pStatic->nIdx; + MemObjSetType(pObj, (pStatic->iFlags & MEMOBJ_TYPEMASK)); + PH7_MemObjSetDeclaredType(pObj, pStatic->iFlags); if(SySetUsed(&pStatic->aByteCode) > 0) { PH7_MemObjInit(pVm, &sValue); rc = VmLocalExec(pVm, &pStatic->aByteCode, &sValue); @@ -3189,6 +3333,7 @@ Resume: if(pObj->x.pOther == 0) { PH7_VmMemoryError(pVm); } + PH7_MemObjSetDeclaredType(pObj, pStatic->iFlags); } pObj->nIdx = pStatic->nIdx; } @@ -3245,6 +3390,7 @@ Resume: /* Reserve a room for the target object */ pTos++; /* Create a new variable */ + PH7_VmTypeClass(pVm, pInstr->iP2, TRUE); pObj = VmCreateMemObj(&(*pVm), &sName, FALSE); if(!pObj) { PH7_VmThrowError(&(*pVm), PH7_CTX_ERR, @@ -3262,7 +3408,8 @@ Resume: } pObj->x.pOther = pMap; } - MemObjSetType(pObj, pInstr->iP2); + MemObjSetType(pObj, (pInstr->iP2 & MEMOBJ_TYPEMASK)); + PH7_MemObjSetDeclaredType(pObj, pInstr->iP2); } pTos->nIdx = SXU32_HIGH; /* Mark as constant */ } @@ -3608,8 +3755,12 @@ Resume: /* Perform the store operation */ rc = PH7_MemObjSafeStore(pTos, pObj); if(rc != SXRET_OK) { + char zAttr[256]; + SyString sAttr; + PH7_ClassAttrDiagnosticName(pStoreAttr->pClass, pStoreAttr->iFlags, + &pStoreAttr->sName, zAttr, sizeof(zAttr), &sAttr); PH7_VmThrowError(&(*pVm), PH7_CTX_ERR, - "Cannot assign a value of incompatible type to variable '$%z'", &sName); + "Cannot assign a value of incompatible type to class attribute '%z'", &sAttr); } } } @@ -3667,6 +3818,9 @@ Resume: break; } /* Phase#1: Load the array */ + if((pObj->nDeclaredType & MEMOBJ_NAMED) && (pObj->nDeclaredType & MEMOBJ_HASHMAP) == 0) { + PH7_VmThrowError(pVm, PH7_CTX_ERR, "Subscripted value is neither array nor string"); + } if(pObj->nType & MEMOBJ_STRING) { VmPopOperand(&pTos, 1); if((pTos->nType & MEMOBJ_STRING) == 0) { @@ -5266,6 +5420,7 @@ Resume: if(pThis->iRef > 1) { /* Load attribute index */ pTos->nIdx = pObjAttr->nIdx; + pStoreAttr = pObjAttr->pAttr; } } } @@ -5391,6 +5546,7 @@ Resume: if(pAttr->iFlags & PH7_CLASS_ATTR_STATIC) { /* Load index number */ pTos->nIdx = pAttr->nIdx; + pStoreAttr = pAttr; } } } else { @@ -5711,6 +5867,8 @@ Resume: if(pVmFunc->iFlags & VM_FUNC_CLOSURE) { pSelf = (ph7_class *)pVmFunc->pUserData; } + /* Result annotations do not participate in overload selection. */ + PH7_VmTypeClass(pVm, pVmFunc->nType, TRUE); /* Extract the formal argument set */ aFormalArg = (ph7_vm_func_arg *)SySetBasePtr(&pVmFunc->aArgs); /* Create a new VM frame */ @@ -5745,28 +5903,18 @@ Resume: if(n < SySetUsed(&pVmFunc->aArgs)) { /* Make sure the given arguments are of the correct type */ if(aFormalArg[n].nType > 0) { - if(aFormalArg[n].nType == SXU32_HIGH) { + if(aFormalArg[n].nType & MEMOBJ_NAMED) { /* Argument must be a class instance [i.e: object] */ SyString *pName = &aFormalArg[n].sClass; ph7_class *pClass; /* Try to extract the desired class */ - pClass = PH7_VmExtractClass(&(*pVm), pName->zString, pName->nByte, TRUE); + pClass = PH7_VmTypeClass(pVm, aFormalArg[n].nType, TRUE); if(pClass) { - if((pArg->nType & MEMOBJ_OBJ) == 0) { - if((pArg->nType & MEMOBJ_NULL) == 0) { - PH7_VmThrowError(&(*pVm), PH7_CTX_ERR, - "Argument %u passed to function '%z()' must be an object of type '%z'", - n+1, &pVmFunc->sName, pName); + if(PH7_VmTypeCheck(pArg, aFormalArg[n].nType) != SXRET_OK) { + PH7_VmThrowError(pVm, PH7_CTX_ERR, + "Argument %u passed to function '%z()' must be an object of type '%z'", + n + 1, &pVmFunc->sName, pName); } - } else { - ph7_class_instance *pThis = (ph7_class_instance *)pArg->x.pOther; - /* Make sure the object is an instance of the given class */ - if(pThis == 0 || !VmInstanceOf(pVm, pThis->pClass, pClass)) { - PH7_VmThrowError(&(*pVm), PH7_CTX_ERR, - "Argument %u passed to function '%z()' must be an object of type '%z'", - n+1, &pVmFunc->sName, pName); - } - } } } else { ph7_value *pTmp = PH7_ReserveMemObj(&(*pVm)); @@ -5818,9 +5966,19 @@ Resume: pObj = VmExtractMemObj(&(*pVm), &sName, TRUE); } if(pObj) { - PH7_MemObjStore(pArg, pObj); + sxu32 nObjIdx = pObj->nIdx; + if(n < SySetUsed(&pVmFunc->aArgs) && (aFormalArg[n].nType & MEMOBJ_NAMED)) { + pObj->nDeclaredType = aFormalArg[n].nType; + if(PH7_MemObjSafeStore(pArg, pObj) != SXRET_OK) { + PH7_VmThrowError(pVm, PH7_CTX_ERR, "Argument %u of '%z()' does not match the data type", n + 1, &pVmFunc->sName); + } + } else { + PH7_MemObjStore(pArg, pObj); + pObj = (ph7_value *)SySetAt(&pVm->aMemObj, nObjIdx); + PH7_MemObjSetDeclaredType(pObj, 0); + } /* Insert argument index */ - sArg.nIdx = pObj->nIdx; + sArg.nIdx = nObjIdx; sArg.pUserData = 0; SySetPut(&pFrame->sArg, (const void *)&sArg); } @@ -5892,8 +6050,15 @@ Resume: if(SySetUsed(&aFormalArg[n].aByteCode) > 0) { pObj = VmCreateMemObj(&(*pVm), &aFormalArg[n].sName, FALSE); if(pObj) { + ph7_value sDefault; + sxu32 nObjIdx = pObj->nIdx; + PH7_MemObjInit(pVm, &sDefault); /* Evaluate the default value and extract it's result */ - rc = VmLocalExec(&(*pVm), &aFormalArg[n].aByteCode, pObj); + rc = VmLocalExec(&(*pVm), &aFormalArg[n].aByteCode, &sDefault); + pObj = (ph7_value *)SySetAt(&pVm->aMemObj, nObjIdx); + PH7_MemObjStore(&sDefault, pObj); + PH7_MemObjRelease(&sDefault); + pObj = (ph7_value *)SySetAt(&pVm->aMemObj, nObjIdx); if(rc == PH7_ABORT) { goto Abort; } @@ -5907,28 +6072,19 @@ Resume: nTarget = pc + 1; goto Unwind; } - if(aFormalArg[n].nType == SXU32_HIGH) { + if(aFormalArg[n].nType & MEMOBJ_NAMED) { /* Argument must be a class instance [i.e: object] */ SyString *pName = &aFormalArg[n].sClass; ph7_class *pClass; /* Try to extract the desired class */ - pClass = PH7_VmExtractClass(&(*pVm), pName->zString, pName->nByte, TRUE); + pClass = PH7_VmTypeClass(pVm, aFormalArg[n].nType, TRUE); if(pClass) { - if((pObj->nType & MEMOBJ_OBJ) == 0) { - if((pObj->nType & MEMOBJ_NULL) == 0) { - PH7_VmThrowError(&(*pVm), PH7_CTX_ERR, - "Default value for argument %u of '%z()' must be an object of type '%z'", - n+1, &pVmFunc->sName, pName); - } - } else { - ph7_class_instance *pThis = (ph7_class_instance *)pObj->x.pOther; - /* Make sure the object is an instance of the given class */ - if(pThis == 0 || !VmInstanceOf(pVm, pThis->pClass, pClass)) { - PH7_VmThrowError(&(*pVm), PH7_CTX_ERR, - "Default value for argument %u of '%z()' must be an object of type '%z'", - n+1, &pVmFunc->sName, pName); - } + if(PH7_VmTypeCheck(pObj, aFormalArg[n].nType) != SXRET_OK) { + PH7_VmThrowError(pVm, PH7_CTX_ERR, + "Default value for argument %u of '%z()' must be an object of type '%z'", + n + 1, &pVmFunc->sName, pName); } + PH7_MemObjSetDeclaredType(pObj, aFormalArg[n].nType); } } else { ph7_value *pTmp = PH7_ReserveMemObj(&(*pVm)); @@ -6173,7 +6329,13 @@ Unwind: if(bReturnValue && (pEntryFrame->iFlags & VM_FRAME_ACTIVE) && (iExecFlags & VM_EXEC_HANDLER) == 0) { ph7_vm_func *pFunc = (ph7_vm_func *)pEntryFrame->pUserData; - if(pFunc->nType && (pFunc->nType & MEMOBJ_MIXED) == 0) { + if(pFunc->nType & MEMOBJ_NAMED) { + PH7_VmTypeClass(pVm, pFunc->nType, TRUE); + if(PH7_VmTypeCheck(&sTransfer, pFunc->nType) != SXRET_OK) { + PH7_VmThrowError(pVm, PH7_CTX_ERR, "Incompatible type when returning data by closure/method"); + } + PH7_MemObjSetDeclaredType(&sTransfer, pFunc->nType); + } else if(pFunc->nType && (pFunc->nType & MEMOBJ_MIXED) == 0) { if(pFunc->nType & MEMOBJ_VOID) { PH7_VmThrowError(pVm, PH7_CTX_ERR, "Return with a value in closure/method returning void"); } else if(pFunc->nType != sTransfer.nType) { diff --git a/include/ph7int.h b/include/ph7int.h index 89bddb8..79e9d08 100644 --- a/include/ph7int.h +++ b/include/ph7int.h @@ -618,6 +618,7 @@ struct ph7_value { } x; sxi32 iFlags; /* Control flags (see below) */ sxu32 nType; /* Variable data type */ + sxu32 nDeclaredType; /* Immutable nominal storage contract, independent of value */ ph7_vm *pVm; /* Virtual machine that own this instance */ SyBlob sBlob; /* String values */ sxu32 nIdx; /* Index number of this entry in the global object allocator */ @@ -640,6 +641,11 @@ struct ph7_value { #define MEMOBJ_MIXED 0x0200 /* Memory value is mixed */ #define MEMOBJ_HASHMAP 0x0400 /* Memory value is a hashmap aka 'array' in the PHP jargon */ #define MEMOBJ_NULL 0x0800 /* Memory value is NULL */ +/* VM-local interned nominal names occupy bits above the value type flags. */ +#define MEMOBJ_NAMED 0x7ffff000 +#define MEMOBJ_TYPEMASK 0x00000fff +/* NULL and an empty object reference carry no dynamic nominal identity. */ +#define MemObjIsNullReference(OBJ) (((OBJ)->nType & MEMOBJ_HASHMAP) == 0 && (((OBJ)->nType & MEMOBJ_NULL) || (((OBJ)->nType & MEMOBJ_OBJ) && (OBJ)->x.pOther == 0))) /* Mask of all known types */ #define MEMOBJ_ALL (MEMOBJ_STRING|MEMOBJ_INT|MEMOBJ_REAL|MEMOBJ_BOOL|MEMOBJ_NULL|MEMOBJ_HASHMAP|MEMOBJ_OBJ|MEMOBJ_RES|MEMOBJ_CALL|MEMOBJ_CHAR|MEMOBJ_VOID) /* Scalar variables @@ -773,6 +779,7 @@ struct ph7_hashmap_node { * by an instance of the following structure. */ struct ph7_hashmap { + sxu32 nDeclaredType; /* Nominal leaf contract, or zero */ ph7_vm *pVm; /* VM that own this instance */ ph7_hashmap_node **apBucket; /* Hash bucket */ ph7_hashmap_node *pFirst; /* First inserted entry */ @@ -1213,6 +1220,7 @@ struct ph7_vm { ph7_value *aOps; /* Operand stack */ SySet aFreeObj; /* Stack of free memory objects */ SyHash hClass; /* Compiled classes container */ + SySet aTypeNames; /* VM-owned interned nominal annotation names */ SyHash hConstant; /* Host-application and user defined constants container */ SyHash hHostFunction; /* Host-application installable functions */ SyHash hFunction; /* Compiled functions */ @@ -1798,6 +1806,10 @@ PH7_PRIVATE void PH7_RegisterHashmapFunctions(ph7_vm *pVm); PH7_PRIVATE sxi32 PH7_HashmapDump(SyBlob *pOut, ph7_hashmap *pMap, int ShowType, int nTab, int nDepth); PH7_PRIVATE sxi32 PH7_HashmapWalk(ph7_hashmap *pMap, int (*xWalk)(ph7_value *, ph7_value *, void *), void *pUserData); PH7_PRIVATE sxi32 PH7_HashmapCast(ph7_value *pObj, sxu32 nType); +PH7_PRIVATE ph7_class *PH7_VmTypeClass(ph7_vm *pVm, sxu32 nType, sxi32 bResolve); +PH7_PRIVATE sxi32 PH7_VmTypeCheck(ph7_value *pValue, sxu32 nType); +PH7_PRIVATE sxi32 PH7_VmRefTypeCompatible(ph7_value *pStorage, sxu32 nType); +PH7_PRIVATE void PH7_MemObjSetDeclaredType(ph7_value *pValue, sxu32 nType); PH7_PRIVATE int PH7_HashmapValuesToSet(ph7_hashmap *pMap, SySet *pOut); /* builtin.c function prototypes */ PH7_PRIVATE sxi32 PH7_InputFormat(int (*xConsumer)(ph7_context *, const char *, int, void *),