diff options
Diffstat (limited to 'compiler')
77 files changed, 3087 insertions, 1670 deletions
diff --git a/compiler/ast.nim b/compiler/ast.nim index 694944631..40a05e6bf 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -363,7 +363,9 @@ type tyUInt, tyUInt8, tyUInt16, tyUInt32, tyUInt64, tyOptAsRef, tySink, tyLent, tyVarargs, - tyUnused, + tyUncheckedArray + # An array with boundaries [0,+∞] + tyProxy # used as errornous type (for idetools) tyBuiltInTypeClass @@ -563,13 +565,9 @@ const routineKinds* = {skProc, skFunc, skMethod, skIterator, skConverter, skMacro, skTemplate} tfIncompleteStruct* = tfVarargs - tfUncheckedArray* = tfVarargs tfUnion* = tfNoSideEffect tfGcSafe* = tfThread tfObjHasKids* = tfEnumHasHoles - tfOldSchoolExprStmt* = tfVarargs # for now used to distinguish \ - # 'varargs[expr]' from 'varargs[untyped]'. Eventually 'expr' will be - # deprecated and this mess can be cleaned up. tfReturnsNew* = tfInheritable skError* = skUnknown @@ -580,7 +578,8 @@ type TMagic* = enum # symbols that require compiler magic: mNone, mDefined, mDefinedInScope, mCompiles, mArrGet, mArrPut, mAsgn, - mLow, mHigh, mSizeOf, mTypeTrait, mIs, mOf, mAddr, mType, mTypeOf, + mLow, mHigh, mSizeOf, mAlignOf, mOffsetOf, mTypeTrait, + mIs, mOf, mAddr, mType, mTypeOf, mRoof, mPlugin, mEcho, mShallowCopy, mSlurp, mStaticExec, mStatic, mParseExprToAst, mParseStmtToAst, mExpandToAst, mQuoteAst, mUnaryLt, mInc, mDec, mOrd, @@ -628,7 +627,7 @@ type mIsPartOf, mAstToStr, mParallel, mSwap, mIsNil, mArrToSeq, mCopyStr, mCopyStrLast, mNewString, mNewStringOfCap, mParseBiggestFloat, - mMove, mWasMoved, + mMove, mWasMoved, mDestroy, mReset, mArray, mOpenArray, mRange, mSet, mSeq, mOpt, mVarargs, mRef, mPtr, mVar, mDistinct, mVoid, mTuple, @@ -657,14 +656,15 @@ type mNHint, mNWarning, mNError, mInstantiationInfo, mGetTypeInfo, mNimvm, mIntDefine, mStrDefine, mRunnableExamples, - mException, mBuiltinType, mSymOwner + mException, mBuiltinType, mSymOwner, mUncheckedArray, mGetImplTransf, + mSymIsInstantiationOf # things that we can evaluate safely at compile time, even if not asked for it: const ctfeWhitelist* = {mNone, mUnaryLt, mSucc, mPred, mInc, mDec, mOrd, mLengthOpenArray, mLengthStr, mLengthArray, mLengthSeq, mXLenStr, mXLenSeq, - mArrGet, mArrPut, mAsgn, + mArrGet, mArrPut, mAsgn, mDestroy, mIncl, mExcl, mCard, mChr, mAddI, mSubI, mMulI, mDivI, mModI, mAddF64, mSubF64, mMulF64, mDivF64, @@ -696,11 +696,6 @@ const mConStrStr, mAppendStrCh, mAppendStrStr, mAppendSeqElem, mInRange, mInSet, mRepr, mCopyStr, mCopyStrLast} - # magics that require special semantic checking and - # thus cannot be overloaded (also documented in the spec!): - SpecialSemMagics* = { - mDefined, mDefinedInScope, mCompiles, mLow, mHigh, mSizeOf, mIs, mOf, - mShallowCopy, mExpandToAst, mParallel, mSpawn, mAstToStr} type PNode* = ref TNode @@ -762,8 +757,6 @@ type OnUnknown, # location is unknown (stack, heap or static) OnStatic, # in a static section OnStack, # location is on hardware stack - OnStackShadowDup, # location is on the stack but also replicated - # on the shadow stack OnHeap # location is on heap or global # (reference counting needed) TLocFlags* = set[TLocFlag] @@ -773,7 +766,6 @@ type flags*: TLocFlags # location's flags lode*: PNode # Node where the location came from; can be faked r*: Rope # rope value of location (code generators) - dup*: Rope # duplicated location for precise stack scans # ---------------- end of backend information ------------------------------ @@ -813,7 +805,7 @@ type of routineKinds: procInstCache*: seq[PInstantiation] gcUnsafetyReason*: PSym # for better error messages wrt gcsafe - #scope*: PScope # the scope where the proc was defined + transformedBody*: PNode # cached body after transf pass of skModule, skPackage: # modules keep track of the generic symbols they use from other modules. # this is because in incremental compilation, when a module is about to @@ -904,6 +896,8 @@ type loc*: TLoc typeInst*: PType # for generic instantiations the tyGenericInst that led to this # type. + uniqueId*: int # due to a design mistake, we need to keep the real ID here as it + # required by the --incremental:on mode. TPair* = object key*, val*: RootRef @@ -1272,8 +1266,9 @@ proc newType*(kind: TTypeKind, owner: PSym): PType = result.kind = kind result.owner = owner result.size = -1 - result.align = 2 # default alignment + result.align = -1 # default alignment result.id = getID() + result.uniqueId = result.id result.lockLevel = UnspecifiedLockLevel when debugIds: registerId(result) @@ -1347,15 +1342,12 @@ proc copyType*(t: PType, owner: PSym, keepId: bool): PType = proc exactReplica*(t: PType): PType = copyType(t, t.owner, true) -proc copySym*(s: PSym, keepId: bool = false): PSym = +proc copySym*(s: PSym): PSym = result = newSym(s.kind, s.name, s.owner, s.info, s.options) #result.ast = nil # BUGFIX; was: s.ast which made problems result.typ = s.typ - if keepId: - result.id = s.id - else: - result.id = getID() - when debugIds: registerId(result) + result.id = getID() + when debugIds: registerId(result) result.flags = s.flags result.magic = s.magic if s.kind == skModule: @@ -1660,6 +1652,11 @@ proc isCompileTimeProc*(s: PSym): bool {.inline.} = result = s.kind == skMacro or s.kind == skProc and sfCompileTime in s.flags +proc isRunnableExamples*(n: PNode): bool = + # Templates and generics don't perform symbol lookups. + result = n.kind == nkSym and n.sym.magic == mRunnableExamples or + n.kind == nkIdent and n.ident.s == "runnableExamples" + proc requiredParams*(s: PSym): int = # Returns the number of required params (without default values) # XXX: Perhaps we can store this in the `offset` field of the @@ -1711,8 +1708,7 @@ proc toVar*(typ: PType): PType = proc toRef*(typ: PType): PType = ## If ``typ`` is a tyObject then it is converted into a `ref <typ>` and ## returned. Otherwise ``typ`` is simply returned as-is. - result = typ - if typ.kind == tyObject: + if typ.skipTypes({tyAlias, tyGenericInst}).kind == tyObject: result = newType(tyRef, typ.owner) rawAddSon(result, typ) @@ -1720,32 +1716,33 @@ proc toObject*(typ: PType): PType = ## If ``typ`` is a tyRef then its immediate son is returned (which in many ## cases should be a ``tyObject``). ## Otherwise ``typ`` is simply returned as-is. - result = typ - if result.kind == tyRef: - result = result.lastSon + let t = typ.skipTypes({tyAlias, tyGenericInst}) + if t.kind == tyRef: t.lastSon + else: typ proc isException*(t: PType): bool = # check if `y` is object type and it inherits from Exception assert(t != nil) - if t.kind != tyObject: + if t.kind notin {tyObject, tyGenericInst}: return false var base = t - while base != nil: + while base != nil and base.kind in {tyRef, tyObject, tyGenericInst}: if base.sym != nil and base.sym.magic == mException: return true base = base.lastSon return false proc isImportedException*(t: PType; conf: ConfigRef): bool = - assert(t != nil) + assert t != nil + if optNoCppExceptions in conf.globalOptions: return false let base = t.skipTypes({tyAlias, tyPtr, tyDistinct, tyGenericInst}) - if base.sym != nil and sfCompileToCpp in base.sym.flags: + if base.sym != nil and {sfCompileToCpp, sfImportc} * base.sym.flags != {}: result = true proc isInfixAs*(n: PNode): bool = @@ -1776,3 +1773,6 @@ template typeCompleted*(s: PSym) = incl s.flags, sfNoForward template getBody*(s: PSym): PNode = s.ast[bodyPos] + +template detailedInfo*(sym: PSym): string = + sym.name.s diff --git a/compiler/astalgo.nim b/compiler/astalgo.nim index b716882dc..b2671d81e 100644 --- a/compiler/astalgo.nim +++ b/compiler/astalgo.nim @@ -254,21 +254,26 @@ proc symToYamlAux(conf: ConfigRef; n: PSym, marker: var IntSet, indent: int, if n == nil: result = rope("null") elif containsOrIncl(marker, n.id): - result = "\"$1 @$2\"" % [rope(n.name.s), rope( - strutils.toHex(cast[ByteAddress](n), sizeof(n) * 2))] + result = "\"$1\"" % [rope(n.name.s)] else: var ast = treeToYamlAux(conf, n.ast, marker, indent + 2, maxRecDepth - 1) result = ropeConstr(indent, [rope("kind"), makeYamlString($n.kind), rope("name"), makeYamlString(n.name.s), - rope("typ"), typeToYamlAux(conf, n.typ, marker, - indent + 2, maxRecDepth - 1), + #rope("typ"), typeToYamlAux(conf, n.typ, marker, + # indent + 2, maxRecDepth - 1), rope("info"), lineInfoToStr(conf, n.info), rope("flags"), flagsToStr(n.flags), rope("magic"), makeYamlString($n.magic), rope("ast"), ast, rope("options"), flagsToStr(n.options), rope("position"), - rope(n.position)]) + rope(n.position), + rope("k"), makeYamlString($n.loc.k), + rope("storage"), makeYamlString($n.loc.storage), + rope("flags"), makeYamlString($n.loc.flags), + rope("r"), n.loc.r, + rope("lode"), treeToYamlAux(conf, n.loc.lode, marker, indent + 2, maxRecDepth - 1) + ]) proc typeToYamlAux(conf: ConfigRef; n: PType, marker: var IntSet, indent: int, maxRecDepth: int): Rope = @@ -394,10 +399,16 @@ proc debugTree(conf: ConfigRef; n: PNode, indent: int, maxRecDepth: int; of nkStrLit..nkTripleStrLit: addf(result, ",$N$1\"strVal\": $2", [istr, makeYamlString(n.strVal)]) of nkSym: - addf(result, ",$N$1\"sym\": $2_$3", - [istr, rope(n.sym.name.s), rope(n.sym.id)]) - # [istr, symToYaml(n.sym, indent, maxRecDepth), - # rope(n.sym.id)]) + let s = n.sym + addf(result, ",$N$1\"sym\": $2_$3 k: $4 storage: $5 flags: $6 r: $7", + [istr, rope(s.name.s), rope(s.id), + rope($s.loc.k), + rope($s.loc.storage), + rope($s.loc.flags), + s.loc.r + ]) +# [istr, symToYaml(conf, n.sym, indent, maxRecDepth), +# rope(n.sym.id)]) if renderType and n.sym.typ != nil: addf(result, ",$N$1\"typ\": $2", [istr, debugType(conf, n.sym.typ, 2)]) of nkIdent: diff --git a/compiler/canonicalizer.nim b/compiler/canonicalizer.nim index d1669a06c..2b6096298 100644 --- a/compiler/canonicalizer.nim +++ b/compiler/canonicalizer.nim @@ -300,7 +300,7 @@ proc encodeType(w: PRodWriter, t: PType, result: var string) = if t.size != - 1: add(result, '/') encodeVBiggestInt(t.size, result) - if t.align != 2: + if t.align != - 1: add(result, '=') encodeVInt(t.align, result) encodeLoc(w, t.loc, result) diff --git a/compiler/ccgcalls.nim b/compiler/ccgcalls.nim index 33b07a5a7..b23cd598e 100644 --- a/compiler/ccgcalls.nim +++ b/compiler/ccgcalls.nim @@ -106,7 +106,7 @@ proc openArrayLoc(p: BProc, n: PNode): Rope = result = "($1)+($2), ($3)-($2)+1" % [rdLoc(a), rdLoc(b), rdLoc(c)] else: result = "($1)+(($2)-($4)), ($3)-($2)+1" % [rdLoc(a), rdLoc(b), rdLoc(c), intLiteral(first)] - of tyOpenArray, tyVarargs: + of tyOpenArray, tyVarargs, tyUncheckedArray: result = "($1)+($2), ($3)-($2)+1" % [rdLoc(a), rdLoc(b), rdLoc(c)] of tyString, tySequence: if skipTypes(n.typ, abstractInst).kind == tyVar and diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim index a1365ce07..348a9f375 100644 --- a/compiler/ccgexprs.nim +++ b/compiler/ccgexprs.nim @@ -59,7 +59,7 @@ proc genLiteral(p: BProc, n: PNode, ty: PType): Rope = else: result = rope("NIM_NIL") of nkStrLit..nkTripleStrLit: - case skipTypes(ty, abstractVarRange + {tyStatic}).kind + case skipTypes(ty, abstractVarRange + {tyStatic, tyUserTypeClass, tyUserTypeClassInst}).kind of tyNil: result = genNilStringLiteral(p.module, n.info) of tyString: @@ -168,32 +168,12 @@ proc canMove(p: BProc, n: PNode): bool = # echo n.info, " optimized ", n # result = false -proc genRefAssign(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) = - if dest.storage == OnStack or not usesWriteBarrier(p.config): +proc genRefAssign(p: BProc, dest, src: TLoc) = + if (dest.storage == OnStack and p.config.selectedGC != gcGo) or not usesWriteBarrier(p.config): linefmt(p, cpsStmts, "$1 = $2;$n", rdLoc(dest), rdLoc(src)) elif dest.storage == OnHeap: - # location is on heap - # now the writer barrier is inlined for performance: - # - # if afSrcIsNotNil in flags: - # UseMagic(p.module, 'nimGCref') - # lineF(p, cpsStmts, 'nimGCref($1);$n', [rdLoc(src)]) - # elif afSrcIsNil notin flags: - # UseMagic(p.module, 'nimGCref') - # lineF(p, cpsStmts, 'if ($1) nimGCref($1);$n', [rdLoc(src)]) - # if afDestIsNotNil in flags: - # UseMagic(p.module, 'nimGCunref') - # lineF(p, cpsStmts, 'nimGCunref($1);$n', [rdLoc(dest)]) - # elif afDestIsNil notin flags: - # UseMagic(p.module, 'nimGCunref') - # lineF(p, cpsStmts, 'if ($1) nimGCunref($1);$n', [rdLoc(dest)]) - # lineF(p, cpsStmts, '$1 = $2;$n', [rdLoc(dest), rdLoc(src)]) - if canFormAcycle(dest.t): - linefmt(p, cpsStmts, "#asgnRef((void**) $1, $2);$n", - addrLoc(p.config, dest), rdLoc(src)) - else: - linefmt(p, cpsStmts, "#asgnRefNoCycle((void**) $1, $2);$n", - addrLoc(p.config, dest), rdLoc(src)) + linefmt(p, cpsStmts, "#asgnRef((void**) $1, $2);$n", + addrLoc(p.config, dest), rdLoc(src)) else: linefmt(p, cpsStmts, "#unsureAsgnRef((void**) $1, $2);$n", addrLoc(p.config, dest), rdLoc(src)) @@ -261,11 +241,11 @@ proc genGenericAsgn(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) = # (for objects, etc.): if p.config.selectedGC == gcDestructors: linefmt(p, cpsStmts, - "$1.len = $2.len; $1.p = $2.p;$n", + "$1 = $2;$n", rdLoc(dest), rdLoc(src)) elif needToCopy notin flags or tfShallow in skipTypes(dest.t, abstractVarRange).flags: - if dest.storage == OnStack or not usesWriteBarrier(p.config): + if (dest.storage == OnStack and p.config.selectedGC != gcGo) or not usesWriteBarrier(p.config): linefmt(p, cpsStmts, "#nimCopyMem((void*)$1, (NIM_CONST void*)$2, sizeof($3));$n", addrLoc(p.config, dest), addrLoc(p.config, src), rdLoc(dest)) @@ -286,12 +266,12 @@ proc genAssignment(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) = let ty = skipTypes(dest.t, abstractRange + tyUserTypeClasses + {tyStatic}) case ty.kind of tyRef: - genRefAssign(p, dest, src, flags) + genRefAssign(p, dest, src) of tySequence: if p.config.selectedGC == gcDestructors: genGenericAsgn(p, dest, src, flags) elif (needToCopy notin flags and src.storage != OnStatic) or canMove(p, src.lode): - genRefAssign(p, dest, src, flags) + genRefAssign(p, dest, src) else: linefmt(p, cpsStmts, "#genericSeqAssign($1, $2, $3);$n", addrLoc(p.config, dest), rdLoc(src), @@ -300,9 +280,9 @@ proc genAssignment(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) = if p.config.selectedGC == gcDestructors: genGenericAsgn(p, dest, src, flags) elif (needToCopy notin flags and src.storage != OnStatic) or canMove(p, src.lode): - genRefAssign(p, dest, src, flags) + genRefAssign(p, dest, src) else: - if dest.storage == OnStack or not usesWriteBarrier(p.config): + if (dest.storage == OnStack and p.config.selectedGC != gcGo) or not usesWriteBarrier(p.config): linefmt(p, cpsStmts, "$1 = #copyString($2);$n", dest.rdLoc, src.rdLoc) elif dest.storage == OnHeap: # we use a temporary to care for the dreaded self assignment: @@ -315,16 +295,16 @@ proc genAssignment(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) = linefmt(p, cpsStmts, "#unsureAsgnRef((void**) $1, #copyString($2));$n", addrLoc(p.config, dest), rdLoc(src)) of tyProc: - if needsComplexAssignment(dest.t): + if containsGarbageCollectedRef(dest.t): # optimize closure assignment: let a = optAsgnLoc(dest, dest.t, "ClE_0".rope) let b = optAsgnLoc(src, dest.t, "ClE_0".rope) - genRefAssign(p, a, b, flags) + genRefAssign(p, a, b) linefmt(p, cpsStmts, "$1.ClP_0 = $2.ClP_0;$n", rdLoc(dest), rdLoc(src)) else: linefmt(p, cpsStmts, "$1 = $2;$n", rdLoc(dest), rdLoc(src)) of tyTuple: - if needsComplexAssignment(dest.t): + if containsGarbageCollectedRef(dest.t): if dest.t.len <= 4: genOptAsgnTuple(p, dest, src, flags) else: genGenericAsgn(p, dest, src, flags) else: @@ -335,7 +315,7 @@ proc genAssignment(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) = linefmt(p, cpsStmts, "$1 = $2;$n", rdLoc(dest), rdLoc(src)) elif not isObjLackingTypeField(ty): genGenericAsgn(p, dest, src, flags) - elif needsComplexAssignment(ty): + elif containsGarbageCollectedRef(ty): if ty.sons[0].isNil and asgnComplexity(ty.n) <= 4: discard getTypeDesc(p.module, ty) internalAssert p.config, ty.n != nil @@ -345,7 +325,7 @@ proc genAssignment(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) = else: linefmt(p, cpsStmts, "$1 = $2;$n", rdLoc(dest), rdLoc(src)) of tyArray: - if needsComplexAssignment(dest.t): + if containsGarbageCollectedRef(dest.t): genGenericAsgn(p, dest, src, flags) else: linefmt(p, cpsStmts, @@ -354,7 +334,7 @@ proc genAssignment(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) = of tyOpenArray, tyVarargs: # open arrays are always on the stack - really? What if a sequence is # passed to an open array? - if needsComplexAssignment(dest.t): + if containsGarbageCollectedRef(dest.t): linefmt(p, cpsStmts, # XXX: is this correct for arrays? "#genericAssignOpenArray((void*)$1, (void*)$2, $1Len_0, $3);$n", addrLoc(p.config, dest), addrLoc(p.config, src), @@ -741,7 +721,7 @@ proc genAddr(p: BProc, e: PNode, d: var TLoc) = initLocExpr(p, e.sons[0], a) putIntoDest(p, d, e, "&" & a.r, a.storage) #Message(e.info, warnUser, "HERE NEW &") - elif mapType(p.config, e.sons[0].typ) == ctArray or isCppRef(p, e.sons[0].typ): + elif mapType(p.config, e.sons[0].typ) == ctArray or isCppRef(p, e.typ): expr(p, e.sons[0], d) else: var a: TLoc @@ -856,6 +836,15 @@ proc genCheckedRecordField(p: BProc, e: PNode, d: var TLoc) = else: genRecordField(p, e.sons[0], d) +proc genUncheckedArrayElem(p: BProc, n, x, y: PNode, d: var TLoc) = + var a, b: TLoc + initLocExpr(p, x, a) + initLocExpr(p, y, b) + var ty = skipTypes(a.t, abstractVarRange + abstractPtrs + tyUserTypeClasses) + d.inheritLocation(a) + putIntoDest(p, d, n, ropecg(p.module, "$1[$2]", rdLoc(a), rdCharLoc(b)), + a.storage) + proc genArrayElem(p: BProc, n, x, y: PNode, d: var TLoc) = var a, b: TLoc initLocExpr(p, x, a) @@ -863,7 +852,7 @@ proc genArrayElem(p: BProc, n, x, y: PNode, d: var TLoc) = var ty = skipTypes(a.t, abstractVarRange + abstractPtrs + tyUserTypeClasses) var first = intLiteral(firstOrd(p.config, ty)) # emit range check: - if optBoundsCheck in p.options and tfUncheckedArray notin ty.flags: + if optBoundsCheck in p.options and ty.kind != tyUncheckedArray: if not isConstExpr(y): # semantic pass has already checked for const index expressions if firstOrd(p.config, ty) == 0: @@ -900,11 +889,10 @@ proc genBoundsCheck(p: BProc; arr, a, b: TLoc) = rdLoc(a), rdLoc(b), rdLoc(arr)) of tyArray: let first = intLiteral(firstOrd(p.config, ty)) - if tfUncheckedArray notin ty.flags: - linefmt(p, cpsStmts, - "if ($2-$1 != -1 && " & - "($2-$1 < -1 || $1 < $3 || $1 > $4 || $2 < $3 || $2 > $4)) #raiseIndexError();$n", - rdCharLoc(a), rdCharLoc(b), first, intLiteral(lastOrd(p.config, ty))) + linefmt(p, cpsStmts, + "if ($2-$1 != -1 && " & + "($2-$1 < -1 || $1 < $3 || $1 > $4 || $2 < $3 || $2 > $4)) #raiseIndexError();$n", + rdCharLoc(a), rdCharLoc(b), first, intLiteral(lastOrd(p.config, ty))) of tySequence, tyString: linefmt(p, cpsStmts, "if ($2-$1 != -1 && " & @@ -949,6 +937,7 @@ proc genBracketExpr(p: BProc; n: PNode; d: var TLoc) = var ty = skipTypes(n.sons[0].typ, abstractVarRange + tyUserTypeClasses) if ty.kind in {tyRef, tyPtr}: ty = skipTypes(ty.lastSon, abstractVarRange) case ty.kind + of tyUncheckedArray: genUncheckedArrayElem(p, n, n.sons[0], n.sons[1], d) of tyArray: genArrayElem(p, n, n.sons[0], n.sons[1], d) of tyOpenArray, tyVarargs: genOpenArrayElem(p, n, n.sons[0], n.sons[1], d) of tySequence, tyString: genSeqElem(p, n, n.sons[0], n.sons[1], d) @@ -1139,14 +1128,14 @@ proc genSeqElemAppend(p: BProc, e: PNode, d: var TLoc) = genTypeInfo(p.module, seqType, e.info)]) # emit the write barrier if required, but we can always move here, so # use 'genRefAssign' for the seq. - genRefAssign(p, a, call, {}) + genRefAssign(p, a, call) #if bt != b.t: # echo "YES ", e.info, " new: ", typeToString(bt), " old: ", typeToString(b.t) initLoc(dest, locExpr, e.sons[2], OnHeap) getIntTemp(p, tmpL) lineCg(p, cpsStmts, "$1 = $2->$3++;$n", tmpL.r, rdLoc(a), lenField(p)) dest.r = ropecg(p.module, "$1$3[$2]", rdLoc(a), tmpL.r, dataField(p)) - genAssignment(p, dest, b, {needToCopy, afDestIsNil}) + genAssignment(p, dest, b, {needToCopy}) gcUsage(p.config, e) proc genReset(p: BProc, n: PNode) = @@ -1183,13 +1172,19 @@ proc rawGenNew(p: BProc, a: TLoc, sizeExpr: Rope) = let args = [getTypeDesc(p.module, typ), ti, sizeExpr] if a.storage == OnHeap and usesWriteBarrier(p.config): - # use newObjRC1 as an optimization if canFormAcycle(a.t): linefmt(p, cpsStmts, "if ($1) { #nimGCunrefRC1($1); $1 = NIM_NIL; }$n", a.rdLoc) else: linefmt(p, cpsStmts, "if ($1) { #nimGCunrefNoCycle($1); $1 = NIM_NIL; }$n", a.rdLoc) - b.r = ropecg(p.module, "($1) #newObjRC1($2, $3)", args) - linefmt(p, cpsStmts, "$1 = $2;$n", a.rdLoc, b.rdLoc) + if p.config.selectedGC == gcGo: + # newObjRC1() would clash with unsureAsgnRef() - which is used by gcGo to + # implement the write barrier + b.r = ropecg(p.module, "($1) #newObj($2, $3)", args) + linefmt(p, cpsStmts, "#unsureAsgnRef((void**) $1, $2);$n", addrLoc(p.config, a), b.rdLoc) + else: + # use newObjRC1 as an optimization + b.r = ropecg(p.module, "($1) #newObjRC1($2, $3)", args) + linefmt(p, cpsStmts, "$1 = $2;$n", a.rdLoc, b.rdLoc) else: b.r = ropecg(p.module, "($1) #newObj($2, $3)", args) genAssignment(p, a, b, {}) # set the object type: @@ -1219,8 +1214,13 @@ proc genNewSeqAux(p: BProc, dest: TLoc, length: Rope; lenIsZero: bool) = else: linefmt(p, cpsStmts, "if ($1) { #nimGCunrefNoCycle($1); $1 = NIM_NIL; }$n", dest.rdLoc) if not lenIsZero: - call.r = ropecg(p.module, "($1) #newSeqRC1($2, $3)", args) - linefmt(p, cpsStmts, "$1 = $2;$n", dest.rdLoc, call.rdLoc) + if p.config.selectedGC == gcGo: + # we need the write barrier + call.r = ropecg(p.module, "($1) #newSeq($2, $3)", args) + linefmt(p, cpsStmts, "#unsureAsgnRef((void**) $1, $2);$n", addrLoc(p.config, dest), call.rdLoc) + else: + call.r = ropecg(p.module, "($1) #newSeqRC1($2, $3)", args) + linefmt(p, cpsStmts, "$1 = $2;$n", dest.rdLoc, call.rdLoc) else: if lenIsZero: call.r = rope"NIM_NIL" @@ -1339,9 +1339,17 @@ proc genSeqConstr(p: BProc, n: PNode, d: var TLoc) = getTemp(p, n.typ, tmp) elif d.k == locNone: getTemp(p, n.typ, d) - # generate call to newSeq before adding the elements per hand: - genNewSeqAux(p, dest[], intLiteral(sonsLen(n)), - optNilSeqs notin p.options and n.len == 0) + + let l = intLiteral(sonsLen(n)) + if p.config.selectedGC == gcDestructors: + let seqtype = n.typ + linefmt(p, cpsStmts, "$1.len = $2; $1.p = ($4*) #newSeqPayload($2, sizeof($3));$n", + rdLoc dest[], l, getTypeDesc(p.module, seqtype.lastSon), + getSeqPayloadType(p.module, seqtype)) + else: + # generate call to newSeq before adding the elements per hand: + genNewSeqAux(p, dest[], l, + optNilSeqs notin p.options and n.len == 0) for i in countup(0, sonsLen(n) - 1): initLoc(arr, locExpr, n[i], OnHeap) arr.r = ropecg(p.module, "$1$3[$2]", rdLoc(dest[]), intLiteral(i), dataField(p)) @@ -1364,7 +1372,13 @@ proc genArrToSeq(p: BProc, n: PNode, d: var TLoc) = getTemp(p, n.typ, d) # generate call to newSeq before adding the elements per hand: let L = int(lengthOrd(p.config, n.sons[1].typ)) - genNewSeqAux(p, d, intLiteral(L), optNilSeqs notin p.options and L == 0) + if p.config.selectedGC == gcDestructors: + let seqtype = n.typ + linefmt(p, cpsStmts, "$1.len = $2; $1.p = ($4*) #newSeqPayload($2, sizeof($3));$n", + rdLoc d, rope L, getTypeDesc(p.module, seqtype.lastSon), + getSeqPayloadType(p.module, seqtype)) + else: + genNewSeqAux(p, d, intLiteral(L), optNilSeqs notin p.options and L == 0) initLocExpr(p, n.sons[1], a) # bug #5007; do not produce excessive C source code: if L < 10: @@ -1374,7 +1388,7 @@ proc genArrToSeq(p: BProc, n: PNode, d: var TLoc) = elem.storage = OnHeap # we know that sequences are on the heap initLoc(arr, locExpr, lodeTyp elemType(skipTypes(n.sons[1].typ, abstractInst)), a.storage) arr.r = ropecg(p.module, "$1[$2]", rdLoc(a), intLiteral(i)) - genAssignment(p, elem, arr, {afDestIsNil, needToCopy}) + genAssignment(p, elem, arr, {needToCopy}) else: var i: TLoc getTemp(p, getSysType(p.module.g.graph, unknownLineInfo(), tyInt), i) @@ -1385,7 +1399,7 @@ proc genArrToSeq(p: BProc, n: PNode, d: var TLoc) = elem.storage = OnHeap # we know that sequences are on the heap initLoc(arr, locExpr, lodeTyp elemType(skipTypes(n.sons[1].typ, abstractInst)), a.storage) arr.r = ropecg(p.module, "$1[$2]", rdLoc(a), rdLoc(i)) - genAssignment(p, elem, arr, {afDestIsNil, needToCopy}) + genAssignment(p, elem, arr, {needToCopy}) lineF(p, cpsStmts, "}$n", []) @@ -1523,8 +1537,19 @@ proc genArrayLen(p: BProc, e: PNode, d: var TLoc, op: TMagic) = var typ = skipTypes(a.typ, abstractVar + tyUserTypeClasses) case typ.kind of tyOpenArray, tyVarargs: - if op == mHigh: unaryExpr(p, e, d, "($1Len_0-1)") - else: unaryExpr(p, e, d, "$1Len_0") + # Bug #9279, len(toOpenArray()) has to work: + if a.kind in nkCallKinds and a[0].kind == nkSym and a[0].sym.magic == mSlice: + # magic: pass slice to openArray: + var b, c: TLoc + initLocExpr(p, a[2], b) + initLocExpr(p, a[3], c) + if op == mHigh: + putIntoDest(p, d, e, ropecg(p.module, "($2)-($1)", [rdLoc(b), rdLoc(c)])) + else: + putIntoDest(p, d, e, ropecg(p.module, "($2)-($1)+1", [rdLoc(b), rdLoc(c)])) + else: + if op == mHigh: unaryExpr(p, e, d, "($1Len_0-1)") + else: unaryExpr(p, e, d, "$1Len_0") of tyCString: if op == mHigh: unaryExpr(p, e, d, "($1 ? (#nimCStrLen($1)-1) : -1)") else: unaryExpr(p, e, d, "($1 ? #nimCStrLen($1) : 0)") @@ -1549,8 +1574,17 @@ proc genArrayLen(p: BProc, e: PNode, d: var TLoc, op: TMagic) = else: putIntoDest(p, d, e, rope(lengthOrd(p.config, typ))) else: internalError(p.config, e.info, "genArrayLen()") +proc makePtrType(baseType: PType): PType = + result = newType(tyPtr, baseType.owner) + addSonSkipIntLit(result, baseType) + +proc makeAddr(n: PNode): PNode = + result = newTree(nkHiddenAddr, n) + result.typ = makePtrType(n.typ) + proc genSetLengthSeq(p: BProc, e: PNode, d: var TLoc) = if p.config.selectedGc == gcDestructors: + e.sons[1] = makeAddr(e[1]) genCall(p, e, d) return var a, b, call: TLoc @@ -1732,7 +1766,7 @@ proc genSetOp(p: BProc, e: PNode, d: var TLoc, op: TMagic) = getTemp(p, getSysType(p.module.g.graph, unknownLineInfo(), tyInt), i) # our counter initLocExpr(p, e.sons[1], a) initLocExpr(p, e.sons[2], b) - if d.k == locNone: getTemp(p, a.t, d) + if d.k == locNone: getTemp(p, setType, d) lineF(p, cpsStmts, "for ($1 = 0; $1 < $2; $1++) $n" & " $3[$1] = $4[$1] $6 $5[$1];$n", [ @@ -1932,6 +1966,7 @@ proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) = of mAppendStrStr: genStrAppend(p, e, d) of mAppendSeqElem: if p.config.selectedGc == gcDestructors: + e.sons[1] = makeAddr(e[1]) genCall(p, e, d) else: genSeqElemAppend(p, e, d) @@ -1976,8 +2011,8 @@ proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) = frmt = "$1 = $2->len;$n" lineCg(p, cpsStmts, frmt, tmp.r, rdLoc(a)) putIntoDest(p, d, e, tmp.r) - of mGCref: unaryStmt(p, e, d, "#nimGCref($1);$n") - of mGCunref: unaryStmt(p, e, d, "#nimGCunref($1);$n") + of mGCref: unaryStmt(p, e, d, "if ($1) { #nimGCref($1); }$n") + of mGCunref: unaryStmt(p, e, d, "if ($1) { #nimGCunref($1); }$n") of mSetLengthStr: genSetLengthStr(p, e, d) of mSetLengthSeq: genSetLengthSeq(p, e, d) of mIncl, mExcl, mCard, mLtSet, mLeSet, mEqSet, mMulSet, mPlusSet, mMinusSet, @@ -1999,8 +2034,11 @@ proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) = let n = lowerings.wrapProcForSpawn(p.module.g.graph, p.module.module, e, e.typ, nil, nil) expr(p, n, d) of mParallel: - let n = semparallel.liftParallel(p.module.g.graph, p.module.module, e) - expr(p, n, d) + when defined(leanCompiler): + quit "compiler built without support for the 'parallel' statement" + else: + let n = semparallel.liftParallel(p.module.g.graph, p.module.module, e) + expr(p, n, d) of mDeepCopy: var a, b: TLoc let x = if e[1].kind in {nkAddr, nkHiddenAddr}: e[1][0] else: e[1] @@ -2010,6 +2048,10 @@ proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) = of mDotDot, mEqCString: genCall(p, e, d) of mWasMoved: genWasMoved(p, e) of mMove: genMove(p, e, d) + of mDestroy: discard "ignore calls to the default destructor" + of mSlice: + localError(p.config, e.info, "invalid context for 'toOpenArray'; " & + " 'toOpenArray' is only valid within a call expression") else: when defined(debugMagics): echo p.prc.name.s, " ", p.prc.id, " ", p.prc.flags, " ", p.prc.ast[genericParamsPos].kind @@ -2079,7 +2121,7 @@ proc isConstClosure(n: PNode): bool {.inline.} = n.sons[1].kind == nkNilLit proc genClosure(p: BProc, n: PNode, d: var TLoc) = - assert n.kind == nkClosure + assert n.kind in {nkPar, nkTupleConstr, nkClosure} if isConstClosure(n): inc(p.module.labels) @@ -2337,7 +2379,9 @@ proc expr(p: BProc, n: PNode, d: var TLoc) = else: genArrayConstr(p, n, d) of nkPar, nkTupleConstr: - if isDeepConstExpr(n) and n.len != 0: + if n.typ != nil and n.typ.kind == tyProc and n.len == 2: + genClosure(p, n, d) + elif isDeepConstExpr(n) and n.len != 0: exprComplexConst(p, n, d) else: genTupleConstr(p, n, d) @@ -2392,15 +2436,8 @@ proc expr(p: BProc, n: PNode, d: var TLoc) = if ex.kind != nkEmpty: genLineDir(p, n) var a: TLoc - if ex.kind in nkCallKinds and (ex[0].kind != nkSym or - ex[0].sym.magic == mNone): - # bug #6037: do not assign to a temp in C++ mode: - incl a.flags, lfSingleUse - genCall(p, ex, a) - if lfSingleUse notin a.flags: - line(p, cpsStmts, a.r & ";\L") - else: - initLocExpr(p, ex, a) + initLocExprSingleUse(p, ex, a) + line(p, cpsStmts, "(void)(" & a.r & ");\L") of nkAsmStmt: genAsmStmt(p, n) of nkTryStmt: if p.module.compileToCpp and optNoCppExceptions notin p.config.globalOptions: @@ -2571,15 +2608,21 @@ proc genConstExpr(p: BProc, n: PNode): Rope = var t = skipTypes(n.typ, abstractInst) if t.kind == tySequence: result = genConstSeq(p, n, n.typ) - elif t.kind == tyProc and t.callConv == ccClosure and n.len > 0 and - n.sons[0].kind == nkNilLit and n.sons[1].kind == nkNilLit: + elif t.kind == tyProc and t.callConv == ccClosure and n.len > 1 and + n.sons[1].kind == nkNilLit: + # Conversion: nimcall -> closure. # this hack fixes issue that nkNilLit is expanded to {NIM_NIL,NIM_NIL} # this behaviour is needed since closure_var = nil must be # expanded to {NIM_NIL,NIM_NIL} # in VM closures are initialized with nkPar(nkNilLit, nkNilLit) # leading to duplicate code like this: # "{NIM_NIL,NIM_NIL}, {NIM_NIL,NIM_NIL}" - result = ~"{NIM_NIL,NIM_NIL}" + if n[0].kind == nkNilLit: + result = ~"{NIM_NIL,NIM_NIL}" + else: + var d: TLoc + initLocExpr(p, n[0], d) + result = "{(($1) $2),NIM_NIL}" % [getClosureType(p.module, t, clHalfWithEnv), rdLoc(d)] else: result = genConstSimpleList(p, n) of nkObjConstr: diff --git a/compiler/ccgliterals.nim b/compiler/ccgliterals.nim index 34677ec06..904d01e81 100644 --- a/compiler/ccgliterals.nim +++ b/compiler/ccgliterals.nim @@ -54,7 +54,7 @@ proc genStringLiteralV1(m: BModule; n: PNode): Rope = proc genStringLiteralDataOnlyV2(m: BModule, s: string): Rope = result = getTempName(m) addf(m.s[cfsData], "static const struct {$n" & - " NI cap; void* allocator; NIM_CHAR data[$2];$n" & + " NI cap; void* allocator; NIM_CHAR data[$2+1];$n" & "} $1 = { $2, NIM_NIL, $3 };$n", [result, rope(len(s)), makeCString(s)]) @@ -69,7 +69,7 @@ proc genStringLiteralV2(m: BModule; n: PNode): Rope = addf(m.s[cfsData], "static const NimStringV2 $1 = {$2, (NimStrPayload*)&$3};$n", [result, rope(len(n.strVal)), pureLit]) else: - result = m.tmpBase & rope(id) + result = m.tmpBase & rope(id+1) proc genStringLiteralV2Const(m: BModule; n: PNode): Rope = let id = nodeTableTestOrSet(m.dataCache, n, m.labels) diff --git a/compiler/ccgstmts.nim b/compiler/ccgstmts.nim index b7846f605..e83b80b7c 100644 --- a/compiler/ccgstmts.nim +++ b/compiler/ccgstmts.nim @@ -71,7 +71,7 @@ proc genVarTuple(p: BProc, n: PNode) = field.r = "$1.Field$2" % [rdLoc(tup), rope(i)] else: if t.n.sons[i].kind != nkSym: internalError(p.config, n.info, "genVarTuple") - field.r = "$1.$2" % [rdLoc(tup), mangleRecFieldName(p.module, t.n.sons[i].sym, t)] + field.r = "$1.$2" % [rdLoc(tup), mangleRecFieldName(p.module, t.n.sons[i].sym)] putLocIntoDest(p, v.loc, field) proc genDeref(p: BProc, e: PNode, d: var TLoc; enforceDeref=false) @@ -277,7 +277,7 @@ proc genSingleVar(p: BProc, a: PNode) = not containsHiddenPointer(v.typ): # C++ really doesn't like things like 'Foo f; f = x' as that invokes a # parameterless constructor followed by an assignment operator. So we - # generate better code here: + # generate better code here: 'Foo f = x;' genLineDir(p, a) let decl = localVarDecl(p, vn) var tmp: TLoc @@ -307,11 +307,13 @@ proc genSingleVar(p: BProc, a: PNode) = proc genClosureVar(p: BProc, a: PNode) = var immediateAsgn = a.sons[2].kind != nkEmpty + var v: TLoc + initLocExpr(p, a.sons[0], v) + genLineDir(p, a) if immediateAsgn: - var v: TLoc - initLocExpr(p, a.sons[0], v) - genLineDir(p, a) loadInto(p, a.sons[0], a.sons[2], v) + else: + constructLoc(p, v) proc genVarStmt(p: BProc, n: PNode) = for it in n.sons: @@ -400,6 +402,16 @@ proc genGotoForCase(p: BProc; caseStmt: PNode) = genStmts(p, it.lastSon) endBlock(p) + +iterator fieldValuePairs(n: PNode): tuple[memberSym, valueSym: PNode] = + assert(n.kind in {nkLetSection, nkVarSection}) + for identDefs in n: + if identDefs.kind == nkIdentDefs: + let valueSym = identDefs[^1] + for i in 0 ..< identDefs.len-2: + let memberSym = identDefs[i] + yield((memberSym: memberSym, valueSym: valueSym)) + proc genComputedGoto(p: BProc; n: PNode) = # first pass: Generate array of computed labels: var casePos = -1 @@ -429,22 +441,12 @@ proc genComputedGoto(p: BProc; n: PNode) = let tmp = "TMP$1_" % [id.rope] var gotoArray = "static void* $#[$#] = {" % [tmp, arraySize.rope] for i in 1..arraySize-1: - gotoArray.addf("&&TMP$#_, ", [(id+i).rope]) - gotoArray.addf("&&TMP$#_};$n", [(id+arraySize).rope]) + gotoArray.addf("&&TMP$#_, ", [rope(id+i)]) + gotoArray.addf("&&TMP$#_};$n", [rope(id+arraySize)]) line(p, cpsLocals, gotoArray) - let topBlock = p.blocks.len-1 - let oldBody = p.blocks[topBlock].sections[cpsStmts] - p.blocks[topBlock].sections[cpsStmts] = nil - - for j in casePos+1 ..< n.len: genStmts(p, n.sons[j]) - let tailB = p.blocks[topBlock].sections[cpsStmts] - - p.blocks[topBlock].sections[cpsStmts] = nil - for j in 0 .. casePos-1: genStmts(p, n.sons[j]) - let tailA = p.blocks[topBlock].sections[cpsStmts] - - p.blocks[topBlock].sections[cpsStmts] = oldBody & tailA + for j in 0 ..< casePos: + genStmts(p, n.sons[j]) let caseStmt = n.sons[casePos] var a: TLoc @@ -459,19 +461,40 @@ proc genComputedGoto(p: BProc; n: PNode) = if it.sons[j].kind == nkRange: localError(p.config, it.info, "range notation not available for computed goto") return + let val = getOrdValue(it.sons[j]) lineF(p, cpsStmts, "TMP$#_:$n", [intLiteral(val+id+1)]) + genStmts(p, it.lastSon) - #for j in casePos+1 ..< n.len: genStmts(p, n.sons[j]) # tailB - #for j in 0 .. casePos-1: genStmts(p, n.sons[j]) # tailA - add(p.s(cpsStmts), tailB) - add(p.s(cpsStmts), tailA) + + for j in casePos+1 ..< n.sons.len: + genStmts(p, n.sons[j]) + + for j in 0 ..< casePos: + # prevent new local declarations + # compile declarations as assignments + let it = n.sons[j] + if it.kind in {nkLetSection, nkVarSection}: + let asgn = copyNode(it) + asgn.kind = nkAsgn + asgn.sons.setLen 2 + for sym, value in it.fieldValuePairs: + if value.kind != nkEmpty: + asgn.sons[0] = sym + asgn.sons[1] = value + genStmts(p, asgn) + else: + genStmts(p, it) var a: TLoc initLocExpr(p, caseStmt.sons[0], a) lineF(p, cpsStmts, "goto *$#[$#];$n", [tmp, a.rdLoc]) endBlock(p) + for j in casePos+1 ..< n.sons.len: + genStmts(p, n.sons[j]) + + proc genWhileStmt(p: BProc, t: PNode) = # we don't generate labels here as for example GCC would produce # significantly worse code @@ -482,26 +505,26 @@ proc genWhileStmt(p: BProc, t: PNode) = genLineDir(p, t) preserveBreakIdx: - p.breakIdx = startBlock(p, "while (1) {$n") - p.blocks[p.breakIdx].isLoop = true - initLocExpr(p, t.sons[0], a) - if (t.sons[0].kind != nkIntLit) or (t.sons[0].intVal == 0): - let label = assignLabel(p.blocks[p.breakIdx]) - lineF(p, cpsStmts, "if (!$1) goto $2;$n", [rdLoc(a), label]) var loopBody = t.sons[1] if loopBody.stmtsContainPragma(wComputedGoto) and - hasComputedGoto in CC[p.config.cCompiler].props: - # for closure support weird loop bodies are generated: + hasComputedGoto in CC[p.config.cCompiler].props: + # for closure support weird loop bodies are generated: if loopBody.len == 2 and loopBody.sons[0].kind == nkEmpty: loopBody = loopBody.sons[1] genComputedGoto(p, loopBody) else: + p.breakIdx = startBlock(p, "while (1) {$n") + p.blocks[p.breakIdx].isLoop = true + initLocExpr(p, t.sons[0], a) + if (t.sons[0].kind != nkIntLit) or (t.sons[0].intVal == 0): + let label = assignLabel(p.blocks[p.breakIdx]) + lineF(p, cpsStmts, "if (!$1) goto $2;$n", [rdLoc(a), label]) genStmts(p, loopBody) - if optProfiler in p.options: - # invoke at loop body exit: - linefmt(p, cpsStmts, "#nimProfile();$n") - endBlock(p) + if optProfiler in p.options: + # invoke at loop body exit: + linefmt(p, cpsStmts, "#nimProfile();$n") + endBlock(p) dec(p.withinLoop) @@ -536,7 +559,7 @@ proc genParForStmt(p: BProc, t: PNode) = initLocExpr(p, call.sons[1], rangeA) initLocExpr(p, call.sons[2], rangeB) - lineF(p, cpsStmts, "#pragma omp parallel for $4$n" & + lineF(p, cpsStmts, "#pragma omp $4$n" & "for ($1 = $2; $1 <= $3; ++$1)", [forLoopVar.loc.rdLoc, rangeA.rdLoc, rangeB.rdLoc, @@ -587,8 +610,10 @@ proc genRaiseStmt(p: BProc, t: PNode) = if isImportedException(typ, p.config): lineF(p, cpsStmts, "throw $1;$n", [e]) else: - lineCg(p, cpsStmts, "#raiseException((#Exception*)$1, $2);$n", - [e, makeCString(typ.sym.name.s)]) + lineCg(p, cpsStmts, "#raiseExceptionEx((#Exception*)$1, $2, $3, $4, $5);$n", + [e, makeCString(typ.sym.name.s), + makeCString(if p.prc != nil: p.prc.name.s else: p.module.module.name.s), + makeCString(toFileName(p.config, t.info)), rope(toLinenumber(t.info))]) else: genLineDir(p, t) # reraise the last exception: @@ -851,14 +876,15 @@ proc genTryCpp(p: BProc, t: PNode, d: var TLoc) = discard pop(p.nestedTryStmts) - if not catchAllPresent and t[^1].kind == nkFinally: - # finally requires catch all presence - startBlock(p, "catch (...) {$n") - genSimpleBlock(p, t[^1][0]) - line(p, cpsStmts, ~"throw;$n") - endBlock(p) - if t[^1].kind == nkFinally: + # c++ does not have finally, therefore code needs to be generated twice + if not catchAllPresent: + # finally requires catch all presence + startBlock(p, "catch (...) {$n") + genStmts(p, t[^1][0]) + line(p, cpsStmts, ~"throw;$n") + endBlock(p) + genSimpleBlock(p, t[^1][0]) proc genTry(p: BProc, t: PNode, d: var TLoc) = diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim index 3c7a0d26e..266f63647 100644 --- a/compiler/ccgtypes.nim +++ b/compiler/ccgtypes.nim @@ -139,7 +139,7 @@ proc mapType(conf: ConfigRef; typ: PType): TCTypeKind = of tyBool: result = ctBool of tyChar: result = ctChar of tySet: result = mapSetType(conf, typ) - of tyOpenArray, tyArray, tyVarargs: result = ctArray + of tyOpenArray, tyArray, tyVarargs, tyUncheckedArray: result = ctArray of tyObject, tyTuple: result = ctStruct of tyUserTypeClasses: doAssert typ.isResolvedUserTypeClass @@ -161,7 +161,7 @@ proc mapType(conf: ConfigRef; typ: PType): TCTypeKind = of tyPtr, tyVar, tyLent, tyRef, tyOptAsRef: var base = skipTypes(typ.lastSon, typedescInst) case base.kind - of tyOpenArray, tyArray, tyVarargs: result = ctPtrToArray + of tyOpenArray, tyArray, tyVarargs, tyUncheckedArray: result = ctPtrToArray of tySet: if mapSetType(conf, base) == ctArray: result = ctPtrToArray else: result = ctPtr @@ -193,8 +193,6 @@ proc isImportedCppType(t: PType): bool = (x.sym != nil and sfInfixCall in x.sym.flags) proc getTypeDescAux(m: BModule, origTyp: PType, check: var IntSet): Rope -proc needsComplexAssignment(typ: PType): bool = - result = containsGarbageCollectedRef(typ) proc isObjLackingTypeField(typ: PType): bool {.inline.} = result = (typ.kind == tyObject) and ((tfFinal in typ.flags) and @@ -214,7 +212,7 @@ proc isInvalidReturnType(conf: ConfigRef; rettype: PType): bool = of ctStruct: let t = skipTypes(rettype, typedescInst) if rettype.isImportedCppType or t.isImportedCppType: return false - result = needsComplexAssignment(t) or + result = containsGarbageCollectedRef(t) or (t.kind == tyObject and not isObjLackingTypeField(t)) else: result = false @@ -290,7 +288,7 @@ proc getSimpleTypeDesc(m: BModule, typ: PType): Rope = of tyCString: result = typeNameOrLiteral(m, typ, "NCSTRING") of tyBool: result = typeNameOrLiteral(m, typ, "NIM_BOOL") of tyChar: result = typeNameOrLiteral(m, typ, "NIM_CHAR") - of tyNil: result = typeNameOrLiteral(m, typ, "0") + of tyNil: result = typeNameOrLiteral(m, typ, "void*") of tyInt..tyUInt64: result = typeNameOrLiteral(m, typ, NumericalTypeToStr[typ.kind]) of tyDistinct, tyRange, tyOrdinal: result = getSimpleTypeDesc(m, typ.sons[0]) @@ -430,9 +428,8 @@ proc genProcParams(m: BModule, t: PType, rettype, params: var Rope, else: add(params, ")") params = "(" & params -proc mangleRecFieldName(m: BModule; field: PSym, rectype: PType): Rope = - if (rectype.sym != nil) and - ({sfImportc, sfExportc} * rectype.sym.flags != {}): +proc mangleRecFieldName(m: BModule; field: PSym): Rope = + if {sfImportc, sfExportc} * field.flags != {}: result = field.loc.r else: result = rope(mangleField(m, field.name)) @@ -484,7 +481,7 @@ proc genRecordFieldsAux(m: BModule, n: PNode, let field = n.sym if field.typ.kind == tyVoid: return #assert(field.ast == nil) - let sname = mangleRecFieldName(m, field, rectype) + let sname = mangleRecFieldName(m, field) let ae = if accessExpr != nil: "$1.$2" % [accessExpr, sname] else: sname fillLoc(field.loc, locField, n, ae, OnUnknown) @@ -493,7 +490,7 @@ proc genRecordFieldsAux(m: BModule, n: PNode, # with heavily templatized C++ code: if not isImportedCppType(rectype): let fieldType = field.loc.lode.typ.skipTypes(abstractInst) - if fieldType.kind == tyArray and tfUncheckedArray in fieldType.flags: + if fieldType.kind == tyUncheckedArray: addf(result, "$1 $2[SEQ_DECL_SIZE];$n", [getTypeDescAux(m, fieldType.elemType, check), sname]) elif fieldType.kind == tySequence and m.config.selectedGC != gcDestructors: @@ -547,7 +544,7 @@ proc getRecordDesc(m: BModule, typ: PType, name: Rope, appcg(m, result, "virtual void raise() {throw *this;}$n") # required for polymorphic exceptions if typ.sym.magic == mException: # Add cleanup destructor to Exception base class - appcg(m, result, "~$1() {if(this->raise_id) popCurrentExceptionEx(this->raise_id);}$n", [name]) + appcg(m, result, "~$1() {if(this->raiseId) popCurrentExceptionEx(this->raiseId);}$n", [name]) # hack: forward declare popCurrentExceptionEx() on top of type description, # proper request to generate popCurrentExceptionEx not possible for 2 reasons: # generated function will be below declared Exception type and circular dependency @@ -747,6 +744,12 @@ proc getTypeDescAux(m: BModule, origTyp: PType, check: var IntSet): Rope = else: result = rope("TGenericSeq") add(result, seqStar(m)) + of tyUncheckedArray: + result = getTypeName(m, origTyp, sig) + m.typeCache[sig] = result + if not isImportedType(t): + let foo = getTypeDescAux(m, t.sons[0], check) + addf(m.s[cfsTypes], "typedef $1 $2[1];$n", [foo, result]) of tyArray: var n: BiggestInt = lengthOrd(m.config, t) if n <= 0: n = 1 # make an array of at least one element @@ -855,8 +858,10 @@ proc getTypeDesc(m: BModule, typ: PType): Rope = result = getTypeDescAux(m, typ, check) type - TClosureTypeKind = enum - clHalf, clHalfWithEnv, clFull + TClosureTypeKind = enum ## In C closures are mapped to 3 different things. + clHalf, ## fn(args) type without the trailing 'void* env' parameter + clHalfWithEnv, ## fn(args, void* env) type with trailing 'void* env' parameter + clFull ## struct {fn(args, void* env), env} proc getClosureType(m: BModule, t: PType, kind: TClosureTypeKind): Rope = assert t.kind == tyProc @@ -1181,6 +1186,7 @@ proc genTypeInfo(m: BModule, t: PType; info: TLineInfo): Rope = let owner = t.skipTypes(typedescPtrs).owner.getModule if owner != m.module: # make sure the type info is created in the owner module + assert m.g.modules[owner.position] != nil discard genTypeInfo(m.g.modules[owner.position], origType, info) # reference the type info as extern here discard cgsym(m, "TNimType") @@ -1206,8 +1212,8 @@ proc genTypeInfo(m: BModule, t: PType; info: TLineInfo): Rope = let x = fakeClosureType(m, t.owner) genTupleInfo(m, x, x, result, info) of tySequence: + genTypeInfoAux(m, t, t, result, info) if m.config.selectedGC != gcDestructors: - genTypeInfoAux(m, t, t, result, info) if m.config.selectedGC >= gcMarkAndSweep: let markerProc = genTraverseProc(m, origType, sig) addf(m.s[cfsTypeInit3], "$1.marker = $2;$n", [result, markerProc]) @@ -1216,7 +1222,7 @@ proc genTypeInfo(m: BModule, t: PType; info: TLineInfo): Rope = if m.config.selectedGC >= gcMarkAndSweep: let markerProc = genTraverseProc(m, origType, sig) addf(m.s[cfsTypeInit3], "$1.marker = $2;$n", [result, markerProc]) - of tyPtr, tyRange: genTypeInfoAux(m, t, t, result, info) + of tyPtr, tyRange, tyUncheckedArray: genTypeInfoAux(m, t, t, result, info) of tyArray: genArrayInfo(m, t, result, info) of tySet: genSetInfo(m, t, result, info) of tyEnum: genEnumInfo(m, t, result, info) diff --git a/compiler/cgen.nim b/compiler/cgen.nim index 3040f98da..3545edc88 100644 --- a/compiler/cgen.nim +++ b/compiler/cgen.nim @@ -14,11 +14,14 @@ import nversion, nimsets, msgs, std / sha1, bitsets, idents, types, ccgutils, os, ropes, math, passes, wordrecg, treetab, cgmeth, condsyms, rodutils, renderer, idgen, cgendata, ccgmerge, semfold, aliases, - lowerings, semparallel, tables, sets, ndi, lineinfos, pathutils + lowerings, tables, sets, ndi, lineinfos, pathutils, transf + +when not defined(leanCompiler): + import semparallel import strutils except `%` # collides with ropes.`%` -from modulegraphs import ModuleGraph +from modulegraphs import ModuleGraph, PPassContext from lineinfos import warnGcMem, errXMustBeCompileTime, hintDependency, errGenerated, errCannotOpenFile import dynlib @@ -42,8 +45,7 @@ when options.hasTinyCBackend: # implementation proc addForwardedProc(m: BModule, prc: PSym) = - m.forwardedProcs.add(prc) - inc(m.g.forwardedProcsCounter) + m.g.forwardedProcs.add(prc) proc findPendingModule(m: BModule, s: PSym): BModule = var ms = getModule(s) @@ -292,10 +294,10 @@ proc genObjectInit(p: BProc, section: TCProcSection, t: PType, a: TLoc, type TAssignmentFlag = enum - needToCopy, afDestIsNil, afDestIsNotNil, afSrcIsNil, afSrcIsNotNil + needToCopy TAssignmentFlags = set[TAssignmentFlag] -proc genRefAssign(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) +proc genRefAssign(p: BProc, dest, src: TLoc) proc isComplexValueType(t: PType): bool {.inline.} = let t = t.skipTypes(abstractInst + tyUserTypeClasses) @@ -311,7 +313,7 @@ proc resetLoc(p: BProc, loc: var TLoc) = var nilLoc: TLoc initLoc(nilLoc, locTemp, loc.lode, OnStack) nilLoc.r = rope("NIM_NIL") - genRefAssign(p, loc, nilLoc, {afSrcIsNil}) + genRefAssign(p, loc, nilLoc) else: linefmt(p, cpsStmts, "$1 = 0;$n", rdLoc(loc)) else: @@ -444,20 +446,21 @@ proc assignGlobalVar(p: BProc, n: PNode) = return useHeader(p.module, s) if lfNoDecl in s.loc.flags: return - if sfThread in s.flags: - declareThreadVar(p.module, s, sfImportc in s.flags) - else: - var decl: Rope = nil - var td = getTypeDesc(p.module, s.loc.t) - if s.constraint.isNil: - if sfImportc in s.flags: add(decl, "extern ") - add(decl, td) - if sfRegister in s.flags: add(decl, " register") - if sfVolatile in s.flags: add(decl, " volatile") - addf(decl, " $1;$n", [s.loc.r]) + if not containsOrIncl(p.module.declaredThings, s.id): + if sfThread in s.flags: + declareThreadVar(p.module, s, sfImportc in s.flags) else: - decl = (s.cgDeclFrmt & ";$n") % [td, s.loc.r] - add(p.module.s[cfsVars], decl) + var decl: Rope = nil + var td = getTypeDesc(p.module, s.loc.t) + if s.constraint.isNil: + if sfImportc in s.flags: add(decl, "extern ") + add(decl, td) + if sfRegister in s.flags: add(decl, " register") + if sfVolatile in s.flags: add(decl, " volatile") + addf(decl, " $1;$n", [s.loc.r]) + else: + decl = (s.cgDeclFrmt & ";$n") % [td, s.loc.r] + add(p.module.s[cfsVars], decl) if p.withinLoop > 0: # fixes tests/run/tzeroarray: resetLoc(p, s.loc) @@ -567,7 +570,12 @@ proc loadDynamicLib(m: BModule, lib: PLib) = var p = newProc(nil, m) p.options = p.options - {optStackTrace, optEndb} var dest: TLoc - initLocExpr(p, lib.path, dest) + initLoc(dest, locTemp, lib.path, OnStack) + dest.r = getTempName(m) + appcg(m, m.s[cfsDynLibInit],"$1 $2;$n", + [getTypeDesc(m, lib.path.typ), rdLoc(dest)]) + expr(p, lib.path, dest) + add(m.s[cfsVars], p.s(cpsLocals)) add(m.s[cfsDynLibInit], p.s(cpsInit)) add(m.s[cfsDynLibInit], p.s(cpsStmts)) @@ -697,8 +705,12 @@ proc closureSetup(p: BProc, prc: PSym) = #echo "created environment: ", env.id, " for ", prc.name.s assignLocalVar(p, ls) # generate cast assignment: - linefmt(p, cpsStmts, "$1 = ($2) ClE_0;$n", - rdLoc(env.loc), getTypeDesc(p.module, env.typ)) + if p.config.selectedGC == gcGo: + linefmt(p, cpsStmts, "#unsureAsgnRef((void**) $1, ($2) ClE_0);$n", + addrLoc(p.config, env.loc), getTypeDesc(p.module, env.typ)) + else: + linefmt(p, cpsStmts, "$1 = ($2) ClE_0;$n", + rdLoc(env.loc), getTypeDesc(p.module, env.typ)) proc containsResult(n: PNode): bool = if n.kind == nkSym and n.sym.kind == skResult: @@ -766,7 +778,13 @@ proc allPathsAsgnResult(n: PNode): InitResultEnum = result = InitRequired of nkReturnStmt: if n.len > 0: - result = allPathsAsgnResult(n[0]) + if n[0].kind == nkEmpty and result != InitSkippable: + # This is a bare `return` statement, if `result` was not initialized + # anywhere else (or if we're not sure about this) let's require it to be + # initialized. This avoids cases like #9286 where this heuristic lead to + # wrong code being generated. + result = InitRequired + else: result = allPathsAsgnResult(n[0]) of nkIfStmt, nkIfExpr: var exhaustive = false result = InitSkippable @@ -830,6 +848,8 @@ proc genProcAux(m: BModule, prc: PSym) = var header = genProcHeader(m, prc) var returnStmt: Rope = nil assert(prc.ast != nil) + let procBody = transformBody(m.g.graph, prc, cache = false) + if sfPure notin prc.flags and prc.typ.sons[0] != nil: if resultPos >= prc.ast.len: internalError(m.config, prc.info, "proc has no result symbol") @@ -837,7 +857,7 @@ proc genProcAux(m: BModule, prc: PSym) = let res = resNode.sym # get result symbol if not isInvalidReturnType(m.config, prc.typ.sons[0]): if sfNoInit in prc.flags: incl(res.flags, sfNoInit) - if sfNoInit in prc.flags and p.module.compileToCpp and (let val = easyResultAsgn(prc.getBody); val != nil): + if sfNoInit in prc.flags and p.module.compileToCpp and (let val = easyResultAsgn(procBody); val != nil): var decl = localVarDecl(p, resNode) var a: TLoc initLocExprSingleUse(p, val, a) @@ -858,7 +878,7 @@ proc genProcAux(m: BModule, prc: PSym) = # global is either 'nil' or points to valid memory and so the RC operation # succeeds without touching not-initialized memory. if sfNoInit in prc.flags: discard - elif allPathsAsgnResult(prc.getBody) == InitSkippable: discard + elif allPathsAsgnResult(procBody) == InitSkippable: discard else: resetLoc(p, res.loc) if skipTypes(res.typ, abstractInst).kind == tyArray: @@ -870,7 +890,7 @@ proc genProcAux(m: BModule, prc: PSym) = if param.typ.isCompileTimeOnly: continue assignParam(p, param) closureSetup(p, prc) - genStmts(p, prc.getBody) # modifies p.locals, p.init, etc. + genStmts(p, procBody) # modifies p.locals, p.init, etc. var generatedProc: Rope if sfNoReturn in prc.flags: if hasDeclspec in extccomp.CC[p.config.cCompiler].props: @@ -1019,7 +1039,7 @@ proc genVarPrototype(m: BModule, n: PNode) = let sym = n.sym useHeader(m, sym) fillLoc(sym.loc, locGlobalVar, n, mangleName(m, sym), OnHeap) - if (lfNoDecl in sym.loc.flags) or containsOrIncl(m.declaredThings, sym.id): + if (lfNoDecl in sym.loc.flags) or contains(m.declaredThings, sym.id): return if sym.owner.id != m.module.id: # else we already have the symbol generated! @@ -1038,7 +1058,7 @@ proc addIntTypes(result: var Rope; conf: ConfigRef) {.inline.} = addf(result, "#define NIM_NEW_MANGLING_RULES\L" & "#define NIM_INTBITS $1\L", [ platform.CPU[conf.target.targetCPU].intSize.rope]) - if conf.cppCustomNamespace.len > 0: + if conf.cppCustomNamespace.len > 0: result.add("#define USE_NIM_NAMESPACE ") result.add(conf.cppCustomNamespace) result.add("\L") @@ -1373,7 +1393,6 @@ proc rawNewModule(g: BModuleList; module: PSym, filename: AbsoluteFile): BModule result.preInitProc = newPreInitProc(result) initNodeTable(result.dataCache) result.typeStack = @[] - result.forwardedProcs = @[] result.typeNodesName = getTempName(result) result.nimTypesName = getTempName(result) # no line tracing for the init sections of the system module so that we @@ -1389,49 +1408,6 @@ proc nullify[T](arr: var T) = for i in low(arr)..high(arr): arr[i] = Rope(nil) -proc resetModule*(m: BModule) = - # between two compilations in CAAS mode, we can throw - # away all the data that was written to disk - m.headerFiles = @[] - m.declaredProtos = initIntSet() - m.forwTypeCache = initTable[SigHash, Rope]() - m.initProc = newProc(nil, m) - m.initProc.options = initProcOptions(m) - m.preInitProc = newPreInitProc(m) - initNodeTable(m.dataCache) - m.typeStack = @[] - m.forwardedProcs = @[] - m.typeNodesName = getTempName(m) - m.nimTypesName = getTempName(m) - if sfSystemModule in m.module.flags: - incl m.flags, preventStackTrace - else: - excl m.flags, preventStackTrace - nullify m.s - m.typeNodes = 0 - m.nimTypes = 0 - nullify m.extensionLoaders - - # indicate that this is now cached module - # the cache will be invalidated by nullifying gModules - #m.fromCache = true - m.g = nil - - # we keep only the "merge info" information for the module - # and the properties that can't change: - # m.filename - # m.cfilename - # m.isHeaderFile - # m.module ? - # m.typeCache - # m.declaredThings - # m.typeInfoMarker - # m.labels - # m.FrameDeclared - -proc resetCgenModules*(g: BModuleList) = - for m in cgenModules(g): resetModule(m) - proc rawNewModule(g: BModuleList; module: PSym; conf: ConfigRef): BModule = result = rawNewModule(g, module, AbsoluteFile toFullPath(conf, module.position.FileIndex)) @@ -1507,27 +1483,14 @@ proc myProcess(b: PPassContext, n: PNode): PNode = m.initProc.options = initProcOptions(m) #softRnl = if optLineDir in m.config.options: noRnl else: rnl # XXX replicate this logic! - genStmts(m.initProc, n) - -proc finishModule(m: BModule) = - var i = 0 - while i <= high(m.forwardedProcs): - # Note: ``genProc`` may add to ``m.forwardedProcs``, so we cannot use - # a ``for`` loop here - var prc = m.forwardedProcs[i] - if sfForward in prc.flags: - internalError(m.config, prc.info, "still forwarded: " & prc.name.s) - genProcNoForward(m, prc) - inc(i) - assert(m.g.forwardedProcsCounter >= i) - dec(m.g.forwardedProcsCounter, i) - setLen(m.forwardedProcs, 0) + let tranformed_n = transformStmt(m.g.graph, m.module, n) + genStmts(m.initProc, tranformed_n) proc shouldRecompile(m: BModule; code: Rope, cfile: Cfile): bool = result = true if optForceFullMake notin m.config.globalOptions: if not equalsFile(code, cfile.cname): - if isDefined(m.config, "nimdiff"): + if m.config.symbolFiles == readOnlySf: #isDefined(m.config, "nimdiff"): if fileExists(cfile.cname): copyFile(cfile.cname.string, cfile.cname.string & ".backup") echo "diff ", cfile.cname.string, ".backup ", cfile.cname.string @@ -1618,22 +1581,38 @@ proc myClose(graph: ModuleGraph; b: PPassContext, n: PNode): PNode = registerModuleToMain(m.g, m.module) if sfMainModule in m.module.flags: - if m.g.forwardedProcsCounter == 0: + if m.g.forwardedProcs.len == 0: incl m.flags, objHasKidsValid let disp = generateMethodDispatchers(graph) for x in disp: genProcAux(m, x.sym) genMainProc(m) +proc genForwardedProcs(g: BModuleList) = + # Forward declared proc:s lack bodies when first encountered, so they're given + # a second pass here + # Note: ``genProcNoForward`` may add to ``forwardedProcs`` + while g.forwardedProcs.len > 0: + let + prc = g.forwardedProcs.pop() + ms = getModule(prc) + m = g.modules[ms.position] + if sfForward in prc.flags: + internalError(m.config, prc.info, "still forwarded: " & prc.name.s) + + genProcNoForward(m, prc) + proc cgenWriteModules*(backend: RootRef, config: ConfigRef) = let g = BModuleList(backend) # we need to process the transitive closure because recursive module # deps are allowed (and the system module is processed in the wrong # order anyway) g.config = config - if g.generatedHeader != nil: finishModule(g.generatedHeader) - while g.forwardedProcsCounter > 0: - for m in cgenModules(g): - finishModule(m) + let (outDir, _, _) = splitFile(config.outfile) + if not outDir.isEmpty: + createDir(outDir) + + genForwardedProcs(g) + for m in cgenModules(g): m.writeModule(pending=true) writeMapping(config, g.mapping) diff --git a/compiler/cgendata.nim b/compiler/cgendata.nim index 406ee050f..28e36364e 100644 --- a/compiler/cgendata.nim +++ b/compiler/cgendata.nim @@ -13,7 +13,7 @@ import ast, astalgo, ropes, passes, options, intsets, platform, sighashes, tables, ndi, lineinfos, pathutils -from modulegraphs import ModuleGraph +from modulegraphs import ModuleGraph, PPassContext type TLabel* = Rope # for the C generator a label is just a rope @@ -112,7 +112,7 @@ type mainModProcs*, mainModInit*, otherModsInit*, mainDatInit*: Rope mapping*: Rope # the generated mapping file (if requested) modules*: seq[BModule] # list of all compiled modules - forwardedProcsCounter*: int + forwardedProcs*: seq[PSym] # proc:s that did not yet have a body generatedHeader*: BModule breakPointId*: int breakpoints*: Rope # later the breakpoints are inserted into the main proc @@ -132,7 +132,7 @@ type # nimtvDeps is VERY hard to cache because it's # not a list of IDs nor can it be made to be one. - TCGen = object of TPassContext # represents a C source file + TCGen = object of PPassContext # represents a C source file s*: TCFileSections # sections of the C file flags*: set[Codegenflag] module*: PSym @@ -150,7 +150,6 @@ type preInitProc*: BProc # code executed before the init proc typeStack*: TTypeSeq # used for type generation dataCache*: TNodeTable - forwardedProcs*: TSymSeq # keep forwarded procs here typeNodes*, nimTypes*: int # used for type info generation typeNodesName*, nimTypesName*: Rope # used for type info generation labels*: Natural # for generating unique module-scope names @@ -188,12 +187,12 @@ proc newProc*(prc: PSym, module: BModule): BProc = result.sigConflicts = initCountTable[string]() proc newModuleList*(g: ModuleGraph): BModuleList = - BModuleList(modules: @[], typeInfoMarker: initTable[SigHash, Rope](), config: g.config, - graph: g, nimtvDeps: @[], nimtvDeclared: initIntSet()) + BModuleList(typeInfoMarker: initTable[SigHash, Rope](), config: g.config, + graph: g, nimtvDeclared: initIntSet()) iterator cgenModules*(g: BModuleList): BModule = - for i in 0..high(g.modules): + for m in g.modules: # ultimately, we are iterating over the file ids here. # some "files" won't have an associated cgen module (like stdin) # and we must skip over them. - if g.modules[i] != nil: yield g.modules[i] + if m != nil: yield m diff --git a/compiler/cgmeth.nim b/compiler/cgmeth.nim index d0ec6c636..cba07446f 100644 --- a/compiler/cgmeth.nim +++ b/compiler/cgmeth.nim @@ -73,7 +73,7 @@ proc sameMethodBucket(a, b: PSym): MethodResult = bb = bb.lastSon else: break - if sameType(aa, bb): + if sameType(a.typ.sons[i], b.typ.sons[i]): if aa.kind == tyObject and result != Invalid: result = Yes elif aa.kind == tyObject and bb.kind == tyObject: @@ -83,7 +83,7 @@ proc sameMethodBucket(a, b: PSym): MethodResult = result = Yes else: return No - elif diff != high(int): + elif diff != high(int) and sfFromGeneric notin (a.flags+b.flags): result = Invalid else: return No @@ -281,6 +281,7 @@ proc genDispatcher(g: ModuleGraph; methods: TSymSeq, relevantCols: IntSet): PSym else: disp = ret nilchecks.add disp + nilchecks.flags.incl nfTransf # should not be further transformed result.ast.sons[bodyPos] = nilchecks proc generateMethodDispatchers*(g: ModuleGraph): PNode = diff --git a/compiler/closureiters.nim b/compiler/closureiters.nim index b0857e6c7..5ded6d054 100644 --- a/compiler/closureiters.nim +++ b/compiler/closureiters.nim @@ -210,7 +210,7 @@ proc newUnrollFinallyAccess(ctx: var Ctx, info: TLineInfo): PNode = proc newCurExcAccess(ctx: var Ctx): PNode = if ctx.curExcSym.isNil: - ctx.curExcSym = ctx.newEnvVar(":curExc", ctx.g.callCodegenProc("getCurrentException", ctx.g.emptyNode).typ) + ctx.curExcSym = ctx.newEnvVar(":curExc", ctx.g.callCodegenProc("getCurrentException").typ) ctx.newEnvVarAccess(ctx.curExcSym) proc newState(ctx: var Ctx, n, gotoOut: PNode): int = @@ -324,7 +324,7 @@ proc collectExceptState(ctx: var Ctx, n: PNode): PNode {.inline.} = assert(c[i].kind == nkType) let nextCond = newTree(nkCall, newSymNode(g.getSysMagic(c.info, "of", mOf)), - g.callCodegenProc("getCurrentException", ctx.g.emptyNode), + g.callCodegenProc("getCurrentException"), c[i]) nextCond.typ = ctx.g.getSysType(c.info, tyBool) nextCond.info = c.info @@ -364,7 +364,7 @@ proc addElseToExcept(ctx: var Ctx, n: PNode) = block: # :curExc = getCurrentException() branchBody.add(newTree(nkAsgn, ctx.newCurExcAccess(), - ctx.g.callCodegenProc("getCurrentException", ctx.g.emptyNode))) + ctx.g.callCodegenProc("getCurrentException"))) block: # goto nearestFinally branchBody.add(newTree(nkGotoState, ctx.g.newIntLit(n.info, ctx.nearestFinally))) @@ -462,10 +462,17 @@ proc lowerStmtListExprs(ctx: var Ctx, n: PNode, needsSplit: var bool): PNode = result.typ = n.typ for i in 0 ..< n.len: - if n[i].kind == nkStmtListExpr: + case n[i].kind + of nkExprColonExpr: + if n[i][1].kind == nkStmtListExpr: + let (st, ex) = exprToStmtList(n[i][1]) + result.add(st) + n[i][1] = ex + of nkStmtListExpr: let (st, ex) = exprToStmtList(n[i]) result.add(st) n[i] = ex + else: discard result.add(n) of nkIfStmt, nkIfExpr: @@ -803,7 +810,7 @@ proc newEndFinallyNode(ctx: var Ctx, info: TLineInfo): PNode = let branch = newTree(nkElifBranch, cmp, retStmt) # The C++ backend requires `getCurrentException` here. - let raiseStmt = newTree(nkRaiseStmt, ctx.g.callCodegenProc("getCurrentException", ctx.g.emptyNode)) + let raiseStmt = newTree(nkRaiseStmt, ctx.g.callCodegenProc("getCurrentException")) raiseStmt.info = info let elseBranch = newTree(nkElse, raiseStmt) @@ -852,15 +859,8 @@ proc transformClosureIteratorBody(ctx: var Ctx, n: PNode, gotoOut: PNode): PNode discard of nkStmtList, nkStmtListExpr: - assert(isEmptyType(n.typ), "nkStmtListExpr not lowered") - result = addGotoOut(result, gotoOut) for i in 0 ..< n.len: - if n[i].hasYieldsInExpressions: - # Lower nkStmtListExpr nodes inside `n[i]` first - var ns = false - n[i] = ctx.lowerStmtListExprs(n[i], ns) - if n[i].hasYields: # Create a new split let go = newNodeI(nkGotoState, n[i].info) @@ -1009,7 +1009,7 @@ proc stateFromGotoState(n: PNode): int = assert(n.kind == nkGotoState) result = n[0].intVal.int -proc tranformStateAssignments(ctx: var Ctx, n: PNode): PNode = +proc transformStateAssignments(ctx: var Ctx, n: PNode): PNode = # This transforms 3 patterns: ########################## 1 # yield e @@ -1051,7 +1051,7 @@ proc tranformStateAssignments(ctx: var Ctx, n: PNode): PNode = result.add(retStmt) else: for i in 0 ..< n.len: - n[i] = ctx.tranformStateAssignments(n[i]) + n[i] = ctx.transformStateAssignments(n[i]) of nkSkip: discard @@ -1071,7 +1071,7 @@ proc tranformStateAssignments(ctx: var Ctx, n: PNode): PNode = else: for i in 0 ..< n.len: - n[i] = ctx.tranformStateAssignments(n[i]) + n[i] = ctx.transformStateAssignments(n[i]) proc skipStmtList(ctx: Ctx; n: PNode): PNode = result = n @@ -1204,7 +1204,7 @@ proc newCatchBody(ctx: var Ctx, info: TLineInfo): PNode {.inline.} = block: result.add(newTree(nkAsgn, ctx.newCurExcAccess(), - ctx.g.callCodegenProc("getCurrentException", ctx.g.emptyNode))) + ctx.g.callCodegenProc("getCurrentException"))) proc wrapIntoTryExcept(ctx: var Ctx, n: PNode): PNode {.inline.} = let setupExc = newTree(nkCall, @@ -1220,18 +1220,20 @@ proc wrapIntoStateLoop(ctx: var Ctx, n: PNode): PNode = # while true: # block :stateLoop: # gotoState :state + # local vars decl (if needed) # body # Might get wrapped in try-except let loopBody = newNodeI(nkStmtList, n.info) result = newTree(nkWhileStmt, newSymNode(ctx.g.getSysSym(n.info, "true")), loopBody) result.info = n.info + let localVars = newNodeI(nkStmtList, n.info) if not ctx.stateVarSym.isNil: let varSect = newNodeI(nkVarSection, n.info) addVar(varSect, newSymNode(ctx.stateVarSym)) - loopBody.add(varSect) + localVars.add(varSect) if not ctx.tempVars.isNil: - loopBody.add(ctx.tempVars) + localVars.add(ctx.tempVars) let blockStmt = newNodeI(nkBlockStmt, n.info) blockStmt.add(newSymNode(ctx.stateLoopLabel)) @@ -1240,7 +1242,7 @@ proc wrapIntoStateLoop(ctx: var Ctx, n: PNode): PNode = gs.add(ctx.newStateAccess()) gs.add(ctx.g.newIntLit(n.info, ctx.states.len - 1)) - var blockBody = newTree(nkStmtList, gs, n) + var blockBody = newTree(nkStmtList, gs, localVars, n) if ctx.hasExceptions: blockBody = ctx.wrapIntoTryExcept(blockBody) @@ -1292,13 +1294,18 @@ proc transformClosureIterator*(g: ModuleGraph; fn: PSym, n: PNode): PNode = # should folllow the same logic. ctx.stateVarSym = newSym(skVar, getIdent(ctx.g.cache, ":state"), fn, fn.info) ctx.stateVarSym.typ = g.createClosureIterStateType(fn) - ctx.stateLoopLabel = newSym(skLabel, getIdent(ctx.g.cache, ":stateLoop"), fn, fn.info) - let n = n.toStmtList + var n = n.toStmtList discard ctx.newState(n, nil) let gotoOut = newTree(nkGotoState, g.newIntLit(n.info, -1)) + var ns = false + n = ctx.lowerStmtListExprs(n, ns) + + if n.hasYieldsInExpressions(): + internalError(ctx.g.config, "yield in expr not lowered") + # Splitting transformation discard ctx.transformClosureIteratorBody(n, gotoOut) @@ -1314,7 +1321,7 @@ proc transformClosureIterator*(g: ModuleGraph; fn: PSym, n: PNode): PNode = result.add(s) result.add(body) - result = ctx.tranformStateAssignments(result) + result = ctx.transformStateAssignments(result) result = ctx.wrapIntoStateLoop(result) # echo "TRANSFORM TO STATES: " diff --git a/compiler/commands.nim b/compiler/commands.nim index 39967c4bc..fa17e9851 100644 --- a/compiler/commands.nim +++ b/compiler/commands.nim @@ -115,6 +115,7 @@ const errInvalidCmdLineOption = "invalid command line option: '$1'" errOnOrOffExpectedButXFound = "'on' or 'off' expected, but '$1' found" errOnOffOrListExpectedButXFound = "'on', 'off' or 'list' expected, but '$1' found" + errOffHintsError = "'off', 'hint' or 'error' expected, but '$1' found" proc invalidCmdLineOption(conf: ConfigRef; pass: TCmdLinePass, switch: string, info: TLineInfo) = if switch == " ": localError(conf, info, errInvalidCmdLineOption % "-") @@ -641,14 +642,19 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo; of "help", "h": expectNoArg(conf, switch, arg, pass, info) helpOnError(conf, pass) - of "symbolfiles", "incremental": + of "symbolfiles": discard "ignore for backwards compat" + of "incremental": + when not defined(nimIncremental): + localError(conf, info, "the compiler was not built with " & + "incremental compilation features; bootstrap with " & + "-d:nimIncremental to enable") case arg.normalize of "on": conf.symbolFiles = v2Sf of "off": conf.symbolFiles = disabledSf of "writeonly": conf.symbolFiles = writeOnlySf of "readonly": conf.symbolFiles = readOnlySf of "v2": conf.symbolFiles = v2Sf - else: localError(conf, info, "invalid option for --symbolFiles: " & arg) + else: localError(conf, info, "invalid option for --incremental: " & arg) of "skipcfg": expectNoArg(conf, switch, arg, pass, info) incl(conf.globalOptions, optSkipSystemConfigFile) @@ -732,8 +738,14 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo; doAssert(conf != nil) incl(conf.features, destructor) defineSymbol(conf.symbols, "nimNewRuntime") - of "nep1": - processOnOffSwitchG(conf, {optCheckNep1}, arg, pass, info) + of "stylecheck": + case arg.normalize + of "off": conf.globalOptions = conf.globalOptions - {optStyleHint, optStyleError} + of "hint": conf.globalOptions = conf.globalOptions + {optStyleHint} + of "error": conf.globalOptions = conf.globalOptions + {optStyleError} + else: localError(conf, info, errOffHintsError % arg) + of "showallmismatches": + processOnOffSwitchG(conf, {optShowAllMismatches}, arg, pass, info) of "cppcompiletonamespace": if arg.len > 0: conf.cppCustomNamespace = arg @@ -769,6 +781,7 @@ proc processArgument*(pass: TCmdLinePass; p: OptParser; # nim filename.nims is the same as "nim e filename.nims": if p.key.endswith(".nims"): config.command = "e" + incl(config.globalOptions, optWasNimscript) config.projectName = unixToNativePath(p.key) config.arguments = cmdLineRest(p) result = true diff --git a/compiler/condsyms.nim b/compiler/condsyms.nim index 62c55de3d..9a4c1701c 100644 --- a/compiler/condsyms.nim +++ b/compiler/condsyms.nim @@ -58,6 +58,7 @@ proc initDefines*(symbols: StringTableRef) = defineSymbol("nimtypedescfixed") defineSymbol("nimKnowsNimvm") defineSymbol("nimArrIdx") + defineSymbol("nimHasalignOf") defineSymbol("nimImmediateDeprecated") defineSymbol("nimNewShiftOps") defineSymbol("nimDistros") @@ -82,7 +83,10 @@ proc initDefines*(symbols: StringTableRef) = defineSymbol("nimNoNilSeqs") defineSymbol("nimNoNilSeqs2") defineSymbol("nimHasUserErrors") - + defineSymbol("nimUncheckedArrayTyp") + defineSymbol("nimHasTypeof") + defineSymbol("nimErrorProcCanHaveBody") + defineSymbol("nimHasInstantiationOfInMacro") defineSymbol("nimHasNilSeqs") for f in low(Feature)..high(Feature): defineSymbol("nimHas" & $f) diff --git a/compiler/depends.nim b/compiler/depends.nim index c26593ea5..d380d637a 100644 --- a/compiler/depends.nim +++ b/compiler/depends.nim @@ -13,10 +13,10 @@ import os, options, ast, astalgo, msgs, ropes, idents, passes, modulepaths, pathutils -from modulegraphs import ModuleGraph +from modulegraphs import ModuleGraph, PPassContext type - TGen = object of TPassContext + TGen = object of PPassContext module: PSym config: ConfigRef graph: ModuleGraph diff --git a/compiler/destroyer.nim b/compiler/destroyer.nim index ff5494ad8..51ad26f2c 100644 --- a/compiler/destroyer.nim +++ b/compiler/destroyer.nim @@ -117,7 +117,7 @@ Remarks: Rule 1.2 is not yet implemented because ``sink`` is currently import intsets, ast, astalgo, msgs, renderer, magicsys, types, idents, trees, strutils, options, dfa, lowerings, tables, modulegraphs, - lineinfos + lineinfos, parampatterns const InterestingSyms = {skVar, skResult, skLet} @@ -133,6 +133,7 @@ type toDropBit: Table[int, PSym] graph: ModuleGraph emptyNode: PNode + otherRead: PNode proc getTemp(c: var Con; typ: PType; info: TLineInfo): PNode = # XXX why are temps fields in an object here? @@ -190,14 +191,81 @@ proc isHarmlessVar*(s: PSym; c: Con): bool = # if we want to die after the first 'use': if usages > 1: return false inc usages - of useWithinCall: - if c.g[i].sym == s: return false + #of useWithinCall: + # if c.g[i].sym == s: return false of goto, fork: discard "we do not perform an abstract interpretation yet" + result = usages <= 1 + +proc isLastRead(n: PNode; c: var Con): bool = + # first we need to search for the instruction that belongs to 'n': + doAssert n.kind == nkSym + c.otherRead = nil + var instr = -1 + for i in 0..<c.g.len: + if c.g[i].n == n: + if instr < 0: instr = i + else: + # eh, we found two positions that belong to 'n'? + # better return 'false' then: + return false + if instr < 0: return false + # we go through all paths beginning from 'instr+1' and need to + # ensure that we don't find another 'use X' instruction. + if instr+1 >= c.g.len: return true + let s = n.sym + var pcs: seq[int] = @[instr+1] + var takenGotos: IntSet + var takenForks = initIntSet() + while pcs.len > 0: + var pc = pcs.pop + + takenGotos = initIntSet() + while pc < c.g.len: + case c.g[pc].kind + of def: + if c.g[pc].sym == s: + # the path lead to a redefinition of 's' --> abandon it. + when false: + # Too complex thinking ahead: In reality it is enough to find + # the 'def x' here on the current path to make the 'use x' valid. + # but for this the definition needs to dominate the usage: + var dominates = true + for j in pc+1 .. instr: + # not within the same basic block? + if c.g[j].kind in {goto, fork} and (j + c.g[j].dest) in (pc+1 .. instr): + #if j in c.jumpTargets: + dominates = false + if dominates: break + break + inc pc + of use: + if c.g[pc].sym == s: + c.otherRead = c.g[pc].n + return false + inc pc + of goto: + # we must leave endless loops eventually: + if not takenGotos.containsOrIncl(pc): + pc = pc + c.g[pc].dest + else: + inc pc + of fork: + # we follow the next instruction but push the dest onto our "work" stack: + if not takenForks.containsOrIncl(pc): + pcs.add pc + c.g[pc].dest + inc pc + #echo c.graph.config $ n.info, " last read here!" + return true template interestingSym(s: PSym): bool = s.owner == c.owner and s.kind in InterestingSyms and hasDestructor(s.typ) +template isUnpackedTuple(s: PSym): bool = + ## we move out all elements of unpacked tuples, + ## hence unpacked tuples themselves don't need to be destroyed + s.kind == skTemp and s.typ.kind == tyTuple + proc patchHead(n: PNode) = if n.kind in nkCallKinds and n[0].kind == nkSym and n.len > 1: let s = n[0].sym @@ -205,9 +273,6 @@ proc patchHead(n: PNode) = if sfFromGeneric in s.flags: excl(s.flags, sfFromGeneric) patchHead(s.getBody) - if n[1].typ.isNil: - # XXX toptree crashes without this workaround. Figure out why. - return let t = n[1].typ.skipTypes({tyVar, tyLent, tyGenericInst, tyAlias, tySink, tyInferred}) template patch(op, field) = if s.name.s == op and field != nil and field != s: @@ -222,26 +287,44 @@ proc patchHead(s: PSym) = if sfFromGeneric in s.flags: patchHead(s.ast[bodyPos]) -template genOp(opr, opname) = +proc checkForErrorPragma(c: Con; t: PType; ri: PNode; opname: string) = + var m = "'" & opname & "' is not available for type <" & typeToString(t) & ">" + if opname == "=" and ri != nil: + m.add "; requires a copy because it's not the last read of '" + m.add renderTree(ri) + m.add '\'' + if c.otherRead != nil: + m.add "; another read is done here: " + m.add c.graph.config $ c.otherRead.info + localError(c.graph.config, ri.info, errGenerated, m) + +proc makePtrType(c: Con, baseType: PType): PType = + result = newType(tyPtr, c.owner) + addSonSkipIntLit(result, baseType) + +template genOp(opr, opname, ri) = let op = opr if op == nil: globalError(c.graph.config, dest.info, "internal error: '" & opname & "' operator not found for type " & typeToString(t)) elif op.ast[genericParamsPos].kind != nkEmpty: globalError(c.graph.config, dest.info, "internal error: '" & opname & "' operator is generic") patchHead op - result = newTree(nkCall, newSymNode(op), newTree(nkHiddenAddr, dest)) + if sfError in op.flags: checkForErrorPragma(c, t, ri, opname) + let addrExp = newNodeIT(nkHiddenAddr, dest.info, makePtrType(c, dest.typ)) + addrExp.add(dest) + result = newTree(nkCall, newSymNode(op), addrExp) -proc genSink(c: Con; t: PType; dest: PNode): PNode = +proc genSink(c: Con; t: PType; dest, ri: PNode): PNode = let t = t.skipTypes({tyGenericInst, tyAlias, tySink}) - genOp(if t.sink != nil: t.sink else: t.assignment, "=sink") + genOp(if t.sink != nil: t.sink else: t.assignment, "=sink", ri) -proc genCopy(c: Con; t: PType; dest: PNode): PNode = +proc genCopy(c: Con; t: PType; dest, ri: PNode): PNode = let t = t.skipTypes({tyGenericInst, tyAlias, tySink}) - genOp(t.assignment, "=") + genOp(t.assignment, "=", ri) proc genDestroy(c: Con; t: PType; dest: PNode): PNode = let t = t.skipTypes({tyGenericInst, tyAlias, tySink}) - genOp(t.destructor, "=destroy") + genOp(t.destructor, "=destroy", nil) proc addTopVar(c: var Con; v: PNode) = c.topLevelVars.add newTree(nkIdentDefs, v, c.emptyNode, c.emptyNode) @@ -272,14 +355,12 @@ template recurse(n, dest) = proc isSinkParam(s: PSym): bool {.inline.} = result = s.kind == skParam and s.typ.kind == tySink -const constrExprs = nkCallKinds+{nkObjConstr} - proc destructiveMoveSink(n: PNode; c: var Con): PNode = # generate: (chckMove(sinkParam_AliveBit); sinkParam_AliveBit = false; sinkParam) result = newNodeIT(nkStmtListExpr, n.info, n.typ) let bit = newSymNode dropBit(c, n.sym) if optMoveCheck in c.owner.options: - result.add callCodegenProc(c.graph, "chckMove", bit) + result.add callCodegenProc(c.graph, "chckMove", bit.info, bit) result.add newTree(nkAsgn, bit, newIntTypeNode(nkIntLit, 0, getSysType(c.graph, n.info, tyBool))) result.add n @@ -289,48 +370,18 @@ proc genMagicCall(n: PNode; c: var Con; magicname: string; m: TMagic): PNode = result.add(newSymNode(createMagic(c.graph, magicname, m))) result.add n -proc moveOrCopy(dest, ri: PNode; c: var Con): PNode = - if ri.kind in constrExprs: - result = genSink(c, dest.typ, dest) - # watch out and no not transform 'ri' twice if it's a call: - let ri2 = copyNode(ri) - recurse(ri, ri2) - result.add ri2 - elif ri.kind == nkSym and isHarmlessVar(ri.sym, c): - # Rule 3: `=sink`(x, z); wasMoved(z) - var snk = genSink(c, dest.typ, dest) - snk.add p(ri, c) - result = newTree(nkStmtList, snk, genMagicCall(ri, c, "wasMoved", mWasMoved)) - elif ri.kind == nkSym and isSinkParam(ri.sym): - result = genSink(c, dest.typ, dest) - result.add destructiveMoveSink(ri, c) - else: - result = genCopy(c, dest.typ, dest) - result.add p(ri, c) - -proc passCopyToSink(n: PNode; c: var Con): PNode = - result = newNodeIT(nkStmtListExpr, n.info, n.typ) - let tmp = getTemp(c, n.typ, n.info) - if hasDestructor(n.typ): - var m = genCopy(c, n.typ, tmp) - m.add p(n, c) - result.add m - message(c.graph.config, n.info, hintPerformance, - "passing '$1' to a sink parameter introduces an implicit copy; " & - "use 'move($1)' to prevent it" % $n) - else: - result.add newTree(nkAsgn, tmp, p(n, c)) - result.add tmp - proc genWasMoved(n: PNode; c: var Con): PNode = # The mWasMoved builtin does not take the address. result = genMagicCall(n, c, "wasMoved", mWasMoved) proc destructiveMoveVar(n: PNode; c: var Con): PNode = # generate: (let tmp = v; reset(v); tmp) + # XXX: Strictly speaking we can only move if there is a ``=sink`` defined + # or if no ``=sink`` is defined and also no assignment. result = newNodeIT(nkStmtListExpr, n.info, n.typ) var temp = newSym(skLet, getIdent(c.graph.cache, "blitTmp"), c.owner, n.info) + temp.typ = n.typ var v = newNodeI(nkLetSection, n.info) let tempAsNode = newSymNode(temp) @@ -344,6 +395,110 @@ proc destructiveMoveVar(n: PNode; c: var Con): PNode = result.add genWasMoved(n, c) result.add tempAsNode +proc passCopyToSink(n: PNode; c: var Con): PNode = + result = newNodeIT(nkStmtListExpr, n.info, n.typ) + let tmp = getTemp(c, n.typ, n.info) + if hasDestructor(n.typ): + var m = genCopy(c, n.typ, tmp, n) + m.add p(n, c) + result.add m + if isLValue(n): + message(c.graph.config, n.info, hintPerformance, + ("passing '$1' to a sink parameter introduces an implicit copy; " & + "use 'move($1)' to prevent it") % $n) + else: + result.add newTree(nkAsgn, tmp, p(n, c)) + result.add tmp + +proc pArg(arg: PNode; c: var Con; isSink: bool): PNode = + if isSink: + if arg.kind in nkCallKinds: + # recurse but skip the call expression in order to prevent + # destructor injections: Rule 5.1 is different from rule 5.4! + result = copyNode(arg) + let parameters = arg[0].typ + let L = if parameters != nil: parameters.len else: 0 + result.add arg[0] + for i in 1..<arg.len: + result.add pArg(arg[i], c, i < L and parameters[i].kind == tySink) + elif arg.kind in {nkObjConstr, nkCharLit..nkFloat128Lit}: + discard "object construction to sink parameter: nothing to do" + result = arg + elif arg.kind == nkSym and arg.sym.kind in InterestingSyms and isLastRead(arg, c): + # if x is a variable and it its last read we eliminate its + # destructor invokation, but don't. We need to reset its memory + # to disable its destructor which we have not elided: + result = destructiveMoveVar(arg, c) + elif arg.kind == nkSym and isSinkParam(arg.sym): + # mark the sink parameter as used: + result = destructiveMoveSink(arg, c) + else: + # an object that is not temporary but passed to a 'sink' parameter + # results in a copy. + result = passCopyToSink(arg, c) + else: + result = p(arg, c) + +proc moveOrCopy(dest, ri: PNode; c: var Con): PNode = + case ri.kind + of nkCallKinds: + result = genSink(c, dest.typ, dest, ri) + # watch out and no not transform 'ri' twice if it's a call: + let ri2 = copyNode(ri) + let parameters = ri[0].typ + let L = if parameters != nil: parameters.len else: 0 + ri2.add ri[0] + for i in 1..<ri.len: + ri2.add pArg(ri[i], c, i < L and parameters[i].kind == tySink) + #recurse(ri, ri2) + result.add ri2 + of nkBracketExpr: + if ri[0].kind == nkSym and isUnpackedTuple(ri[0].sym): + # unpacking of tuple: move out the elements + result = genSink(c, dest.typ, dest, ri) + else: + result = genCopy(c, dest.typ, dest, ri) + result.add p(ri, c) + of nkStmtListExpr: + result = newNodeI(nkStmtList, ri.info) + for i in 0..ri.len-2: + result.add p(ri[i], c) + result.add moveOrCopy(dest, ri[^1], c) + of nkObjConstr: + result = genSink(c, dest.typ, dest, ri) + let ri2 = copyTree(ri) + for i in 1..<ri.len: + # everything that is passed to an object constructor is consumed, + # so these all act like 'sink' parameters: + ri2[i].sons[1] = pArg(ri[i][1], c, isSink = true) + result.add ri2 + of nkTupleConstr: + result = genSink(c, dest.typ, dest, ri) + let ri2 = copyTree(ri) + for i in 0..<ri.len: + # everything that is passed to an tuple constructor is consumed, + # so these all act like 'sink' parameters: + if ri[i].kind == nkExprColonExpr: + ri2[i].sons[1] = pArg(ri[i][1], c, isSink = true) + else: + ri2[i] = pArg(ri[i], c, isSink = true) + result.add ri2 + of nkSym: + if ri.sym.kind != skParam and isLastRead(ri, c): + # Rule 3: `=sink`(x, z); wasMoved(z) + var snk = genSink(c, dest.typ, dest, ri) + snk.add p(ri, c) + result = newTree(nkStmtList, snk, genMagicCall(ri, c, "wasMoved", mWasMoved)) + elif isSinkParam(ri.sym): + result = genSink(c, dest.typ, dest, ri) + result.add destructiveMoveSink(ri, c) + else: + result = genCopy(c, dest.typ, dest, ri) + result.add p(ri, c) + else: + result = genCopy(c, dest.typ, dest, ri) + result.add p(ri, c) + proc p(n: PNode; c: var Con): PNode = case n.kind of nkVarSection, nkLetSection: @@ -357,7 +512,7 @@ proc p(n: PNode; c: var Con): PNode = if it.kind == nkVarTuple and hasDestructor(ri.typ): let x = lowerTupleUnpacking(c.graph, it, c.owner) result.add p(x, c) - elif it.kind == nkIdentDefs and hasDestructor(it[0].typ): + elif it.kind == nkIdentDefs and hasDestructor(it[0].typ) and not isUnpackedTuple(it[0].sym): for j in 0..L-2: let v = it[j] doAssert v.kind == nkSym @@ -381,36 +536,12 @@ proc p(n: PNode; c: var Con): PNode = let parameters = n[0].typ let L = if parameters != nil: parameters.len else: 0 for i in 1 ..< n.len: - let arg = n[i] - if i < L and parameters[i].kind == tySink: - if arg.kind in nkCallKinds: - # recurse but skip the call expression in order to prevent - # destructor injections: Rule 5.1 is different from rule 5.4! - let a = copyNode(arg) - recurse(arg, a) - n.sons[i] = a - elif arg.kind in {nkObjConstr, nkCharLit..nkFloat128Lit}: - discard "object construction to sink parameter: nothing to do" - elif arg.kind == nkSym and isHarmlessVar(arg.sym, c): - # if x is a variable and it its last read we eliminate its - # destructor invokation, but don't. We need to reset its memory - # to disable its destructor which we have not elided: - n.sons[i] = destructiveMoveVar(arg, c) - elif arg.kind == nkSym and isSinkParam(arg.sym): - # mark the sink parameter as used: - n.sons[i] = destructiveMoveSink(arg, c) - else: - # an object that is not temporary but passed to a 'sink' parameter - # results in a copy. - n.sons[i] = passCopyToSink(arg, c) - else: - n.sons[i] = p(arg, c) - + n.sons[i] = pArg(n[i], c, i < L and parameters[i].kind == tySink) if n.typ != nil and hasDestructor(n.typ): discard "produce temp creation" result = newNodeIT(nkStmtListExpr, n.info, n.typ) let tmp = getTemp(c, n.typ, n.info) - var sinkExpr = genSink(c, n.typ, tmp) + var sinkExpr = genSink(c, n.typ, tmp, n) sinkExpr.add n result.add sinkExpr result.add tmp @@ -431,7 +562,7 @@ proc p(n: PNode; c: var Con): PNode = recurse(n, result) proc injectDestructorCalls*(g: ModuleGraph; owner: PSym; n: PNode): PNode = - when defined(nimDebugDestroys): + when false: # defined(nimDebugDestroys): echo "injecting into ", n var c: Con c.owner = owner @@ -449,6 +580,8 @@ proc injectDestructorCalls*(g: ModuleGraph; owner: PSym; n: PNode): PNode = for i in 0..<c.g.len: if c.g[i].kind in {goto, fork}: c.jumpTargets.incl(i+c.g[i].dest) + #if owner.name.s == "test0p1": + # echoCfg(c.g) if owner.kind in {skProc, skFunc, skMethod, skIterator, skConverter}: let params = owner.typ.n for i in 1 ..< params.len: @@ -466,7 +599,7 @@ proc injectDestructorCalls*(g: ModuleGraph; owner: PSym; n: PNode): PNode = result.add body when defined(nimDebugDestroys): - if owner.name.s == "main" or true: + if true: echo "------------------------------------" echo owner.name.s, " transformed to: " echo result diff --git a/compiler/dfa.nim b/compiler/dfa.nim index 013242f62..415cc4ab3 100644 --- a/compiler/dfa.nim +++ b/compiler/dfa.nim @@ -7,18 +7,25 @@ # distribution, for details about the copyright. # -## Data flow analysis for Nim. For now the task is to prove that every -## usage of a local variable 'v' is covered by an initialization to 'v' -## first. +## Data flow analysis for Nim. ## We transform the AST into a linear list of instructions first to ## make this easier to handle: There are only 2 different branching ## instructions: 'goto X' is an unconditional goto, 'fork X' ## is a conditional goto (either the next instruction or 'X' can be -## taken). Exhaustive case statements are translated -## so that the last branch is transformed into an 'else' branch. +## taken). Exhaustive case statements could be translated +## so that the last branch is transformed into an 'else' branch, but +## this is currently not done. ## ``return`` and ``break`` are all covered by 'goto'. -## The case to detect is ``use v`` that is not dominated by -## a ``def v``. +## +## Control flow through exception handling: +## Contrary to popular belief, exception handling doesn't cause +## many problems for this DFA representation, ``raise`` is a statement +## that ``goes to`` the outer ``finally`` or ``except`` if there is one, +## otherwise it is the same as ``return``. Every call is treated as +## a call that can potentially ``raise``. However, without a surrounding +## ``try`` we don't emit these ``fork ReturnLabel`` instructions in order +## to speed up the dataflow analysis passes. +## ## The data structures and algorithms used here are inspired by ## "A Graph–Free Approach to Data–Flow Analysis" by Markus Mohnen. ## https://link.springer.com/content/pdf/10.1007/3-540-45937-5_6.pdf @@ -27,15 +34,11 @@ import ast, astalgo, types, intsets, tables, msgs, options, lineinfos type InstrKind* = enum - goto, fork, def, use, - useWithinCall # this strange special case is used to get more - # move optimizations out of regular code - # XXX This is still overly pessimistic in - # call(let x = foo; bar(x)) + goto, fork, def, use Instr* = object n*: PNode case kind*: InstrKind - of def, use, useWithinCall: sym*: PSym + of def, use: sym*: PSym of goto, fork: dest*: int ControlFlowGraph* = seq[Instr] @@ -50,8 +53,10 @@ type Con = object code: ControlFlowGraph - inCall: int + inCall, inTryStmt: int blocks: seq[TBlock] + tryStmtFixups: seq[TPosition] + owner: PSym proc debugInfo(info: TLineInfo): string = result = $info.line #info.toFilename & ":" & $info.line @@ -71,7 +76,7 @@ proc codeListing(c: ControlFlowGraph, result: var string, start=0; last = -1) = result.add $c[i].kind result.add "\t" case c[i].kind - of def, use, useWithinCall: + of def, use: result.add c[i].sym.name.s of goto, fork: result.add "L" @@ -199,6 +204,13 @@ proc genCase(c: var Con; n: PNode) = # L2: # elsePart # Lend: + when false: + # XXX Exhaustiveness is not yet mapped to the control flow graph as + # it seems to offer no benefits for the 'last read of' question. + let isExhaustive = skipTypes(n.sons[0].typ, + abstractVarRange-{tyTypeDesc}).kind in {tyFloat..tyFloat128, tyString} or + lastSon(n).kind == nkElse + var endings: seq[TPosition] = @[] c.gen(n.sons[0]) for i in 1 ..< n.len: @@ -215,8 +227,17 @@ proc genCase(c: var Con; n: PNode) = proc genTry(c: var Con; n: PNode) = var endings: seq[TPosition] = @[] + inc c.inTryStmt + var newFixups: seq[TPosition] + swap(newFixups, c.tryStmtFixups) + let elsePos = c.forkI(n) c.gen(n.sons[0]) + dec c.inTryStmt + for f in newFixups: + c.patch(f) + swap(newFixups, c.tryStmtFixups) + c.patch(elsePos) for i in 1 ..< n.len: let it = n.sons[i] @@ -234,10 +255,20 @@ proc genTry(c: var Con; n: PNode) = proc genRaise(c: var Con; n: PNode) = gen(c, n.sons[0]) - c.code.add Instr(n: n, kind: goto, dest: high(int) - c.code.len) + if c.inTryStmt > 0: + c.tryStmtFixups.add c.gotoI(n) + else: + c.code.add Instr(n: n, kind: goto, dest: high(int) - c.code.len) + +proc genImplicitReturn(c: var Con) = + if c.owner.kind in {skProc, skFunc, skMethod, skIterator, skConverter} and resultPos < c.owner.ast.len: + gen(c, c.owner.ast.sons[resultPos]) proc genReturn(c: var Con; n: PNode) = - if n.sons[0].kind != nkEmpty: gen(c, n.sons[0]) + if n.sons[0].kind != nkEmpty: + gen(c, n.sons[0]) + else: + genImplicitReturn(c) c.code.add Instr(n: n, kind: goto, dest: high(int) - c.code.len) const @@ -250,10 +281,7 @@ proc genUse(c: var Con; n: PNode) = nkAddr, nkHiddenAddr}: n = n[0] if n.kind == nkSym and n.sym.kind in InterestingSyms: - if c.inCall > 0: - c.code.add Instr(n: n, kind: useWithinCall, sym: n.sym) - else: - c.code.add Instr(n: n, kind: use, sym: n.sym) + c.code.add Instr(n: n, kind: use, sym: n.sym) proc genDef(c: var Con; n: PNode) = if n.kind == nkSym and n.sym.kind in InterestingSyms: @@ -268,6 +296,9 @@ proc genCall(c: var Con; n: PNode) = gen(c, n[i]) if t != nil and i < t.len and t.sons[i].kind == tyVar: genDef(c, n[i]) + # every call can potentially raise: + if c.inTryStmt > 0: + c.tryStmtFixups.add c.forkI(n) dec c.inCall proc genMagic(c: var Con; n: PNode; m: TMagic) = @@ -333,6 +364,8 @@ proc gen(c: var Con; n: PNode) = gen(c, n.sons[1]) of nkObjDownConv, nkStringToCString, nkCStringToString: gen(c, n.sons[0]) of nkVarSection, nkLetSection: genVarSection(c, n) + of nkDefer: + doAssert false, "dfa construction pass requires the elimination of 'defer'" else: discard proc dfa(code: seq[Instr]; conf: ConfigRef) = @@ -345,7 +378,7 @@ proc dfa(code: seq[Instr]; conf: ConfigRef) = d[i] = initIntSet() c[i] = initIntSet() case code[i].kind - of use, useWithinCall: u[i].incl(code[i].sym.id) + of use: u[i].incl(code[i].sym.id) of def: d[i].incl(code[i].sym.id) of fork, goto: let d = i+code[i].dest @@ -407,7 +440,7 @@ proc dfa(code: seq[Instr]; conf: ConfigRef) = #if someChange: w.add pc + code[pc].dest inc pc - of use, useWithinCall: + of use: #if not d[prevPc].missingOrExcl(): # someChange = true consuming = code[pc].sym.id @@ -424,7 +457,7 @@ proc dfa(code: seq[Instr]; conf: ConfigRef) = # now check the condition we're interested in: for i in 0..<code.len: case code[i].kind - of use, useWithinCall: + of use: let s = code[i].sym if s.id notin d[i]: localError(conf, code[i].n.info, "usage of uninitialized variable: " & s.name.s) @@ -436,10 +469,13 @@ proc dfa(code: seq[Instr]; conf: ConfigRef) = proc dataflowAnalysis*(s: PSym; body: PNode; conf: ConfigRef) = var c = Con(code: @[], blocks: @[]) gen(c, body) + genImplicitReturn(c) when defined(useDfa) and defined(debugDfa): echoCfg(c.code) dfa(c.code, conf) proc constructCfg*(s: PSym; body: PNode): ControlFlowGraph = ## constructs a control flow graph for ``body``. - var c = Con(code: @[], blocks: @[]) + var c = Con(code: @[], blocks: @[], owner: s) + gen(c, body) + genImplicitReturn(c) shallowCopy(result, c.code) diff --git a/compiler/docgen.nim b/compiler/docgen.nim index 4e36b72e5..67f4108e1 100644 --- a/compiler/docgen.nim +++ b/compiler/docgen.nim @@ -112,7 +112,7 @@ proc getOutFile2(conf: ConfigRef; filename: RelativeFile, else: result = getOutFile(conf, filename, ext) -proc newDocumentor*(filename: AbsoluteFile; cache: IdentCache; conf: ConfigRef): PDoc = +proc newDocumentor*(filename: AbsoluteFile; cache: IdentCache; conf: ConfigRef, outExt: string = HtmlExt): PDoc = declareClosures() new(result) result.conf = conf @@ -141,12 +141,33 @@ proc newDocumentor*(filename: AbsoluteFile; cache: IdentCache; conf: ConfigRef): result.id = 100 result.jArray = newJArray() initStrTable result.types - result.onTestSnippet = proc (d: var RstGenerator; filename, cmd: string; status: int; content: string) = - localError(conf, newLineInfo(conf, AbsoluteFile d.filename, -1, -1), - warnUser, "only 'rst2html' supports the ':test:' attribute") + result.onTestSnippet = + proc (gen: var RstGenerator; filename, cmd: string; status: int; content: string) = + var d = TDocumentor(gen) + var outp: AbsoluteFile + if filename.len == 0: + inc(d.id) + let nameOnly = splitFile(d.filename).name + let subdir = getNimcacheDir(conf) / RelativeDir(nameOnly) + createDir(subdir) + outp = subdir / RelativeFile(nameOnly & "_snippet_" & $d.id & ".nim") + elif isAbsolute(filename): + outp = AbsoluteFile filename + else: + # Nim's convention: every path is relative to the file it was written in: + outp = splitFile(d.filename).dir.AbsoluteDir / RelativeFile(filename) + # Include the current file if we're parsing a nim file + let importStmt = if d.isPureRst: "" else: "import \"$1\"\n" % [d.filename] + writeFile(outp, importStmt & content) + let c = if cmd.startsWith("nim "): os.getAppFilename() & cmd.substr(3) + else: cmd + let c2 = c % quoteShell(outp) + rawMessage(conf, hintExecuting, c2) + if execShellCmd(c2) != status: + rawMessage(conf, errGenerated, "executing of external program failed: " & c2) result.emitted = initIntSet() result.destFile = getOutFile2(conf, relativeTo(filename, conf.projectPath), - HtmlExt, RelativeDir"htmldocs", false) + outExt, RelativeDir"htmldocs", false) result.thisDir = result.destFile.splitFile.dir proc dispA(conf: ConfigRef; dest: var Rope, xml, tex: string, args: openArray[Rope]) = @@ -226,18 +247,30 @@ proc genComment(d: PDoc, n: PNode): string = toLinenumber(n.info), toColumn(n.info), dummyHasToc, d.options, d.conf), result) -proc genRecComment(d: PDoc, n: PNode): Rope = +proc genRecCommentAux(d: PDoc, n: PNode): Rope = if n == nil: return nil result = genComment(d, n).rope if result == nil: - if n.kind notin {nkEmpty..nkNilLit, nkEnumTy, nkTupleTy}: + if n.kind in {nkStmtList, nkStmtListExpr, nkTypeDef, nkConstDef, + nkObjectTy, nkRefTy, nkPtrTy, nkAsgn, nkFastAsgn}: + # notin {nkEmpty..nkNilLit, nkEnumTy, nkTupleTy}: for i in countup(0, len(n)-1): - result = genRecComment(d, n.sons[i]) + result = genRecCommentAux(d, n.sons[i]) if result != nil: return else: when defined(nimNoNilSeqs): n.comment = "" else: n.comment = nil +proc genRecComment(d: PDoc, n: PNode): Rope = + if n == nil: return nil + result = genComment(d, n).rope + if result == nil: + if n.kind in {nkProcDef, nkFuncDef, nkMethodDef, nkIteratorDef, + nkMacroDef, nkTemplateDef, nkConverterDef}: + result = genRecCommentAux(d, n[bodyPos]) + else: + result = genRecCommentAux(d, n) + proc getPlainDocstring(n: PNode): string = ## Gets the plain text docstring of a node non destructively. ## @@ -367,7 +400,13 @@ proc extractImports(n: PNode; result: PNode) = for i in 0..<n.safeLen: extractImports(n[i], result) proc prepareExamples(d: PDoc; n: PNode) = + + var docComment = newTree(nkCommentStmt) + let loc = d.conf.toFileLineCol(n.info) + docComment.comment = "autogenerated by docgen from " & loc + var runnableExamples = newTree(nkStmtList, + docComment, newTree(nkImportStmt, newStrNode(nkStrLit, d.filename))) runnableExamples.info = n.info let imports = newTree(nkStmtList) @@ -377,16 +416,11 @@ proc prepareExamples(d: PDoc; n: PNode) = runnableExamples.add newTree(nkBlockStmt, newNode(nkEmpty), copyTree savedLastSon) testExample(d, runnableExamples) -proc isRunnableExample(n: PNode): bool = - # Templates and generics don't perform symbol lookups. - result = n.kind == nkSym and n.sym.magic == mRunnableExamples or - n.kind == nkIdent and n.ident.s == "runnableExamples" - proc getAllRunnableExamplesRec(d: PDoc; n, orig: PNode; dest: var Rope) = if n.info.fileIndex != orig.info.fileIndex: return case n.kind of nkCallKinds: - if isRunnableExample(n[0]) and + if isRunnableExamples(n[0]) and n.len >= 2 and n.lastSon.kind == nkStmtList: prepareExamples(d, n) dispA(d.conf, dest, "\n<p><strong class=\"examples_text\">$1</strong></p>\n", @@ -413,27 +447,6 @@ proc getAllRunnableExamplesRec(d: PDoc; n, orig: PNode; dest: var Rope) = proc getAllRunnableExamples(d: PDoc; n: PNode; dest: var Rope) = getAllRunnableExamplesRec(d, n, n, dest) -when false: - proc findDocComment(n: PNode): PNode = - if n == nil: return nil - if not isNil(n.comment) and startsWith(n.comment, "##"): return n - for i in countup(0, safeLen(n)-1): - result = findDocComment(n.sons[i]) - if result != nil: return - - proc extractDocComment*(s: PSym, d: PDoc): string = - let n = findDocComment(s.ast) - result = "" - if not n.isNil: - if not d.isNil: - var dummyHasToc: bool - renderRstToOut(d[], parseRst(n.comment, toFilename(d.conf, n.info), - toLinenumber(n.info), toColumn(n.info), - dummyHasToc, d.options + {roSkipPounds}), - result) - else: - result = n.comment.substr(2).replace("\n##", "\n").strip - proc isVisible(d: PDoc; n: PNode): bool = result = false if n.kind == nkPostfix: @@ -610,7 +623,7 @@ proc genItem(d: PDoc, n, nameNode: PNode, k: TSymKind) = var seeSrcRope: Rope = nil let docItemSeeSrc = getConfigVar(d.conf, "doc.item.seesrc") if docItemSeeSrc.len > 0: - let path = relativeTo(AbsoluteFile toFullPath(d.conf, n.info), d.conf.projectPath, '/') + let path = relativeTo(AbsoluteFile toFullPath(d.conf, n.info), AbsoluteDir getCurrentDir(), '/') when false: let cwd = canonicalizePath(d.conf, getCurrentDir()) var path = toFullPath(d.conf, n.info) @@ -711,7 +724,7 @@ proc exportSym(d: PDoc; s: PSym) = "<a class=\"reference external\" href=\"$2\">$1</a>", "$1", [rope esc(d.target, changeFileExt(external, "")), rope changeFileExt(external, "html")]) - elif s.owner != nil: + elif s.kind != skModule and s.owner != nil: let module = originatingModule(s) if belongsToPackage(d.conf, module): let external = externalDep(d, module) @@ -723,7 +736,6 @@ proc exportSym(d: PDoc; s: PSym) = rope changeFileExt(external, "html")]) proc generateDoc*(d: PDoc, n, orig: PNode) = - if orig.info.fileIndex != n.info.fileIndex: return case n.kind of nkCommentStmt: add(d.modDesc, genComment(d, n)) of nkProcDef: @@ -765,19 +777,19 @@ proc generateDoc*(d: PDoc, n, orig: PNode) = of nkCallKinds: var comm: Rope = nil getAllRunnableExamples(d, n, comm) - if comm > nil: add(d.modDesc, comm) + if comm != nil: add(d.modDesc, comm) else: discard proc add(d: PDoc; j: JsonNode) = if j != nil: d.jArray.add j -proc generateJson*(d: PDoc, n: PNode) = +proc generateJson*(d: PDoc, n: PNode, includeComments: bool = true) = case n.kind of nkCommentStmt: - if startsWith(n.comment, "##"): - let stripped = n.comment.substr(2).strip - d.add %{ "comment": %stripped, "line": %n.info.line.int, - "col": %n.info.col } + if includeComments: + d.add %*{"comment": genComment(d, n)} + else: + add(d.modDesc, genComment(d, n)) of nkProcDef: when useEffectSystem: documentRaises(d.cache, n) d.add genJsonItem(d, n, n.sons[namePos], skProc) @@ -805,11 +817,11 @@ proc generateJson*(d: PDoc, n: PNode) = succ(skType, ord(n.kind)-ord(nkTypeSection))) of nkStmtList: for i in countup(0, sonsLen(n) - 1): - generateJson(d, n.sons[i]) + generateJson(d, n.sons[i], includeComments) of nkWhenStmt: # generate documentation for the first branch only: if not checkForFalse(n.sons[0].sons[0]): - generateJson(d, lastSon(n.sons[0])) + generateJson(d, lastSon(n.sons[0]), includeComments) else: discard proc genTagsItem(d: PDoc, n, nameNode: PNode, k: TSymKind): string = @@ -891,7 +903,7 @@ proc genOutFile(d: PDoc): Rope = setIndexTerm(d[], external, "", title) else: # Modules get an automatic title for the HTML, but no entry in the index. - title = "Module " & extractFilename(changeFileExt(d.filename, "")) + title = extractFilename(changeFileExt(d.filename, "")) let bodyname = if d.hasToc and not d.isPureRst: "doc.body_toc_group" elif d.hasToc: "doc.body_toc" @@ -935,8 +947,12 @@ proc writeOutput*(d: PDoc, useWarning = false) = outfile.string) proc writeOutputJson*(d: PDoc, useWarning = false) = + var modDesc: string + for desc in d.modDesc: + modDesc &= desc let content = %*{"orig": d.filename, "nimble": getPackageName(d.conf, d.filename), + "moduleDescription": modDesc, "entries": d.jArray} if optStdout in d.conf.globalOptions: write(stdout, $content) @@ -946,7 +962,9 @@ proc writeOutputJson*(d: PDoc, useWarning = false) = write(f, $content) close(f) else: - discard "fixme: error report" + localError(d.conf, newLineInfo(d.conf, AbsoluteFile d.filename, -1, -1), + warnUser, "unable to open file \"" & d.destFile.string & + "\" for writing") proc commandDoc*(cache: IdentCache, conf: ConfigRef) = var ast = parseFile(conf.projectMainIdx, cache, conf) @@ -960,28 +978,7 @@ proc commandDoc*(cache: IdentCache, conf: ConfigRef) = proc commandRstAux(cache: IdentCache, conf: ConfigRef; filename: AbsoluteFile, outExt: string) = var filen = addFileExt(filename, "txt") - var d = newDocumentor(filen, cache, conf) - d.onTestSnippet = proc (d: var RstGenerator; filename, cmd: string; - status: int; content: string) = - var outp: AbsoluteFile - if filename.len == 0: - inc(d.id) - let nameOnly = splitFile(d.filename).name - let subdir = getNimcacheDir(conf) / RelativeDir(nameOnly) - createDir(subdir) - outp = subdir / RelativeFile(nameOnly & "_snippet_" & $d.id & ".nim") - elif isAbsolute(filename): - outp = AbsoluteFile filename - else: - # Nim's convention: every path is relative to the file it was written in: - outp = splitFile(d.filename).dir.AbsoluteDir / RelativeFile(filename) - writeFile(outp, content) - let c = if cmd.startsWith("nim "): os.getAppFilename() & cmd.substr(3) - else: cmd - let c2 = c % quoteShell(outp) - rawMessage(conf, hintExecuting, c2) - if execShellCmd(c2) != status: - rawMessage(conf, errGenerated, "executing of external program failed: " & c2) + var d = newDocumentor(filen, cache, conf, outExt) d.isPureRst = true var rst = parseRst(readFile(filen.string), filen.string, 0, 1, d.hasToc, @@ -1002,6 +999,10 @@ proc commandJson*(cache: IdentCache, conf: ConfigRef) = var ast = parseFile(conf.projectMainIdx, cache, conf) if ast == nil: return var d = newDocumentor(conf.projectFull, cache, conf) + d.onTestSnippet = proc (d: var RstGenerator; filename, cmd: string; + status: int; content: string) = + localError(conf, newLineInfo(conf, AbsoluteFile d.filename, -1, -1), + warnUser, "the ':test:' attribute is not supported by this backend") d.hasToc = true generateJson(d, ast) let json = d.jArray @@ -1019,6 +1020,10 @@ proc commandTags*(cache: IdentCache, conf: ConfigRef) = var ast = parseFile(conf.projectMainIdx, cache, conf) if ast == nil: return var d = newDocumentor(conf.projectFull, cache, conf) + d.onTestSnippet = proc (d: var RstGenerator; filename, cmd: string; + status: int; content: string) = + localError(conf, newLineInfo(conf, AbsoluteFile d.filename, -1, -1), + warnUser, "the ':test:' attribute is not supported by this backend") d.hasToc = true var content: Rope diff --git a/compiler/docgen2.nim b/compiler/docgen2.nim index 01acdd4ca..f2ac6c1a9 100644 --- a/compiler/docgen2.nim +++ b/compiler/docgen2.nim @@ -14,10 +14,10 @@ import os, options, ast, astalgo, msgs, ropes, idents, passes, docgen, lineinfos, pathutils -from modulegraphs import ModuleGraph +from modulegraphs import ModuleGraph, PPassContext type - TGen = object of TPassContext + TGen = object of PPassContext doc: PDoc module: PSym PGen = ref TGen @@ -55,20 +55,26 @@ proc processNodeJson(c: PPassContext, n: PNode): PNode = result = n var g = PGen(c) if shouldProcess(g): - generateJson(g.doc, n) + generateJson(g.doc, n, false) -proc myOpen(graph: ModuleGraph; module: PSym): PPassContext = +template myOpenImpl(ext: untyped) {.dirty.} = var g: PGen new(g) g.module = module var d = newDocumentor(AbsoluteFile toFullPath(graph.config, FileIndex module.position), - graph.cache, graph.config) + graph.cache, graph.config, ext) d.hasToc = true g.doc = d result = g +proc myOpen(graph: ModuleGraph; module: PSym): PPassContext = + myOpenImpl(HtmlExt) + +proc myOpenJson(graph: ModuleGraph; module: PSym): PPassContext = + myOpenImpl(JsonExt) + const docgen2Pass* = makePass(open = myOpen, process = processNode, close = close) -const docgen2JsonPass* = makePass(open = myOpen, process = processNodeJson, +const docgen2JsonPass* = makePass(open = myOpenJson, process = processNodeJson, close = closeJson) proc finishDoc2Pass*(project: string) = diff --git a/compiler/evaltempl.nim b/compiler/evaltempl.nim index d6c630e79..09a1cd436 100644 --- a/compiler/evaltempl.nim +++ b/compiler/evaltempl.nim @@ -17,6 +17,7 @@ type TemplCtx = object owner, genSymOwner: PSym instLines: bool # use the instantiation lines numbers + isDeclarative: bool mapping: TIdTable # every gensym'ed symbol needs to be mapped to some # new symbol config: ConfigRef @@ -46,7 +47,7 @@ proc evalTemplateAux(templ, actual: PNode, c: var TemplCtx, result: PNode) = internalAssert c.config, sfGenSym in s.flags or s.kind == skType var x = PSym(idTableGet(c.mapping, s)) if x == nil: - x = copySym(s, false) + x = copySym(s) x.owner = c.genSymOwner idTablePut(c.mapping, s, x) result.add newSymNode(x, if c.instLines: actual.info else: templ.info) @@ -54,11 +55,30 @@ proc evalTemplateAux(templ, actual: PNode, c: var TemplCtx, result: PNode) = result.add copyNode(c, templ, actual) of nkNone..nkIdent, nkType..nkNilLit: # atom result.add copyNode(c, templ, actual) + of nkCommentStmt: + # for the documentation generator we don't keep documentation comments + # in the AST that would confuse it (bug #9432), but only if we are not in a + # "declarative" context (bug #9235). + if c.isDeclarative: + var res = copyNode(c, templ, actual) + for i in countup(0, sonsLen(templ) - 1): + evalTemplateAux(templ.sons[i], actual, c, res) + result.add res + else: + result.add newNodeI(nkEmpty, templ.info) else: + var isDeclarative = false + if templ.kind in {nkProcDef, nkFuncDef, nkMethodDef, nkIteratorDef, + nkMacroDef, nkTemplateDef, nkConverterDef, nkTypeSection, + nkVarSection, nkLetSection, nkConstSection} and + not c.isDeclarative: + c.isDeclarative = true + isDeclarative = true var res = copyNode(c, templ, actual) for i in countup(0, sonsLen(templ) - 1): evalTemplateAux(templ.sons[i], actual, c, res) result.add res + if isDeclarative: c.isDeclarative = false const errWrongNumberOfArguments = "wrong number of arguments" diff --git a/compiler/extccomp.nim b/compiler/extccomp.nim index 69698ae09..f3f74ece8 100644 --- a/compiler/extccomp.nim +++ b/compiler/extccomp.nim @@ -627,7 +627,8 @@ proc externalFileChanged(conf: ConfigRef; cfile: Cfile): bool = close(f) proc addExternalFileToCompile*(conf: ConfigRef; c: var Cfile) = - if optForceFullMake notin conf.globalOptions and not externalFileChanged(conf, c): + if optForceFullMake notin conf.globalOptions and fileExists(c.obj) and + not externalFileChanged(conf, c): c.flags.incl CfileFlag.Cached conf.toCompile.add(c) diff --git a/compiler/guards.nim b/compiler/guards.nim index 99bb51fce..a01c023e4 100644 --- a/compiler/guards.nim +++ b/compiler/guards.nim @@ -70,7 +70,7 @@ proc isLetLocation(m: PNode, isApprox: bool): bool = n = n.sons[0] inc derefs of nkBracketExpr: - if isConstExpr(n.sons[1]) or isLet(n.sons[1]): + if isConstExpr(n.sons[1]) or isLet(n.sons[1]) or isConstExpr(n.sons[1].skipConv): n = n.sons[0] else: return of nkHiddenStdConv, nkHiddenSubConv, nkConv: diff --git a/compiler/importer.nim b/compiler/importer.nim index 60b7872fe..131b1ad8a 100644 --- a/compiler/importer.nim +++ b/compiler/importer.nim @@ -100,7 +100,7 @@ proc importAllSymbolsExcept(c: PContext, fromMod: PSym, exceptSet: IntSet) = if s.kind != skModule: if s.kind != skEnumField: if s.kind notin ExportableSymKinds: - internalError(c.config, s.info, "importAllSymbols: " & $s.kind) + internalError(c.config, s.info, "importAllSymbols: " & $s.kind & " " & s.name.s) if exceptSet.isNil or s.name.id notin exceptSet: rawImportSymbol(c, s) s = nextIter(i, fromMod.tab) diff --git a/compiler/incremental.nim b/compiler/incremental.nim index 2008d35de..f66a75efd 100644 --- a/compiler/incremental.nim +++ b/compiler/incremental.nim @@ -12,7 +12,7 @@ const nimIncremental* = defined(nimIncremental) -import options, lineinfos +import options, lineinfos, pathutils when nimIncremental: import ast, msgs, intsets, btrees, db_sqlite, std / sha1 @@ -45,10 +45,10 @@ when nimIncremental: incr.r.types = initBTree[int, PType]() - proc hashFileCached*(conf: ConfigRef; fileIdx: FileIndex; fullpath: string): string = + proc hashFileCached*(conf: ConfigRef; fileIdx: FileIndex; fullpath: AbsoluteFile): string = result = msgs.getHash(conf, fileIdx) if result.len == 0: - result = $secureHashFile(fullpath) + result = $secureHashFile(string fullpath) msgs.setHash(conf, fileIdx, result) proc toDbFileId*(incr: var IncrementalCtx; conf: ConfigRef; fileIdx: FileIndex): int = @@ -57,10 +57,10 @@ when nimIncremental: let row = incr.db.getRow(sql"select id, fullhash from filenames where fullpath = ?", fullpath) let id = row[0] - let fullhash = hashFileCached(conf, fileIdx, fullpath) + let fullhash = hashFileCached(conf, fileIdx, AbsoluteFile fullpath) if id.len == 0: - result = int incr.db.insertID(sql"insert into filenames(fullpath, fullhash) values (?, ?)", - fullpath, fullhash) + result = int incr.db.insertID(sql"insert into filenames(nimid, fullpath, fullhash) values (?, ?, ?)", + int(fileIdx), fullpath, fullhash) else: if row[1] != fullhash: incr.db.exec(sql"update filenames set fullhash = ? where fullpath = ?", fullhash, fullpath) @@ -70,7 +70,7 @@ when nimIncremental: if dbId == -1: return FileIndex(-1) let fullpath = incr.db.getValue(sql"select fullpath from filenames where id = ?", dbId) doAssert fullpath.len > 0, "cannot find file name for DB ID " & $dbId - result = fileInfoIdx(conf, fullpath) + result = fileInfoIdx(conf, AbsoluteFile fullpath) proc addModuleDep*(incr: var IncrementalCtx; conf: ConfigRef; @@ -102,6 +102,7 @@ when nimIncremental: db.exec(sql""" create table if not exists filenames( id integer primary key, + nimid integer not null, fullpath varchar(8000) not null, fullHash varchar(256) not null ); diff --git a/compiler/installer.ini b/compiler/installer.ini index 212ec438b..63790d90f 100644 --- a/compiler/installer.ini +++ b/compiler/installer.ini @@ -49,11 +49,8 @@ Start: "doc/html/overview.html" [Other] -Files: "readme.txt;copying.txt;install.txt" -Files: "makefile" +Files: "copying.txt" Files: "koch.nim" -Files: "install_nimble.nims" -Files: "install_tools.nims" Files: "icons/nim.ico" Files: "icons/nim.rc" @@ -69,16 +66,9 @@ Files: "doc" Files: "doc/html" Files: "tools" Files: "nimpretty" +Files: "testament" Files: "nimsuggest" Files: "nimsuggest/tests/*.nim" -Files: "web/website.ini" -Files: "web/ticker.html" -Files: "web/*.nim" -Files: "web/*.rst" -Files: "web/*.csv" -Files: "web/news/*.rst" -Files: "bin/nimblepkg/*.nim" -Files: "bin/nimblepkg/*.cfg" [Lib] Files: "lib" @@ -86,13 +76,11 @@ Files: "lib" [Other] Files: "examples" Files: "dist/nimble" -Files: "dist/nimsuggest" Files: "tests" [Windows] Files: "bin/nim.exe" -Files: "bin/c2nim.exe" Files: "bin/nimgrep.exe" Files: "bin/nimsuggest.exe" Files: "bin/nimble.exe" diff --git a/compiler/jsgen.nim b/compiler/jsgen.nim index aa2386526..a9813f5c5 100644 --- a/compiler/jsgen.nim +++ b/compiler/jsgen.nim @@ -32,12 +32,12 @@ import ast, astalgo, strutils, hashes, trees, platform, magicsys, extccomp, options, nversion, nimsets, msgs, std / sha1, bitsets, idents, types, os, tables, times, ropes, math, passes, ccgutils, wordrecg, renderer, - intsets, cgmeth, lowerings, sighashes, lineinfos, rodutils, pathutils + intsets, cgmeth, lowerings, sighashes, lineinfos, rodutils, pathutils, transf -from modulegraphs import ModuleGraph +from modulegraphs import ModuleGraph, PPassContext type - TJSGen = object of TPassContext + TJSGen = object of PPassContext module: PSym graph: ModuleGraph config: ConfigRef @@ -66,6 +66,12 @@ type res: Rope # result part; index if this is an # (address, index)-tuple address: Rope # address of an (address, index)-tuple + tmpLoc: Rope # tmp var which stores the (address, index) + # pair to prevent multiple evals. + # the tmp is initialized upon evaling the + # address. + # might be nil. + # (see `maybeMakeTemp`) TBlock = object id: int # the ID of the label; positive means that it @@ -131,16 +137,15 @@ proc newGlobals(): PGlobals = proc initCompRes(r: var TCompRes) = r.address = nil r.res = nil + r.tmpLoc = nil r.typ = etyNone r.kind = resNone proc rdLoc(a: TCompRes): Rope {.inline.} = - result = a.res - when false: - if a.typ != etyBaseIndex: - result = a.res - else: - result = "$1[$2]" % [a.address, a.res] + if a.typ != etyBaseIndex: + result = a.res + else: + result = "$1[$2]" % [a.address, a.res] proc newProc(globals: PGlobals, module: BModule, procDef: PNode, options: TOptions): PProc = @@ -179,7 +184,7 @@ proc mapType(typ: PType): TJSTypeKind = of tyFloat..tyFloat128: result = etyFloat of tySet: result = etyObject # map a set to a table of tyString, tySequence, tyOpt: result = etySeq - of tyObject, tyArray, tyTuple, tyOpenArray, tyVarargs: + of tyObject, tyArray, tyTuple, tyOpenArray, tyVarargs, tyUncheckedArray: result = etyObject of tyNil: result = etyNull of tyGenericParam, tyGenericBody, tyGenericInvocation, @@ -195,7 +200,7 @@ proc mapType(typ: PType): TJSTypeKind = else: result = etyNone of tyProc: result = etyProc of tyCString: result = etyString - of tyUnused, tyOptAsRef: doAssert(false, "mapType") + of tyOptAsRef: doAssert(false, "mapType") proc mapType(p: PProc; typ: PType): TJSTypeKind = result = mapType(typ) @@ -447,12 +452,48 @@ const # magic checked op; magic unchecked op; checked op; unchecked op ["cstrToNimstr", "cstrToNimstr", "cstrToNimstr($1)", "cstrToNimstr($1)"], ["", "", "$1", "$1"]] +proc needsTemp(p: PProc; n: PNode): bool = + # check if n contains a call to determine + # if a temp should be made to prevent multiple evals + if n.kind in nkCallKinds + {nkTupleConstr, nkObjConstr, nkBracket, nkCurly}: + return true + for c in n: + if needsTemp(p, c): + return true + +proc maybeMakeTemp(p: PProc, n: PNode; x: TCompRes): tuple[a, tmp: Rope] = + var + a = x.rdLoc + b = a + if needsTemp(p, n): + # if we have tmp just use it + if x.tmpLoc != nil and (mapType(n.typ) == etyBaseIndex or n.kind in {nkHiddenDeref, nkDerefExpr}): + b = "$1[0][$1[1]]" % [x.tmpLoc] + (a: a, tmp: b) + else: + let tmp = p.getTemp + b = tmp + a = "($1 = $2, $1)" % [tmp, a] + (a: a, tmp: b) + else: + (a: a, tmp: b) + proc binaryExpr(p: PProc, n: PNode, r: var TCompRes, magic, frmt: string) = + # $1 and $2 in the `frmt` string bind to lhs and rhs of the expr, + # if $3 or $4 are present they will be substituted with temps for + # lhs and rhs respectively var x, y: TCompRes useMagic(p, magic) gen(p, n.sons[1], x) gen(p, n.sons[2], y) - r.res = frmt % [x.rdLoc, y.rdLoc] + + var + a, tmp = x.rdLoc + b, tmp2 = y.rdLoc + if "$3" in frmt: (a, tmp) = maybeMakeTemp(p, n[1], x) + if "$4" in frmt: (a, tmp) = maybeMakeTemp(p, n[1], x) + + r.res = frmt % [a, b, tmp, tmp2] r.kind = resExpr proc unsignedTrimmerJS(size: BiggestInt): Rope = @@ -473,7 +514,8 @@ proc binaryUintExpr(p: PProc, n: PNode, r: var TCompRes, op: string, gen(p, n.sons[2], y) let trimmer = unsignedTrimmer(n[1].typ.skipTypes(abstractRange).size) if reassign: - r.res = "$1 = (($1 $2 $3) $4)" % [x.rdLoc, rope op, y.rdLoc, trimmer] + let (a, tmp) = maybeMakeTemp(p, n[1], x) + r.res = "$1 = (($5 $2 $3) $4)" % [a, rope op, y.rdLoc, trimmer, tmp] else: r.res = "(($1 $2 $3) $4)" % [x.rdLoc, rope op, y.rdLoc, trimmer] @@ -487,9 +529,12 @@ proc ternaryExpr(p: PProc, n: PNode, r: var TCompRes, magic, frmt: string) = r.kind = resExpr proc unaryExpr(p: PProc, n: PNode, r: var TCompRes, magic, frmt: string) = + # $1 binds to n[1], if $2 is present it will be substituted to a tmp of $1 useMagic(p, magic) gen(p, n.sons[1], r) - r.res = frmt % [r.rdLoc] + var a, tmp = r.rdLoc + if "$2" in frmt: (a, tmp) = maybeMakeTemp(p, n[1], r) + r.res = frmt % [a, tmp] r.kind = resExpr proc arithAux(p: PProc, n: PNode, r: var TCompRes, op: TMagic) = @@ -524,6 +569,14 @@ proc arith(p: PProc, n: PNode, r: var TCompRes, op: TMagic) = of mCharToStr, mBoolToStr, mIntToStr, mInt64ToStr, mFloatToStr, mCStrToStr, mStrToStr, mEnumToStr: arithAux(p, n, r, op) + of mEqRef, mEqUntracedRef: + if mapType(n[1].typ) != etyBaseIndex: + arithAux(p, n, r, op) + else: + var x, y: TCompRes + gen(p, n[1], x) + gen(p, n[2], y) + r.res = "($# == $# && $# == $#)" % [x.address, y.address, x.res, y.res] else: arithAux(p, n, r, op) r.kind = resExpr @@ -615,7 +668,6 @@ proc genTry(p: PProc, n: PNode, r: var TCompRes) = gen(p, n.sons[0], a) moveInto(p, a, r) var generalCatchBranchExists = false - let dollar = rope("") if catchBranchesExist: addf(p.body, "--excHandler;$n} catch (EXC) {$n var prevJSError = lastJSError;$n" & " lastJSError = EXC;$n --excHandler;$n", []) @@ -631,15 +683,42 @@ proc genTry(p: PProc, n: PNode, r: var TCompRes) = if i > 1: lineF(p, "}$n", []) else: var orExpr: Rope = nil + var excAlias: PNode = nil + useMagic(p, "isObj") for j in countup(0, blen - 2): - if n.sons[i].sons[j].kind != nkType: + var throwObj: PNode + let it = n.sons[i].sons[j] + + if it.isInfixAs(): + throwObj = it[1] + excAlias = it[2] + # If this is a ``except exc as sym`` branch there must be no following + # nodes + doAssert orExpr == nil + elif it.kind == nkType: + throwObj = it + else: internalError(p.config, n.info, "genTryStmt") + if orExpr != nil: add(orExpr, "||") - addf(orExpr, "isObj($2lastJSError.m_type, $1)", - [genTypeInfo(p, n.sons[i].sons[j].typ), dollar]) + # Generate the correct type checking code depending on whether this is a + # NIM-native or a JS-native exception + # if isJsObject(throwObj.typ): + if isImportedException(throwObj.typ, p.config): + addf(orExpr, "lastJSError instanceof $1", + [throwObj.typ.sym.loc.r]) + else: + addf(orExpr, "isObj(lastJSError.m_type, $1)", + [genTypeInfo(p, throwObj.typ)]) + if i > 1: line(p, "else ") - lineF(p, "if ($1lastJSError && ($2)) {$n", [dollar, orExpr]) + lineF(p, "if (lastJSError && ($1)) {$n", [orExpr]) + # If some branch requires a local alias introduce it here. This is needed + # since JS cannot do ``catch x as y``. + if excAlias != nil: + excAlias.sym.loc.r = mangleName(p.module, excAlias.sym) + lineF(p, "var $1 = lastJSError;$n", excAlias.sym.loc.r) gen(p, n.sons[i].sons[blen - 1], a) moveInto(p, a, r) lineF(p, "}$n", []) @@ -650,7 +729,7 @@ proc genTry(p: PProc, n: PNode, r: var TCompRes) = line(p, "else {\L") line(p, "\treraiseException();\L") line(p, "}\L") - addf(p.body, "$1lastJSError = $1prevJSError;$n", [dollar]) + lineF(p, "lastJSError = prevJSError;$n") line(p, "} finally {\L") line(p, "framePtr = $1;$n" % [tmpFramePtr]) if i < length and n.sons[i].kind == nkFinally: @@ -775,6 +854,7 @@ proc genAsmOrEmitStmt(p: PProc, n: PNode) = # A fat pointer is disguised as an array r.res = r.address r.address = nil + r.typ = etyNone elif r.typ == etyBaseIndex: # Deference first r.res = "$1[$2]" % [r.address, r.res] @@ -837,26 +917,42 @@ proc countJsParams(typ: PType): int = const nodeKindsNeedNoCopy = {nkCharLit..nkInt64Lit, nkStrLit..nkTripleStrLit, - nkFloatLit..nkFloat64Lit, nkCurly, nkPar, nkTupleConstr, nkObjConstr, nkStringToCString, + nkFloatLit..nkFloat64Lit, nkCurly, nkPar, nkStringToCString, nkCStringToString, nkCall, nkPrefix, nkPostfix, nkInfix, nkCommand, nkHiddenCallConv, nkCallStrLit} proc needsNoCopy(p: PProc; y: PNode): bool = - result = (y.kind in nodeKindsNeedNoCopy) or - (skipTypes(y.typ, abstractInst).kind in {tyRef, tyPtr, tyLent, tyVar}) + # if the node is a literal object constructor we have to recursively + # check the expressions passed into it + case y.kind + of nkObjConstr: + for arg in y.sons[1..^1]: + if not needsNoCopy(p, arg[1]): + return false + of nkTupleConstr: + for arg in y.sons: + var arg = arg + if arg.kind == nkExprColonExpr: + arg = arg[1] + if not needsNoCopy(p, arg): + return false + of nkBracket: + for arg in y.sons: + if not needsNoCopy(p, arg): + return false + of nodeKindsNeedNoCopy: + return true + else: + return (mapType(y.typ) != etyBaseIndex and + (skipTypes(y.typ, abstractInst).kind in + {tyRef, tyPtr, tyLent, tyVar, tyCString} + IntegralTypes)) + return true proc genAsgnAux(p: PProc, x, y: PNode, noCopyNeeded: bool) = var a, b: TCompRes var xtyp = mapType(p, x.typ) - if x.kind == nkHiddenDeref and x.sons[0].kind == nkCall and xtyp != etyObject: - gen(p, x.sons[0], a) - let tmp = p.getTemp(false) - lineF(p, "var $1 = $2;$n", [tmp, a.rdLoc]) - a.res = "$1[0][$1[1]]" % [tmp] - else: - gen(p, x, a) - + gen(p, x, a) genLineDir(p, y) gen(p, y, b) @@ -885,13 +981,13 @@ proc genAsgnAux(p: PProc, x, y: PNode, noCopyNeeded: bool) = let tmp = p.getTemp(false) lineF(p, "var $1 = $4; $2 = $1[0]; $3 = $1[1];$n", [tmp, a.address, a.res, b.rdLoc]) elif b.typ == etyBaseIndex: - lineF(p, "$# = $#;$n", [a.res, b.rdLoc]) + lineF(p, "$# = [$#, $#];$n", [a.res, b.address, b.res]) else: internalError(p.config, x.info, "genAsgn") else: lineF(p, "$1 = $2; $3 = $4;$n", [a.address, b.address, a.res, b.res]) else: - lineF(p, "$1 = $2;$n", [a.res, b.res]) + lineF(p, "$1 = $2;$n", [a.rdLoc, b.rdLoc]) proc genAsgn(p: PProc, n: PNode) = genAsgnAux(p, n.sons[0], n.sons[1], noCopyNeeded=false) @@ -945,28 +1041,78 @@ proc genFieldAddr(p: PProc, n: PNode, r: var TCompRes) = r.kind = resExpr proc genFieldAccess(p: PProc, n: PNode, r: var TCompRes) = - r.typ = etyNone gen(p, n.sons[0], r) + r.typ = mapType(n.typ) let otyp = skipTypes(n.sons[0].typ, abstractVarRange) + + template mkTemp(i: int) = + if r.typ == etyBaseIndex: + if needsTemp(p, n[i]): + let tmp = p.getTemp + r.address = "($1 = $2, $1)[0]" % [tmp, r.res] + r.res = "$1[1]" % [tmp] + r.tmpLoc = tmp + else: + r.address = "$1[0]" % [r.res] + r.res = "$1[1]" % [r.res] if otyp.kind == tyTuple: r.res = ("$1.Field$2") % [r.res, getFieldPosition(p, n.sons[1]).rope] + mkTemp(0) else: if n.sons[1].kind != nkSym: internalError(p.config, n.sons[1].info, "genFieldAccess") var f = n.sons[1].sym if f.loc.r == nil: f.loc.r = mangleName(p.module, f) r.res = "$1.$2" % [r.res, f.loc.r] + mkTemp(1) r.kind = resExpr proc genAddr(p: PProc, n: PNode, r: var TCompRes) -proc genCheckedFieldAddr(p: PProc, n: PNode, r: var TCompRes) = - let m = if n.kind == nkHiddenAddr: n.sons[0] else: n - internalAssert p.config, m.kind == nkCheckedFieldExpr - genAddr(p, m, r) # XXX - -proc genCheckedFieldAccess(p: PProc, n: PNode, r: var TCompRes) = - genFieldAccess(p, n.sons[0], r) # XXX +proc genCheckedFieldOp(p: PProc, n: PNode, addrTyp: PType, r: var TCompRes) = + internalAssert p.config, n.kind == nkCheckedFieldExpr + # nkDotExpr to access the requested field + let accessExpr = n[0] + # nkCall to check if the discriminant is valid + var checkExpr = n[1] + + let negCheck = checkExpr[0].sym.magic == mNot + if negCheck: + checkExpr = checkExpr[^1] + + # Field symbol + var field = accessExpr[1].sym + internalAssert p.config, field.kind == skField + if field.loc.r == nil: field.loc.r = mangleName(p.module, field) + # Discriminant symbol + let disc = checkExpr[2].sym + internalAssert p.config, disc.kind == skField + if disc.loc.r == nil: disc.loc.r = mangleName(p.module, disc) + + var setx: TCompRes + gen(p, checkExpr[1], setx) + + var obj: TCompRes + gen(p, accessExpr[0], obj) + # Avoid evaluating the LHS twice (one to read the discriminant and one to read + # the field) + let tmp = p.getTemp() + lineF(p, "var $1 = $2;$n", tmp, obj.res) + + useMagic(p, "raiseFieldError") + useMagic(p, "makeNimstrLit") + lineF(p, "if ($1[$2.$3]$4undefined) { raiseFieldError(makeNimstrLit($5)); }$n", + setx.res, tmp, disc.loc.r, if negCheck: ~"!==" else: ~"===", + makeJSString(field.name.s)) + + if addrTyp != nil and mapType(p, addrTyp) == etyBaseIndex: + r.typ = etyBaseIndex + r.res = makeJSString($field.loc.r) + r.address = tmp + else: + r.typ = etyNone + r.res = "$1.$2" % [tmp, field.loc.r] + r.kind = resExpr proc genArrayAddr(p: PProc, n: PNode, r: var TCompRes) = var @@ -976,14 +1122,15 @@ proc genArrayAddr(p: PProc, n: PNode, r: var TCompRes) = let m = if n.kind == nkHiddenAddr: n.sons[0] else: n gen(p, m.sons[0], a) gen(p, m.sons[1], b) - internalAssert p.config, a.typ != etyBaseIndex and b.typ != etyBaseIndex - r.address = a.res + #internalAssert p.config, a.typ != etyBaseIndex and b.typ != etyBaseIndex + let (x, tmp) = maybeMakeTemp(p, m[0], a) + r.address = x var typ = skipTypes(m.sons[0].typ, abstractPtrs) if typ.kind == tyArray: first = firstOrd(p.config, typ.sons[0]) else: first = 0 if optBoundsCheck in p.options: useMagic(p, "chckIndx") - r.res = "chckIndx($1, $2, $3.length+$2-1)-$2" % [b.res, rope(first), a.res] + r.res = "chckIndx($1, $2, $3.length+$2-1)-$2" % [b.res, rope(first), tmp] elif first != 0: r.res = "($1)-$2" % [b.res, rope(first)] else: @@ -999,13 +1146,22 @@ proc genArrayAccess(p: PProc, n: PNode, r: var TCompRes) = of tyTuple: genFieldAddr(p, n, r) else: internalError(p.config, n.info, "expr(nkBracketExpr, " & $ty.kind & ')') - r.typ = etyNone + r.typ = mapType(n.typ) if r.res == nil: internalError(p.config, n.info, "genArrayAccess") if ty.kind == tyCString: r.res = "$1.charCodeAt($2)" % [r.address, r.res] + elif r.typ == etyBaseIndex: + if needsTemp(p, n[0]): + let tmp = p.getTemp + r.address = "($1 = $2, $1)[0]" % [tmp, r.rdLoc] + r.res = "$1[1]" % [tmp] + r.tmpLoc = tmp + else: + let x = r.rdLoc + r.address = "$1[0]" % [x] + r.res = "$1[1]" % [x] else: r.res = "$1[$2]" % [r.address, r.res] - r.address = nil r.kind = resExpr template isIndirect(x: PSym): bool = @@ -1045,7 +1201,7 @@ proc genAddr(p: PProc, n: PNode, r: var TCompRes) = #internalError(p.config, n.info, "genAddr: 4 " & renderTree(n)) else: internalError(p.config, n.info, "genAddr: 2") of nkCheckedFieldExpr: - genCheckedFieldAddr(p, n, r) + genCheckedFieldOp(p, n[0], n.typ, r) of nkDotExpr: if mapType(p, n.typ) == etyBaseIndex: genFieldAddr(p, n.sons[0], r) @@ -1106,8 +1262,12 @@ proc genSym(p: PProc, n: PNode, r: var TCompRes) = if k == etyBaseIndex: r.typ = etyBaseIndex if {sfAddrTaken, sfGlobal} * s.flags != {}: - r.address = "$1[0]" % [s.loc.r] - r.res = "$1[1]" % [s.loc.r] + if isIndirect(s): + r.address = "$1[0][0]" % [s.loc.r] + r.res = "$1[0][1]" % [s.loc.r] + else: + r.address = "$1[0]" % [s.loc.r] + r.res = "$1[1]" % [s.loc.r] else: r.address = s.loc.r r.res = s.loc.r & "_Idx" @@ -1147,14 +1307,17 @@ proc genDeref(p: PProc, n: PNode, r: var TCompRes) = else: var a: TCompRes gen(p, it, a) - r.kind = resExpr - if a.typ == etyBaseIndex: - r.res = "$1[$2]" % [a.address, a.res] - elif it.kind == nkCall: + r.kind = a.kind + r.typ = mapType(p, n.typ) + if r.typ == etyBaseIndex: let tmp = p.getTemp - r.res = "($1 = $2, $1[0])[$1[1]]" % [tmp, a.res] - elif t == etyBaseIndex: - r.res = "$1[0]" % [a.res] + r.address = "($1 = $2, $1)[0]" % [tmp, a.rdLoc] + r.res = "$1[1]" % [tmp] + r.tmpLoc = tmp + elif a.typ == etyBaseIndex: + if a.tmpLoc != nil: + r.tmpLoc = a.tmpLoc + r.res = a.rdLoc else: internalError(p.config, n.info, "genDeref") @@ -1179,7 +1342,7 @@ proc genArg(p: PProc, n: PNode, param: PSym, r: var TCompRes; emitted: ptr int = add(r.res, ", ") add(r.res, a.res) if emitted != nil: inc emitted[] - elif n.typ.kind in {tyVar, tyLent} and n.kind in nkCallKinds and mapType(param.typ) == etyBaseIndex: + elif n.typ.kind in {tyVar, tyPtr, tyRef, tyLent} and n.kind in nkCallKinds and mapType(param.typ) == etyBaseIndex: # this fixes bug #5608: let tmp = getTemp(p) add(r.res, "($1 = $2, $1[0]), $1[1]" % [tmp, a.rdLoc]) @@ -1303,6 +1466,14 @@ proc genCall(p: PProc, n: PNode, r: var TCompRes) = return gen(p, n.sons[0], r) genArgs(p, n, r) + if n.typ != nil: + let t = mapType(n.typ) + if t == etyBaseIndex: + let tmp = p.getTemp + r.address = "($1 = $2, $1)[0]" % [tmp, r.rdLoc] + r.res = "$1[1]" % [tmp] + r.tmpLoc = tmp + r.typ = t proc genEcho(p: PProc, n: PNode, r: var TCompRes) = let n = n[1].skipConv @@ -1409,12 +1580,12 @@ proc createVar(p: PProc, typ: PType, indirect: bool): Rope = createObjInitList(p, t, initIntSet(), initList) result = ("{$1}") % [initList] if indirect: result = "[$1]" % [result] - of tyVar, tyPtr, tyLent, tyRef: + of tyVar, tyPtr, tyLent, tyRef, tyPointer: if mapType(p, t) == etyBaseIndex: result = putToSeq("[null, 0]", indirect) else: result = putToSeq("null", indirect) - of tySequence, tyOpt, tyString, tyCString, tyPointer, tyProc: + of tySequence, tyOpt, tyString, tyCString, tyProc: result = putToSeq("null", indirect) of tyStatic: if t.n != nil: @@ -1448,10 +1619,13 @@ proc genVarInit(p: PProc, v: PSym, n: PNode) = varCode = v.constraint.strVal if n.kind == nkEmpty: - lineF(p, varCode & " = $3;$n", - [returnType, varName, createVar(p, v.typ, isIndirect(v))]) - if v.typ.kind in {tyVar, tyPtr, tyLent, tyRef} and mapType(p, v.typ) == etyBaseIndex: + if not isIndirect(v) and + v.typ.kind in {tyVar, tyPtr, tyLent, tyRef} and mapType(p, v.typ) == etyBaseIndex: + lineF(p, "var $1 = null;$n", [varName]) lineF(p, "var $1_Idx = 0;$n", [varName]) + else: + lineF(p, varCode & " = $3;$n", + [returnType, varName, createVar(p, v.typ, isIndirect(v))]) else: gen(p, n, a) case mapType(p, v.typ) @@ -1468,8 +1642,12 @@ proc genVarInit(p: PProc, v: PSym, n: PNode) = lineF(p, varCode & " = $3, $2_Idx = $4;$n", [returnType, v.loc.r, a.address, a.res]) else: - lineF(p, varCode & " = [$3, $4];$n", - [returnType, v.loc.r, a.address, a.res]) + if isIndirect(v): + lineF(p, varCode & " = [[$3, $4]];$n", + [returnType, v.loc.r, a.address, a.res]) + else: + lineF(p, varCode & " = [$3, $4];$n", + [returnType, v.loc.r, a.address, a.res]) else: if targetBaseIndex: let tmp = p.getTemp @@ -1516,7 +1694,12 @@ proc genNew(p: PProc, n: PNode) = var a: TCompRes gen(p, n.sons[1], a) var t = skipTypes(n.sons[1].typ, abstractVar).sons[0] - lineF(p, "$1 = $2;$n", [a.res, createVar(p, t, false)]) + if mapType(t) == etyObject: + lineF(p, "$1 = $2;$n", [a.rdLoc, createVar(p, t, false)]) + elif a.typ == etyBaseIndex: + lineF(p, "$1 = [$3]; $2 = 0;$n", [a.address, a.res, createVar(p, t, false)]) + else: + lineF(p, "$1 = [[$2], 0];$n", [a.rdLoc, createVar(p, t, false)]) proc genNewSeq(p: PProc, n: PNode) = var x, y: TCompRes @@ -1540,20 +1723,20 @@ proc genConStrStr(p: PProc, n: PNode, r: var TCompRes) = if skipTypes(n.sons[1].typ, abstractVarRange).kind == tyChar: r.res.add("[$1].concat(" % [a.res]) else: - r.res.add("($1).concat(" % [a.res]) + r.res.add("($1 || []).concat(" % [a.res]) for i in countup(2, sonsLen(n) - 2): gen(p, n.sons[i], a) if skipTypes(n.sons[i].typ, abstractVarRange).kind == tyChar: r.res.add("[$1]," % [a.res]) else: - r.res.add("$1," % [a.res]) + r.res.add("$1 || []," % [a.res]) gen(p, n.sons[sonsLen(n) - 1], a) if skipTypes(n.sons[sonsLen(n) - 1].typ, abstractVarRange).kind == tyChar: r.res.add("[$1])" % [a.res]) else: - r.res.add("$1)" % [a.res]) + r.res.add("$1 || [])" % [a.res]) proc genToArray(p: PProc; n: PNode; r: var TCompRes) = # we map mArray to PHP's array constructor, a mild hack: @@ -1638,8 +1821,12 @@ proc genReset(p: PProc, n: PNode) = var x: TCompRes useMagic(p, "genericReset") gen(p, n.sons[1], x) - addf(p.body, "$1 = genericReset($1, $2);$n", [x.res, - genTypeInfo(p, n.sons[1].typ)]) + if x.typ == etyBaseIndex: + lineF(p, "$1 = null, $2 = 0;$n", [x.address, x.res]) + else: + let (a, tmp) = maybeMakeTemp(p, n[1], x) + lineF(p, "$1 = genericReset($3, $2);$n", [a, + genTypeInfo(p, n.sons[1].typ), tmp]) proc genMagic(p: PProc, n: PNode, r: var TCompRes) = var @@ -1658,32 +1845,37 @@ proc genMagic(p: PProc, n: PNode, r: var TCompRes) = else: unaryExpr(p, n, r, "subInt", "subInt($1, 1)") of mAppendStrCh: binaryExpr(p, n, r, "addChar", - "if ($1 != null) { addChar($1, $2); } else { $1 = [$2]; }") + "if ($1 != null) { addChar($3, $2); } else { $3 = [$2]; }") of mAppendStrStr: var lhs, rhs: TCompRes gen(p, n[1], lhs) gen(p, n[2], rhs) let rhsIsLit = n[2].kind in nkStrKinds + let (a, tmp) = maybeMakeTemp(p, n[1], lhs) if skipTypes(n.sons[1].typ, abstractVarRange).kind == tyCString: - r.res = "if ($1 != null) { $1 += $2; } else { $1 = $2$3; }" % [ - lhs.rdLoc, rhs.rdLoc, if rhsIsLit: nil else: ~".slice()"] + r.res = "if ($1 != null) { $4 += $2; } else { $4 = $2$3; }" % [ + a, rhs.rdLoc, if rhsIsLit: nil else: ~".slice()", tmp] else: - r.res = "if ($1 != null) { $1 = ($1).concat($2); } else { $1 = $2$3; }" % [ - lhs.rdLoc, rhs.rdLoc, if rhsIsLit: nil else: ~".slice()"] + r.res = "if ($1 != null) { $4 = ($4).concat($2); } else { $4 = $2$3; }" % [ + lhs.rdLoc, rhs.rdLoc, if rhsIsLit: nil else: ~".slice()", tmp] r.kind = resExpr of mAppendSeqElem: var x, y: TCompRes gen(p, n.sons[1], x) gen(p, n.sons[2], y) - if needsNoCopy(p, n[2]): - r.res = "if ($1 != null) { $1.push($2); } else { $1 = [$2]; }" % [x.rdLoc, y.rdLoc] + let (a, tmp) = maybeMakeTemp(p, n[1], x) + if mapType(n[2].typ) == etyBaseIndex: + let c = "[$1, $2]" % [y.address, y.res] + r.res = "if ($1 != null) { $3.push($2); } else { $3 = [$2]; }" % [a, c, tmp] + elif needsNoCopy(p, n[2]): + r.res = "if ($1 != null) { $3.push($2); } else { $3 = [$2]; }" % [a, y.rdLoc, tmp] else: useMagic(p, "nimCopy") let c = getTemp(p, defineInLocals=false) lineF(p, "var $1 = nimCopy(null, $2, $3);$n", [c, y.rdLoc, genTypeInfo(p, n[2].typ)]) - r.res = "if ($1 != null) { $1.push($2); } else { $1 = [$2]; }" % [x.rdLoc, c] + r.res = "if ($1 != null) { $3.push($2); } else { $3 = [$2]; }" % [a, c, tmp] r.kind = resExpr of mConStrStr: genConStrStr(p, n, r) @@ -1693,39 +1885,56 @@ proc genMagic(p: PProc, n: PNode, r: var TCompRes) = binaryExpr(p, n, r, "cmpStrings", "(cmpStrings($1, $2) <= 0)") of mLtStr: binaryExpr(p, n, r, "cmpStrings", "(cmpStrings($1, $2) < 0)") - of mIsNil: unaryExpr(p, n, r, "", "($1 === null)") + of mIsNil: + if mapType(n[1].typ) != etyBaseIndex: + unaryExpr(p, n, r, "", "($1 === null)") + else: + var x: TCompRes + gen(p, n[1], x) + r.res = "($# === null && $# === 0)" % [x.address, x.res] of mEnumToStr: genRepr(p, n, r) of mNew, mNewFinalize: genNew(p, n) - of mSizeOf: r.res = rope(getSize(p.config, n.sons[1].typ)) - of mChr, mArrToSeq: gen(p, n.sons[1], r) # nothing to do + of mChr: gen(p, n.sons[1], r) + of mArrToSeq: + if needsNoCopy(p, n.sons[1]): + gen(p, n.sons[1], r) + else: + var x: TCompRes + gen(p, n.sons[1], x) + useMagic(p, "nimCopy") + r.res = "nimCopy(null, $1, $2)" % [x.rdLoc, genTypeInfo(p, n.typ)] + of mDestroy: discard "ignore calls to the default destructor" of mOrd: genOrd(p, n, r) of mLengthStr, mLengthSeq, mLengthOpenArray, mLengthArray: - unaryExpr(p, n, r, "", "($1 != null ? $1.length : 0)") + unaryExpr(p, n, r, "", "($1 != null ? $2.length : 0)") of mXLenStr, mXLenSeq: unaryExpr(p, n, r, "", "$1.length") of mHigh: - unaryExpr(p, n, r, "", "($1 != null ? ($1.length-1) : -1)") + unaryExpr(p, n, r, "", "($1 != null ? ($2.length-1) : -1)") of mInc: if n[1].typ.skipTypes(abstractRange).kind in tyUInt .. tyUInt64: binaryUintExpr(p, n, r, "+", true) else: if optOverflowCheck notin p.options: binaryExpr(p, n, r, "", "$1 += $2") - else: binaryExpr(p, n, r, "addInt", "$1 = addInt($1, $2)") + else: binaryExpr(p, n, r, "addInt", "$1 = addInt($3, $2)") of ast.mDec: if n[1].typ.skipTypes(abstractRange).kind in tyUInt .. tyUInt64: binaryUintExpr(p, n, r, "-", true) else: if optOverflowCheck notin p.options: binaryExpr(p, n, r, "", "$1 -= $2") - else: binaryExpr(p, n, r, "subInt", "$1 = subInt($1, $2)") + else: binaryExpr(p, n, r, "subInt", "$1 = subInt($3, $2)") of mSetLengthStr: - binaryExpr(p, n, r, "", "$1.length = $2") + binaryExpr(p, n, r, "mnewString", "($1 === null ? $3 = mnewString($2) : $3.length = $2)") of mSetLengthSeq: var x, y: TCompRes gen(p, n.sons[1], x) gen(p, n.sons[2], y) let t = skipTypes(n.sons[1].typ, abstractVar).sons[0] - r.res = """if ($1.length < $2) { for (var i=$1.length;i<$2;++i) $1.push($3); } - else { $1.length = $2; }""" % [x.rdLoc, y.rdLoc, createVar(p, t, false)] + let (a, tmp) = maybeMakeTemp(p, n[1], x) + let (b, tmp2) = maybeMakeTemp(p, n[2], y) + r.res = """if ($1 === null) $4 = []; + if ($4.length < $2) { for (var i=$4.length;i<$5;++i) $4.push($3); } + else { $4.length = $5; }""" % [a, b, createVar(p, t, false), tmp, tmp2] r.kind = resExpr of mCard: unaryExpr(p, n, r, "SetCard", "SetCard($1)") of mLtSet: binaryExpr(p, n, r, "SetLt", "SetLt($1, $2)") @@ -1793,7 +2002,10 @@ proc genArrayConstr(p: PProc, n: PNode, r: var TCompRes) = for i in countup(0, sonsLen(n) - 1): if i > 0: add(r.res, ", ") gen(p, n.sons[i], a) - add(r.res, a.res) + if a.typ == etyBaseIndex: + addf(r.res, "[$1, $2]", [a.address, a.res]) + else: + add(r.res, a.res) add(r.res, "]") proc genTupleConstr(p: PProc, n: PNode, r: var TCompRes) = @@ -1805,7 +2017,10 @@ proc genTupleConstr(p: PProc, n: PNode, r: var TCompRes) = var it = n.sons[i] if it.kind == nkExprColonExpr: it = it.sons[1] gen(p, it, a) - addf(r.res, "Field$#: $#", [i.rope, a.res]) + if a.typ == etyBaseIndex: + addf(r.res, "Field$#: [$#, $#]", [i.rope, a.address, a.res]) + else: + addf(r.res, "Field$#: $#", [i.rope, a.res]) r.res.add("}") proc genObjConstr(p: PProc, n: PNode, r: var TCompRes) = @@ -1825,12 +2040,17 @@ proc genObjConstr(p: PProc, n: PNode, r: var TCompRes) = let typ = val.typ.skipTypes(abstractInst) if (typ.kind in IntegralTypes+{tyCstring, tyRef, tyPtr} and - mapType(p, typ) != etyBaseIndex) or needsNoCopy(p, it.sons[1]): + mapType(p, typ) != etyBaseIndex) or + a.typ == etyBaseIndex or + needsNoCopy(p, it.sons[1]): discard else: useMagic(p, "nimCopy") a.res = "nimCopy(null, $1, $2)" % [a.rdLoc, genTypeInfo(p, typ)] - addf(initList, "$#: $#", [f.loc.r, a.res]) + if a.typ == etyBaseIndex: + addf(initList, "$#: [$#, $#]", [f.loc.r, a.address, a.res]) + else: + addf(initList, "$#: $#", [f.loc.r, a.res]) let t = skipTypes(n.typ, abstractInst + skipPtrs) createObjInitList(p, t, fieldIDs, initList) r.res = ("{$1}") % [initList] @@ -1948,18 +2168,22 @@ proc genProc(oldProc: PProc, prc: PSym): Rope = if prc.typ.sons[0] != nil and sfPure notin prc.flags: resultSym = prc.ast.sons[resultPos].sym let mname = mangleName(p.module, resultSym) - let resVar = createVar(p, resultSym.typ, isIndirect(resultSym)) - resultAsgn = p.indentLine(("var $# = $#;$n") % [mname, resVar]) - if resultSym.typ.kind in {tyVar, tyPtr, tyLent, tyRef} and + if not isindirect(resultSym) and + resultSym.typ.kind in {tyVar, tyPtr, tyLent, tyRef} and mapType(p, resultSym.typ) == etyBaseIndex: + resultAsgn = p.indentLine(("var $# = null;$n") % [mname]) resultAsgn.add p.indentLine("var $#_Idx = 0;$n" % [mname]) + else: + let resVar = createVar(p, resultSym.typ, isIndirect(resultSym)) + resultAsgn = p.indentLine(("var $# = $#;$n") % [mname, resVar]) gen(p, prc.ast.sons[resultPos], a) if mapType(p, resultSym.typ) == etyBaseIndex: returnStmt = "return [$#, $#];$n" % [a.address, a.res] else: returnStmt = "return $#;$n" % [a.res] - p.nested: genStmt(p, prc.getBody) + let transformed_body = transformBody(oldProc.module.graph, prc, cache = false) + p.nested: genStmt(p, transformed_body) var def: Rope if not prc.constraint.isNil: @@ -2043,6 +2267,13 @@ proc genCast(p: PProc, n: PNode, r: var TCompRes) = of 4: "0xfffffffe" else: "" r.res = "($1 - ($2 $3))" % [rope minuend, r.res, trimmer] + elif (src.kind == tyPtr and mapType(p, src) == etyObject) and dest.kind == tyPointer: + r.address = r.res + r.res = ~"null" + r.typ = etyBaseIndex + elif (dest.kind == tyPtr and mapType(p, dest) == etyObject) and src.kind == tyPointer: + r.res = r.address + r.typ = etyObject proc gen(p: PProc, n: PNode, r: var TCompRes) = r.typ = etyNone @@ -2114,7 +2345,7 @@ proc gen(p: PProc, n: PNode, r: var TCompRes) = of nkDerefExpr, nkHiddenDeref: genDeref(p, n, r) of nkBracketExpr: genArrayAccess(p, n, r) of nkDotExpr: genFieldAccess(p, n, r) - of nkCheckedFieldExpr: genCheckedFieldAccess(p, n, r) + of nkCheckedFieldExpr: genCheckedFieldOp(p, n, nil, r) of nkObjDownConv: gen(p, n.sons[0], r) of nkObjUpConv: upConv(p, n, r) of nkCast: genCast(p, n, r) @@ -2209,7 +2440,8 @@ proc genModule(p: PProc, n: PNode) = add(p.body, frameCreate(p, makeJSString("module " & p.module.module.name.s), makeJSString(toFilename(p.config, p.module.module.info)))) - genStmt(p, n) + let n_transformed = transformStmt(p.module.graph, p.module.module, n) + genStmt(p, n_transformed) if optStackTrace in p.options: add(p.body, frameDestroy(p)) @@ -2282,6 +2514,9 @@ proc myClose(graph: ModuleGraph; b: PPassContext, n: PNode): PNode = else: AbsoluteFile(getCurrentDir() / m.config.outFile.string) else: changeFileExt(completeCFilePath(m.config, AbsoluteFile f), ext) + let (outDir, _, _) = splitFile(outfile) + if not outDir.isEmpty: + createDir(outDir) discard writeRopeIfNotEqual(genHeader() & code, outfile) for obj, content in items(globals.classes): genClass(m.config, obj, content, ext) @@ -2290,4 +2525,3 @@ proc myOpen(graph: ModuleGraph; s: PSym): PPassContext = result = newModule(graph, s) const JSgenPass* = makePass(myOpen, myProcess, myClose) - diff --git a/compiler/lambdalifting.nim b/compiler/lambdalifting.nim index d8c0461ce..c318421fa 100644 --- a/compiler/lambdalifting.nim +++ b/compiler/lambdalifting.nim @@ -11,7 +11,8 @@ import intsets, strutils, options, ast, astalgo, trees, treetab, msgs, - idents, renderer, types, magicsys, lowerings, tables, modulegraphs, lineinfos + idents, renderer, types, magicsys, lowerings, tables, modulegraphs, lineinfos, + transf discard """ The basic approach is that captured vars need to be put on the heap and @@ -257,7 +258,7 @@ proc liftIterSym*(g: ModuleGraph; n: PNode; owner: PSym): PNode = # add 'new' statement: result.add newCall(getSysSym(g, n.info, "internalNew"), env) result.add makeClosure(g, iter, env, n.info) - + proc freshVarForClosureIter*(g: ModuleGraph; s, owner: PSym): PNode = let envParam = getHiddenParam(g, owner) let obj = envParam.typ.lastSon @@ -277,8 +278,9 @@ proc freshVarForClosureIter*(g: ModuleGraph; s, owner: PSym): PNode = proc markAsClosure(g: ModuleGraph; owner: PSym; n: PNode) = let s = n.sym if illegalCapture(s): - localError(g.config, n.info, "illegal capture '$1' of type <$2> which is declared here: $3" % - [s.name.s, typeToString(s.typ), g.config$s.info]) + localError(g.config, n.info, + ("'$1' is of type <$2> which cannot be captured as it would violate memory" & + " safety, declared here: $3") % [s.name.s, typeToString(s.typ), g.config$s.info]) elif owner.typ.callConv notin {ccClosure, ccDefault}: localError(g.config, n.info, "illegal capture '$1' because '$2' has the calling convention: <$3>" % [s.name.s, owner.name.s, CallingConvToStr[owner.typ.callConv]]) @@ -389,7 +391,8 @@ proc detectCapturedVars(n: PNode; owner: PSym; c: var DetectionPass) = if innerProc: if s.isIterator: c.somethingToDo = true if not c.processed.containsOrIncl(s.id): - detectCapturedVars(s.getBody, s, c) + let body = transformBody(c.graph, s) + detectCapturedVars(body, s, c) let ow = s.skipGenericOwner if ow == owner: if owner.isIterator: @@ -650,14 +653,17 @@ proc liftCapturedVars(n: PNode; owner: PSym; d: DetectionPass; # echo renderTree(s.getBody, {renderIds}) let oldInContainer = c.inContainer c.inContainer = 0 - var body = liftCapturedVars(s.getBody, s, d, c) + var body = transformBody(d.graph, s) + body = liftCapturedVars(body, s, d, c) if c.envvars.getOrDefault(s.id).isNil: - s.ast.sons[bodyPos] = body + s.transformedBody = body else: - s.ast.sons[bodyPos] = newTree(nkStmtList, rawClosureCreation(s, d, c), body) + s.transformedBody = newTree(nkStmtList, rawClosureCreation(s, d, c), body) c.inContainer = oldInContainer + if s.typ.callConv == ccClosure: result = symToClosure(n, owner, d, c) + elif s.id in d.capturedVars: if s.owner != owner: result = accessViaEnvParam(d.graph, n, owner) diff --git a/compiler/layouter.nim b/compiler/layouter.nim index cc6ec48b7..8605ade45 100644 --- a/compiler/layouter.nim +++ b/compiler/layouter.nim @@ -28,10 +28,10 @@ type config: ConfigRef fid: FileIndex lastTok: TTokType - inquote: bool + inquote, lastTokWasTerse: bool semicolons: SemicolonKind - col, lastLineNumber, lineSpan, indentLevel, indWidth: int - nested: int + col, lastLineNumber, lineSpan, indentLevel, indWidth*: int + keepIndents*: int doIndentMore*: int content: string indentStack: seq[int] @@ -41,9 +41,10 @@ type proc openEmitter*(em: var Emitter, cache: IdentCache; config: ConfigRef, fileIdx: FileIndex) = let fullPath = Absolutefile config.toFullPath(fileIdx) - em.indWidth = getIndentWidth(fileIdx, llStreamOpen(fullPath, fmRead), - cache, config) - if em.indWidth == 0: em.indWidth = 2 + if em.indWidth == 0: + em.indWidth = getIndentWidth(fileIdx, llStreamOpen(fullPath, fmRead), + cache, config) + if em.indWidth == 0: em.indWidth = 2 em.config = config em.fid = fileIdx em.lastTok = tkInvalid @@ -52,11 +53,16 @@ proc openEmitter*(em: var Emitter, cache: IdentCache; em.content = newStringOfCap(16_000) em.indentStack = newSeqOfCap[int](30) em.indentStack.add 0 + em.lastLineNumber = 1 proc closeEmitter*(em: var Emitter) = + if fileExists(em.config.outFile) and readFile(em.config.outFile.string) == em.content: + discard "do nothing, see #9499" + return var f = llStreamOpen(em.config.outFile, fmWrite) if f == nil: rawMessage(em.config, errGenerated, "cannot open file: " & em.config.outFile.string) + return f.llStreamWrite em.content llStreamClose(f) @@ -106,15 +112,16 @@ proc softLinebreak(em: var Emitter, lit: string) = for i in 1..em.indentLevel+moreIndent(em): wr(" ") else: # search backwards for a good split position: - for a in em.altSplitPos: + for a in mitems(em.altSplitPos): if a > em.fixedUntil: var spaces = 0 while a+spaces < em.content.len and em.content[a+spaces] == ' ': inc spaces if spaces > 0: delete(em.content, a, a+spaces-1) - let ws = "\L" & repeat(' ',em.indentLevel+moreIndent(em)) em.col = em.content.len - a + let ws = "\L" & repeat(' ', em.indentLevel+moreIndent(em)) em.content.insert(ws, a) + a = -1 break proc emitTok*(em: var Emitter; L: TLexer; tok: TToken) = @@ -134,6 +141,22 @@ proc emitTok*(em: var Emitter; L: TLexer; tok: TToken) = for i in 1 .. LineCommentColumn - em.col: wr(" ") wr lit + if tok.tokType == tkComment and tok.literal.startsWith("#!nimpretty"): + case tok.literal + of "#!nimpretty off": + inc em.keepIndents + wr("\L") + em.lastLineNumber = tok.line + 1 + of "#!nimpretty on": + dec em.keepIndents + em.lastLineNumber = tok.line + wr("\L") + #for i in 1 .. tok.indent: wr " " + wr tok.literal + em.col = 0 + em.lineSpan = 0 + return + var preventComment = false if tok.tokType == tkComment and tok.line == em.lastLineNumber and tok.indent >= 0: # we have an inline comment so handle it before the indentation token: @@ -142,7 +165,7 @@ proc emitTok*(em: var Emitter; L: TLexer; tok: TToken) = em.fixedUntil = em.content.high elif tok.indent >= 0: - if em.lastTok in (splitters + oprSet): + if em.lastTok in (splitters + oprSet) or em.keepIndents > 0: em.indentLevel = tok.indent else: if tok.indent > em.indentStack[^1]: @@ -171,12 +194,13 @@ proc emitTok*(em: var Emitter; L: TLexer; tok: TToken) = wr(" ") em.fixedUntil = em.content.high + var lastTokWasTerse = false case tok.tokType of tokKeywordLow..tokKeywordHigh: if endsInAlpha(em): wr(" ") elif not em.inquote and not endsInWhite(em) and - em.lastTok notin openPars: + em.lastTok notin openPars and not em.lastTokWasTerse: #and tok.tokType in oprSet wr(" ") @@ -212,14 +236,19 @@ proc emitTok*(em: var Emitter; L: TLexer; tok: TToken) = tkBracketDotRi, tkCurlyDotRi, tkParDotRi, - tkColonColon, tkDot: + tkColonColon: + wr(TokTypeToStr[tok.tokType]) + of tkDot: + lastTokWasTerse = true wr(TokTypeToStr[tok.tokType]) of tkEquals: if not em.inquote and not em.endsInWhite: wr(" ") wr(TokTypeToStr[tok.tokType]) if not em.inquote: wr(" ") of tkOpr, tkDotDot: - if tok.strongSpaceA == 0 and tok.strongSpaceB == 0: + if (tok.strongSpaceA == 0 and tok.strongSpaceB == 0) or em.inquote: + # bug #9504: remember to not spacify a keyword: + lastTokWasTerse = true # if not surrounded by whitespace, don't produce any whitespace either: wr(tok.ident.s) else: @@ -229,8 +258,8 @@ proc emitTok*(em: var Emitter; L: TLexer; tok: TToken) = tok.strongSpaceB == 0 and tok.strongSpaceA > 0 if not isUnary(tok): - wr(" ") rememberSplit(splitBinary) + wr(" ") of tkAccent: if not em.inquote and endsInAlpha(em): wr(" ") wr(TokTypeToStr[tok.tokType]) @@ -253,6 +282,7 @@ proc emitTok*(em: var Emitter; L: TLexer; tok: TToken) = wr lit em.lastTok = tok.tokType + em.lastTokWasTerse = lastTokWasTerse em.lastLineNumber = tok.line + em.lineSpan em.lineSpan = 0 diff --git a/compiler/lexer.nim b/compiler/lexer.nim index 4cb800017..635e6f08d 100644 --- a/compiler/lexer.nim +++ b/compiler/lexer.nim @@ -224,7 +224,7 @@ proc openLexer*(lex: var TLexer, fileIdx: FileIndex, inputstream: PLLStream; cache: IdentCache; config: ConfigRef) = openBaseLexer(lex, inputstream) lex.fileIdx = fileidx - lex.indentAhead = - 1 + lex.indentAhead = -1 lex.currLineIndent = 0 inc(lex.lineNumber, inputstream.lineOffset) lex.cache = cache @@ -633,13 +633,55 @@ proc handleHexChar(L: var TLexer, xi: var int) = of 'A'..'F': xi = (xi shl 4) or (ord(L.buf[L.bufpos]) - ord('A') + 10) inc(L.bufpos) - else: discard + else: + lexMessage(L, errGenerated, + "expected a hex digit, but found: " & L.buf[L.bufpos] & + " ; maybe prepend with 0") + # Need to progress for `nim check` + inc(L.bufpos) proc handleDecChars(L: var TLexer, xi: var int) = while L.buf[L.bufpos] in {'0'..'9'}: xi = (xi * 10) + (ord(L.buf[L.bufpos]) - ord('0')) inc(L.bufpos) +proc addUnicodeCodePoint(s: var string, i: int) = + # inlined toUTF-8 to avoid unicode and strutils dependencies. + let pos = s.len + if i <=% 127: + s.setLen(pos+1) + s[pos+0] = chr(i) + elif i <=% 0x07FF: + s.setLen(pos+2) + s[pos+0] = chr((i shr 6) or 0b110_00000) + s[pos+1] = chr((i and ones(6)) or 0b10_0000_00) + elif i <=% 0xFFFF: + s.setLen(pos+3) + s[pos+0] = chr(i shr 12 or 0b1110_0000) + s[pos+1] = chr(i shr 6 and ones(6) or 0b10_0000_00) + s[pos+2] = chr(i and ones(6) or 0b10_0000_00) + elif i <=% 0x001FFFFF: + s.setLen(pos+4) + s[pos+0] = chr(i shr 18 or 0b1111_0000) + s[pos+1] = chr(i shr 12 and ones(6) or 0b10_0000_00) + s[pos+2] = chr(i shr 6 and ones(6) or 0b10_0000_00) + s[pos+3] = chr(i and ones(6) or 0b10_0000_00) + elif i <=% 0x03FFFFFF: + s.setLen(pos+5) + s[pos+0] = chr(i shr 24 or 0b111110_00) + s[pos+1] = chr(i shr 18 and ones(6) or 0b10_0000_00) + s[pos+2] = chr(i shr 12 and ones(6) or 0b10_0000_00) + s[pos+3] = chr(i shr 6 and ones(6) or 0b10_0000_00) + s[pos+4] = chr(i and ones(6) or 0b10_0000_00) + elif i <=% 0x7FFFFFFF: + s.setLen(pos+6) + s[pos+0] = chr(i shr 30 or 0b1111110_0) + s[pos+1] = chr(i shr 24 and ones(6) or 0b10_0000_00) + s[pos+2] = chr(i shr 18 and ones(6) or 0b10_0000_00) + s[pos+3] = chr(i shr 12 and ones(6) or 0b10_0000_00) + s[pos+4] = chr(i shr 6 and ones(6) or 0b10_0000_00) + s[pos+5] = chr(i and ones(6) or 0b10_0000_00) + proc getEscapedChar(L: var TLexer, tok: var TToken) = inc(L.bufpos) # skip '\' case L.buf[L.bufpos] @@ -686,29 +728,36 @@ proc getEscapedChar(L: var TLexer, tok: var TToken) = of '\\': add(tok.literal, '\\') inc(L.bufpos) - of 'x', 'X', 'u', 'U': - var tp = L.buf[L.bufpos] + of 'x', 'X': inc(L.bufpos) var xi = 0 handleHexChar(L, xi) handleHexChar(L, xi) - if tp in {'u', 'U'}: + add(tok.literal, chr(xi)) + of 'u', 'U': + if tok.tokType == tkCharLit: + lexMessage(L, errGenerated, "\\u not allowed in character literal") + inc(L.bufpos) + var xi = 0 + if L.buf[L.bufpos] == '{': + inc(L.bufpos) + var start = L.bufpos + while L.buf[L.bufpos] != '}': + handleHexChar(L, xi) + if start == L.bufpos: + lexMessage(L, errGenerated, + "Unicode codepoint cannot be empty") + inc(L.bufpos) + if xi > 0x10FFFF: + let hex = ($L.buf)[start..L.bufpos-2] + lexMessage(L, errGenerated, + "Unicode codepoint must be lower than 0x10FFFF, but was: " & hex) + else: handleHexChar(L, xi) handleHexChar(L, xi) - # inlined toUTF-8 to avoid unicode and strutils dependencies. - if xi <=% 127: - add(tok.literal, xi.char ) - elif xi <=% 0x07FF: - add(tok.literal, ((xi shr 6) or 0b110_00000).char ) - add(tok.literal, ((xi and ones(6)) or 0b10_0000_00).char ) - elif xi <=% 0xFFFF: - add(tok.literal, (xi shr 12 or 0b1110_0000).char ) - add(tok.literal, (xi shr 6 and ones(6) or 0b10_0000_00).char ) - add(tok.literal, (xi and ones(6) or 0b10_0000_00).char ) - else: # value is 0xFFFF - add(tok.literal, "\xef\xbf\xbf" ) - else: - add(tok.literal, chr(xi)) + handleHexChar(L, xi) + handleHexChar(L, xi) + addUnicodeCodePoint(tok.literal, xi) of '0'..'9': if matchTwoChars(L, '0', {'0'..'9'}): lexMessage(L, warnOctalEscape) @@ -740,11 +789,17 @@ proc handleCRLF(L: var TLexer, pos: int): int = result = nimlexbase.handleLF(L, pos) else: result = pos -proc getString(L: var TLexer, tok: var TToken, rawMode: bool) = +type + StringMode = enum + normal, + raw, + generalized + +proc getString(L: var TLexer, tok: var TToken, mode: StringMode) = var pos = L.bufpos var buf = L.buf # put `buf` in a register var line = L.lineNumber # save linenumber for better error message - tokenBegin(tok, pos) + tokenBegin(tok, pos - ord(mode == raw)) inc pos # skip " if buf[pos] == '\"' and buf[pos+1] == '\"': tok.tokType = tkTripleStrLit # long string literal: @@ -784,12 +839,12 @@ proc getString(L: var TLexer, tok: var TToken, rawMode: bool) = inc(pos) else: # ordinary string literal - if rawMode: tok.tokType = tkRStrLit + if mode != normal: tok.tokType = tkRStrLit else: tok.tokType = tkStrLit while true: var c = buf[pos] if c == '\"': - if rawMode and buf[pos+1] == '\"': + if mode != normal and buf[pos+1] == '\"': inc(pos, 2) add(tok.literal, '"') else: @@ -800,7 +855,7 @@ proc getString(L: var TLexer, tok: var TToken, rawMode: bool) = tokenEndIgnore(tok, pos) lexMessage(L, errGenerated, "closing \" expected") break - elif (c == '\\') and not rawMode: + elif (c == '\\') and mode == normal: L.bufpos = pos getEscapedChar(L, tok) pos = L.bufpos @@ -912,7 +967,7 @@ proc getPrecedence*(tok: TToken, strongSpaces: bool): int = of '?': result = 2 else: considerAsgn(2) of tkDiv, tkMod, tkShl, tkShr: result = 9 - of tkIn, tkNotin, tkIs, tkIsnot, tkNot, tkOf, tkAs: result = 5 + of tkIn, tkNotin, tkIs, tkIsnot, tkOf, tkAs: result = 5 of tkDotDot: result = 6 of tkAnd: result = 4 of tkOr, tkXor, tkPtr, tkRef: result = 3 @@ -1064,8 +1119,10 @@ proc skip(L: var TLexer, tok: var TToken) = tok.strongSpaceA = 0 when defined(nimpretty): var hasComment = false + var commentIndent = L.currLineIndent tok.commentOffsetA = L.offsetBase + pos tok.commentOffsetB = tok.commentOffsetA + tok.line = -1 while true: case buf[pos] of ' ': @@ -1076,9 +1133,6 @@ proc skip(L: var TLexer, tok: var TToken) = inc(pos) of CR, LF: tokenEndPrevious(tok, pos) - when defined(nimpretty): - # we are not yet in a comment, so update the comment token's line information: - if not hasComment: inc tok.line pos = handleCRLF(L, pos) buf = L.buf var indent = 0 @@ -1087,13 +1141,19 @@ proc skip(L: var TLexer, tok: var TToken) = inc(pos) inc(indent) elif buf[pos] == '#' and buf[pos+1] == '[': - when defined(nimpretty): hasComment = true + when defined(nimpretty): + hasComment = true + if tok.line < 0: + tok.line = L.lineNumber + commentIndent = indent skipMultiLineComment(L, tok, pos+2, false) pos = L.bufpos buf = L.buf else: break tok.strongSpaceA = 0 + when defined(nimpretty): + if buf[pos] == '#' and tok.line < 0: commentIndent = indent if buf[pos] > ' ' and (buf[pos] != '#' or buf[pos+1] == '#'): tok.indent = indent L.currLineIndent = indent @@ -1101,14 +1161,20 @@ proc skip(L: var TLexer, tok: var TToken) = of '#': # do not skip documentation comment: if buf[pos+1] == '#': break - when defined(nimpretty): hasComment = true + when defined(nimpretty): + hasComment = true + if tok.line < 0: + tok.line = L.lineNumber + if buf[pos+1] == '[': skipMultiLineComment(L, tok, pos+2, false) pos = L.bufpos buf = L.buf else: tokenBegin(tok, pos) - while buf[pos] notin {CR, LF, nimlexbase.EndOfFile}: inc(pos) + while buf[pos] notin {CR, LF, nimlexbase.EndOfFile}: + when defined(nimpretty): tok.literal.add buf[pos] + inc(pos) tokenEndIgnore(tok, pos+1) when defined(nimpretty): tok.commentOffsetB = L.offsetBase + pos + 1 @@ -1120,6 +1186,7 @@ proc skip(L: var TLexer, tok: var TToken) = if hasComment: tok.commentOffsetB = L.offsetBase + pos - 1 tok.tokType = tkComment + tok.indent = commentIndent if gIndentationWidth <= 0: gIndentationWidth = tok.indent @@ -1168,7 +1235,7 @@ proc rawGetTok*(L: var TLexer, tok: var TToken) = of 'r', 'R': if L.buf[L.bufpos + 1] == '\"': inc(L.bufpos) - getString(L, tok, true) + getString(L, tok, raw) else: getSymbol(L, tok) of '(': @@ -1246,9 +1313,9 @@ proc rawGetTok*(L: var TLexer, tok: var TToken) = lexMessage(L, errGenerated, "invalid token: " & c & " (\\" & $(ord(c)) & ')') of '\"': # check for generalized raw string literal: - var rawMode = L.bufpos > 0 and L.buf[L.bufpos-1] in SymChars - getString(L, tok, rawMode) - if rawMode: + let mode = if L.bufpos > 0 and L.buf[L.bufpos-1] in SymChars: generalized else: normal + getString(L, tok, mode) + if mode == generalized: # tkRStrLit -> tkGStrLit # tkTripleStrLit -> tkGTripleStrLit inc(tok.tokType, 2) diff --git a/compiler/lineinfos.nim b/compiler/lineinfos.nim index 8749e764d..b1ecf779e 100644 --- a/compiler/lineinfos.nim +++ b/compiler/lineinfos.nim @@ -41,7 +41,7 @@ type hintLineTooLong, hintXDeclaredButNotUsed, hintConvToBaseNotNeeded, hintConvFromXtoItselfNotNeeded, hintExprAlwaysX, hintQuitCalled, hintProcessing, hintCodeBegin, hintCodeEnd, hintConf, hintPath, - hintConditionAlwaysTrue, hintName, hintPattern, + hintConditionAlwaysTrue, hintConditionAlwaysFalse, hintName, hintPattern, hintExecuting, hintLinking, hintDependency, hintSource, hintPerformance, hintStackTrace, hintGCStats, hintGlobalVar, @@ -107,6 +107,7 @@ const hintConf: "used config file '$1'", hintPath: "added path: '$1'", hintConditionAlwaysTrue: "condition is always true: '$1'", + hintConditionAlwaysFalse: "condition is always false: '$1'", hintName: "name should be: '$1'", hintPattern: "$1", hintExecuting: "$1", @@ -140,7 +141,7 @@ const "Success", "SuccessX", "CC", "LineTooLong", "XDeclaredButNotUsed", "ConvToBaseNotNeeded", "ConvFromXtoItselfNotNeeded", "ExprAlwaysX", "QuitCalled", "Processing", "CodeBegin", "CodeEnd", "Conf", - "Path", "CondTrue", "Name", "Pattern", "Exec", "Link", "Dependency", + "Path", "CondTrue", "CondFalse", "Name", "Pattern", "Exec", "Link", "Dependency", "Source", "Performance", "StackTrace", "GCStats", "GlobalVar", "User", "UserRaw", "ExtendedContext", ] @@ -248,7 +249,7 @@ type ## some close token. errorOutputs*: TErrorOutputs - msgContext*: seq[TLineInfo] + msgContext*: seq[tuple[info: TLineInfo, detail: string]] lastError*: TLineInfo filenameToIndexTbl*: Table[string, FileIndex] fileInfos*: seq[TFileInfo] diff --git a/compiler/linter.nim b/compiler/linter.nim index 0b69db8cb..a881f2711 100644 --- a/compiler/linter.nim +++ b/compiler/linter.nim @@ -7,8 +7,7 @@ # distribution, for details about the copyright. # -## This module implements the code "prettifier". This is part of the toolchain -## to convert Nim code into a consistent style. +## This module implements the style checker. import strutils, os, intsets, strtabs @@ -23,9 +22,6 @@ proc identLen*(line: string, start: int): int = while start+result < line.len and line[start+result] in Letters: inc result -when false: - import prettybase - type StyleCheck* {.pure.} = enum None, Warn, Auto @@ -71,9 +67,9 @@ proc beautifyName(s: string, k: TSymKind): string = if s =~ ["int", "uint", "cint", "cuint", "clong", "cstring", "string", "char", "byte", "bool", "openArray", "seq", "array", "void", "pointer", "float", "csize", "cdouble", "cchar", "cschar", - "cshort", "cu", "nil", "expr", "stmt", "typedesc", "auto", "any", - "range", "openarray", "varargs", "set", "cfloat" - ]: + "cshort", "cu", "nil", "typedesc", "auto", "any", + "range", "openarray", "varargs", "set", "cfloat", "ref", "ptr", + "untyped", "typed", "static", "sink", "lent", "type"]: result.add s[i] else: result.add toUpperAscii(s[i]) @@ -122,6 +118,12 @@ proc replaceInFile(conf: ConfigRef; info: TLineInfo; newName: string) = system.shallowCopy(conf.m.fileInfos[info.fileIndex.int].lines[info.line.int-1], x) conf.m.fileInfos[info.fileIndex.int].dirty = true +proc lintReport(conf: ConfigRef; info: TLineInfo, beau: string) = + if optStyleError in conf.globalOptions: + localError(conf, info, "name should be: '$1'" % beau) + else: + message(conf, info, hintName, beau) + proc checkStyle(conf: ConfigRef; cache: IdentCache; info: TLineInfo, s: string, k: TSymKind; sym: PSym) = let beau = beautifyName(s, k) if s != beau: @@ -129,7 +131,7 @@ proc checkStyle(conf: ConfigRef; cache: IdentCache; info: TLineInfo, s: string, sym.name = getIdent(cache, beau) replaceInFile(conf, info, beau) else: - message(conf, info, hintName, beau) + lintReport(conf, info, beau) proc styleCheckDefImpl(conf: ConfigRef; cache: IdentCache; info: TLineInfo; s: PSym; k: TSymKind) = # operators stay as they are: @@ -142,12 +144,14 @@ proc nep1CheckDefImpl(conf: ConfigRef; info: TLineInfo; s: PSym; k: TSymKind) = # operators stay as they are: if k in {skResult, skTemp} or s.name.s[0] notin Letters: return if k in {skType, skGenericParam} and sfAnon in s.flags: return + if s.typ != nil and s.typ.kind == tyTypeDesc: return + if {sfImportc, sfExportc} * s.flags != {}: return let beau = beautifyName(s.name.s, k) if s.name.s != beau: - message(conf, info, hintName, beau) + lintReport(conf, info, beau) template styleCheckDef*(conf: ConfigRef; info: TLineInfo; s: PSym; k: TSymKind) = - if optCheckNep1 in conf.globalOptions: + if {optStyleHint, optStyleError} * conf.globalOptions != {}: nep1CheckDefImpl(conf, info, s, k) when defined(nimfix): if gStyleCheck != StyleCheck.None: styleCheckDefImpl(conf, cache, info, s, k) diff --git a/compiler/lookups.nim b/compiler/lookups.nim index d2e7fdcfa..2fb4e5241 100644 --- a/compiler/lookups.nim +++ b/compiler/lookups.nim @@ -168,7 +168,7 @@ proc ensureNoMissingOrUnusedSymbols(c: PContext; scope: PScope) = localError(c.config, s.info, "implementation of '$1' expected" % getSymRepr(c.config, s)) inc missingImpls - elif {sfUsed, sfExported} * s.flags == {} and optHints in s.options: + elif {sfUsed, sfExported} * s.flags == {}: if s.kind notin {skForVar, skParam, skMethod, skUnknown, skGenericParam}: # XXX: implicit type params are currently skTypes # maybe they can be made skGenericParam as well. diff --git a/compiler/lowerings.nim b/compiler/lowerings.nim index 1b17f620c..d199abcc7 100644 --- a/compiler/lowerings.nim +++ b/compiler/lowerings.nim @@ -78,7 +78,7 @@ proc lowerTupleUnpackingForAsgn*(g: ModuleGraph; n: PNode; owner: PSym): PNode = let value = n.lastSon result = newNodeI(nkStmtList, n.info) - var temp = newSym(skLet, getIdent(g.cache, "_"), owner, value.info, owner.options) + var temp = newSym(skTemp, getIdent(g.cache, "_"), owner, value.info, owner.options) var v = newNodeI(nkLetSection, value.info) let tempAsNode = newSymNode(temp) #newIdentNode(getIdent(genPrefix & $temp.id), value.info) @@ -281,15 +281,16 @@ proc genDeref*(n: PNode): PNode = n.typ.skipTypes(abstractInst).sons[0]) result.add n -proc callCodegenProc*(g: ModuleGraph; name: string, arg1: PNode; - arg2, arg3, optionalArgs: PNode = nil): PNode = - result = newNodeI(nkCall, arg1.info) +proc callCodegenProc*(g: ModuleGraph; name: string; + info: TLineInfo = unknownLineInfo(); + arg1, arg2, arg3, optionalArgs: PNode = nil): PNode = + result = newNodeI(nkCall, info) let sym = magicsys.getCompilerProc(g, name) if sym == nil: - localError(g.config, arg1.info, "system module needs: " & name) + localError(g.config, info, "system module needs: " & name) else: result.add newSymNode(sym) - result.add arg1 + if arg1 != nil: result.add arg1 if arg2 != nil: result.add arg2 if arg3 != nil: result.add arg3 if optionalArgs != nil: @@ -404,13 +405,16 @@ proc createWrapperProc(g: ModuleGraph; f: PNode; threadParam, argsParam: PSym; varSection, varInit, call, barrier, fv: PNode; spawnKind: TSpawnResult): PSym = var body = newNodeI(nkStmtList, f.info) + body.flags.incl nfTransf # do not transform further + var threadLocalBarrier: PSym if barrier != nil: var varSection2 = newNodeI(nkVarSection, barrier.info) threadLocalBarrier = addLocalVar(g, varSection2, nil, argsParam.owner, barrier.typ, barrier) body.add varSection2 - body.add callCodegenProc(g, "barrierEnter", threadLocalBarrier.newSymNode) + body.add callCodegenProc(g, "barrierEnter", threadLocalBarrier.info, + threadLocalBarrier.newSymNode) var threadLocalProm: PSym if spawnKind == srByVar: threadLocalProm = addLocalVar(g, varSection, nil, argsParam.owner, fv.typ, fv) @@ -425,7 +429,8 @@ proc createWrapperProc(g: ModuleGraph; f: PNode; threadParam, argsParam: PSym; body.add newAsgnStmt(indirectAccess(threadLocalProm.newSymNode, "owner", fv.info, g.cache), threadParam.newSymNode) - body.add callCodegenProc(g, "nimArgsPassingDone", threadParam.newSymNode) + body.add callCodegenProc(g, "nimArgsPassingDone", threadParam.info, + threadParam.newSymNode) if spawnKind == srByVar: body.add newAsgnStmt(genDeref(threadLocalProm.newSymNode), call) elif fv != nil: @@ -444,11 +449,13 @@ proc createWrapperProc(g: ModuleGraph; f: PNode; threadParam, argsParam: PSym; if barrier == nil: # by now 'fv' is shared and thus might have beeen overwritten! we need # to use the thread-local view instead: - body.add callCodegenProc(g, "nimFlowVarSignal", threadLocalProm.newSymNode) + body.add callCodegenProc(g, "nimFlowVarSignal", threadLocalProm.info, + threadLocalProm.newSymNode) else: body.add call if barrier != nil: - body.add callCodegenProc(g, "barrierLeave", threadLocalBarrier.newSymNode) + body.add callCodegenProc(g, "barrierLeave", threadLocalBarrier.info, + threadLocalBarrier.newSymNode) var params = newNodeI(nkFormalParams, f.info) params.add newNodeI(nkEmpty, f.info) @@ -710,7 +717,8 @@ proc wrapProcForSpawn*(g: ModuleGraph; owner: PSym; spawnExpr: PNode; retType: P # create flowVar: result.add newFastAsgnStmt(fvField, callProc(spawnExpr[^1])) if barrier == nil: - result.add callCodegenProc(g, "nimFlowVarCreateSemaphore", fvField) + result.add callCodegenProc(g, "nimFlowVarCreateSemaphore", fvField.info, + fvField) elif spawnKind == srByVar: var field = newSym(skField, getIdent(g.cache, "fv"), owner, n.info, g.config.options) @@ -723,7 +731,7 @@ proc wrapProcForSpawn*(g: ModuleGraph; owner: PSym; spawnExpr: PNode; retType: P let wrapper = createWrapperProc(g, fn, threadParam, argsParam, varSection, varInit, call, barrierAsExpr, fvAsExpr, spawnKind) - result.add callCodegenProc(g, "nimSpawn" & $spawnExpr.len, wrapper.newSymNode, - genAddrOf(scratchObj.newSymNode), nil, spawnExpr) + result.add callCodegenProc(g, "nimSpawn" & $spawnExpr.len, wrapper.info, + wrapper.newSymNode, genAddrOf(scratchObj.newSymNode), nil, spawnExpr) if spawnKind == srFlowVar: result.add fvField diff --git a/compiler/main.nim b/compiler/main.nim index 6c8b0343e..6afe57d87 100644 --- a/compiler/main.nim +++ b/compiler/main.nim @@ -15,12 +15,15 @@ when not defined(nimcore): import llstream, strutils, ast, astalgo, lexer, syntaxes, renderer, options, msgs, os, condsyms, times, - wordrecg, sem, semdata, idents, passes, docgen, extccomp, - cgen, jsgen, json, nversion, + wordrecg, sem, semdata, idents, passes, extccomp, + cgen, json, nversion, platform, nimconf, importer, passaux, depends, vm, vmdef, types, idgen, - docgen2, parser, modules, ccgutils, sigmatch, ropes, + parser, modules, ccgutils, sigmatch, ropes, modulegraphs, tables, rod, lineinfos, pathutils +when not defined(leanCompiler): + import jsgen, docgen, docgen2 + from magicsys import resetSysTypes proc codegenPass(g: ModuleGraph) = @@ -57,13 +60,14 @@ proc commandCheck(graph: ModuleGraph) = semanticPasses(graph) # use an empty backend for semantic checking only compileProject(graph) -proc commandDoc2(graph: ModuleGraph; json: bool) = - graph.config.errorMax = high(int) # do not stop after first error - semanticPasses(graph) - if json: registerPass(graph, docgen2JsonPass) - else: registerPass(graph, docgen2Pass) - compileProject(graph) - finishDoc2Pass(graph.config.projectName) +when not defined(leanCompiler): + proc commandDoc2(graph: ModuleGraph; json: bool) = + graph.config.errorMax = high(int) # do not stop after first error + semanticPasses(graph) + if json: registerPass(graph, docgen2JsonPass) + else: registerPass(graph, docgen2Pass) + compileProject(graph) + finishDoc2Pass(graph.config.projectName) proc commandCompileToC(graph: ModuleGraph) = let conf = graph.config @@ -84,15 +88,16 @@ proc commandJsonScript(graph: ModuleGraph) = let proj = changeFileExt(graph.config.projectFull, "") extccomp.runJsonBuildInstructions(graph.config, proj) -proc commandCompileToJS(graph: ModuleGraph) = - #incl(gGlobalOptions, optSafeCode) - setTarget(graph.config.target, osJS, cpuJS) - #initDefines() - defineSymbol(graph.config.symbols, "ecmascript") # For backward compatibility - defineSymbol(graph.config.symbols, "js") - semanticPasses(graph) - registerPass(graph, JSgenPass) - compileProject(graph) +when not defined(leanCompiler): + proc commandCompileToJS(graph: ModuleGraph) = + #incl(gGlobalOptions, optSafeCode) + setTarget(graph.config.target, osJS, cpuJS) + #initDefines() + defineSymbol(graph.config.symbols, "ecmascript") # For backward compatibility + defineSymbol(graph.config.symbols, "js") + semanticPasses(graph) + registerPass(graph, JSgenPass) + compileProject(graph) proc interactivePasses(graph: ModuleGraph) = initDefines(graph.config.symbols) @@ -177,49 +182,76 @@ proc mainCommand*(graph: ModuleGraph) = else: rawMessage(conf, errGenerated, "'run' command not available; rebuild with -d:tinyc") of "js", "compiletojs": - conf.cmd = cmdCompileToJS - commandCompileToJS(graph) + when defined(leanCompiler): + quit "compiler wasn't built with JS code generator" + else: + conf.cmd = cmdCompileToJS + commandCompileToJS(graph) of "doc0": - wantMainModule(conf) - conf.cmd = cmdDoc - loadConfigs(DocConfig, cache, conf) - commandDoc(cache, conf) + when defined(leanCompiler): + quit "compiler wasn't built with documentation generator" + else: + wantMainModule(conf) + conf.cmd = cmdDoc + loadConfigs(DocConfig, cache, conf) + commandDoc(cache, conf) of "doc2", "doc": - conf.cmd = cmdDoc - loadConfigs(DocConfig, cache, conf) - defineSymbol(conf.symbols, "nimdoc") - commandDoc2(graph, false) + when defined(leanCompiler): + quit "compiler wasn't built with documentation generator" + else: + conf.cmd = cmdDoc + loadConfigs(DocConfig, cache, conf) + defineSymbol(conf.symbols, "nimdoc") + commandDoc2(graph, false) of "rst2html": - conf.cmd = cmdRst2html - loadConfigs(DocConfig, cache, conf) - commandRst2Html(cache, conf) + when defined(leanCompiler): + quit "compiler wasn't built with documentation generator" + else: + conf.cmd = cmdRst2html + loadConfigs(DocConfig, cache, conf) + commandRst2Html(cache, conf) of "rst2tex": - conf.cmd = cmdRst2tex - loadConfigs(DocTexConfig, cache, conf) - commandRst2TeX(cache, conf) + when defined(leanCompiler): + quit "compiler wasn't built with documentation generator" + else: + conf.cmd = cmdRst2tex + loadConfigs(DocTexConfig, cache, conf) + commandRst2TeX(cache, conf) of "jsondoc0": - wantMainModule(conf) - conf.cmd = cmdDoc - loadConfigs(DocConfig, cache, conf) - wantMainModule(conf) - defineSymbol(conf.symbols, "nimdoc") - commandJson(cache, conf) + when defined(leanCompiler): + quit "compiler wasn't built with documentation generator" + else: + wantMainModule(conf) + conf.cmd = cmdDoc + loadConfigs(DocConfig, cache, conf) + wantMainModule(conf) + defineSymbol(conf.symbols, "nimdoc") + commandJson(cache, conf) of "jsondoc2", "jsondoc": - conf.cmd = cmdDoc - loadConfigs(DocConfig, cache, conf) - wantMainModule(conf) - defineSymbol(conf.symbols, "nimdoc") - commandDoc2(graph, true) + when defined(leanCompiler): + quit "compiler wasn't built with documentation generator" + else: + conf.cmd = cmdDoc + loadConfigs(DocConfig, cache, conf) + wantMainModule(conf) + defineSymbol(conf.symbols, "nimdoc") + commandDoc2(graph, true) of "ctags": - wantMainModule(conf) - conf.cmd = cmdDoc - loadConfigs(DocConfig, cache, conf) - defineSymbol(conf.symbols, "nimdoc") - commandTags(cache, conf) + when defined(leanCompiler): + quit "compiler wasn't built with documentation generator" + else: + wantMainModule(conf) + conf.cmd = cmdDoc + loadConfigs(DocConfig, cache, conf) + defineSymbol(conf.symbols, "nimdoc") + commandTags(cache, conf) of "buildindex": - conf.cmd = cmdDoc - loadConfigs(DocConfig, cache, conf) - commandBuildIndex(cache, conf) + when defined(leanCompiler): + quit "compiler wasn't built with documentation generator" + else: + conf.cmd = cmdDoc + loadConfigs(DocConfig, cache, conf) + commandBuildIndex(cache, conf) of "gendepend": conf.cmd = cmdGenDepend commandGenDepend(graph) @@ -265,6 +297,7 @@ proc mainCommand*(graph: ModuleGraph) = conf.cmd = cmdInteractive commandInteractive(graph) of "e": + incl conf.globalOptions, optWasNimscript commandEval(graph, mainCommandArg(conf)) of "nop", "help": # prevent the "success" message: diff --git a/compiler/modulegraphs.nim b/compiler/modulegraphs.nim index 1eecc4176..d05b301ae 100644 --- a/compiler/modulegraphs.nim +++ b/compiler/modulegraphs.nim @@ -62,9 +62,51 @@ type cacheSeqs*: Table[string, PNode] # state that is shared to suppor the 'macrocache' API cacheCounters*: Table[string, BiggestInt] cacheTables*: Table[string, BTree[string, PNode]] + passes*: seq[TPass] + onDefinition*: proc (graph: ModuleGraph; s: PSym; info: TLineInfo) {.nimcall.} + onDefinitionResolveForward*: proc (graph: ModuleGraph; s: PSym; info: TLineInfo) {.nimcall.} + onUsage*: proc (graph: ModuleGraph; s: PSym; info: TLineInfo) {.nimcall.} + + TPassContext* = object of RootObj # the pass's context + PPassContext* = ref TPassContext + + TPassOpen* = proc (graph: ModuleGraph; module: PSym): PPassContext {.nimcall.} + TPassClose* = proc (graph: ModuleGraph; p: PPassContext, n: PNode): PNode {.nimcall.} + TPassProcess* = proc (p: PPassContext, topLevelStmt: PNode): PNode {.nimcall.} + + TPass* = tuple[open: TPassOpen, + process: TPassProcess, + close: TPassClose, + isFrontend: bool] proc hash*(x: FileIndex): Hash {.borrow.} +when defined(nimfind): + template onUse*(info: TLineInfo; s: PSym) = + when compiles(c.c.graph): + if c.c.graph.onUsage != nil: c.c.graph.onUsage(c.c.graph, s, info) + else: + if c.graph.onUsage != nil: c.graph.onUsage(c.graph, s, info) + + template onDef*(info: TLineInfo; s: PSym) = + when compiles(c.c.graph): + if c.c.graph.onDefinition != nil: c.c.graph.onDefinition(c.c.graph, s, info) + else: + if c.graph.onDefinition != nil: c.graph.onDefinition(c.graph, s, info) + + template onDefResolveForward*(info: TLineInfo; s: PSym) = + when compiles(c.c.graph): + if c.c.graph.onDefinitionResolveForward != nil: + c.c.graph.onDefinitionResolveForward(c.c.graph, s, info) + else: + if c.graph.onDefinitionResolveForward != nil: + c.graph.onDefinitionResolveForward(c.graph, s, info) + +else: + template onUse*(info: TLineInfo; s: PSym) = discard + template onDef*(info: TLineInfo; s: PSym) = discard + template onDefResolveForward*(info: TLineInfo; s: PSym) = discard + proc stopCompile*(g: ModuleGraph): bool {.inline.} = result = g.doStopCompile != nil and g.doStopCompile() diff --git a/compiler/modules.nim b/compiler/modules.nim index 8fedba10a..442305a06 100644 --- a/compiler/modules.nim +++ b/compiler/modules.nim @@ -17,18 +17,7 @@ import proc resetSystemArtifacts*(g: ModuleGraph) = magicsys.resetSysTypes(g) -proc newModule(graph: ModuleGraph; fileIdx: FileIndex): PSym = - # We cannot call ``newSym`` here, because we have to circumvent the ID - # mechanism, which we do in order to assign each module a persistent ID. - new(result) - result.id = -1 # for better error checking - result.kind = skModule - let filename = toFullPath(graph.config, fileIdx) - result.name = getIdent(graph.cache, splitFile(filename).name) - if not isNimIdentifier(result.name.s): - rawMessage(graph.config, errGenerated, "invalid module name: " & result.name.s) - - result.info = newLineInfo(fileIdx, 1, 1) +proc partialInitModule(result: PSym; graph: ModuleGraph; fileIdx: FileIndex; filename: string) = let pck = getPackageName(graph.config, filename) pck2 = if pck.len > 0: pck else: "unknown" @@ -38,13 +27,11 @@ proc newModule(graph: ModuleGraph; fileIdx: FileIndex): PSym = packSym = newSym(skPackage, getIdent(graph.cache, pck2), nil, result.info) initStrTable(packSym.tab) graph.packageSyms.strTableAdd(packSym) - result.owner = packSym result.position = int fileIdx if int(fileIdx) >= graph.modules.len: setLen(graph.modules, int(fileIdx) + 1) - #growCache graph.modules, int fileIdx graph.modules[result.position] = result incl(result.flags, sfUsed) @@ -58,15 +45,36 @@ proc newModule(graph: ModuleGraph; fileIdx: FileIndex): PSym = # strTableIncl() for error corrections: discard strTableIncl(packSym.tab, result) +proc newModule(graph: ModuleGraph; fileIdx: FileIndex): PSym = + # We cannot call ``newSym`` here, because we have to circumvent the ID + # mechanism, which we do in order to assign each module a persistent ID. + new(result) + result.id = -1 # for better error checking + result.kind = skModule + let filename = toFullPath(graph.config, fileIdx) + result.name = getIdent(graph.cache, splitFile(filename).name) + if not isNimIdentifier(result.name.s): + rawMessage(graph.config, errGenerated, "invalid module name: " & result.name.s) + result.info = newLineInfo(fileIdx, 1, 1) + partialInitModule(result, graph, fileIdx, filename) + proc compileModule*(graph: ModuleGraph; fileIdx: FileIndex; flags: TSymFlags): PSym = result = graph.getModule(fileIdx) if result == nil: - result = newModule(graph, fileIdx) - result.flags = result.flags + flags - if sfMainModule in result.flags: - graph.config.mainPackageId = result.owner.id - - result.id = getModuleId(graph, fileIdx, toFullPath(graph.config, fileIdx)) + let filename = toFullPath(graph.config, fileIdx) + let (r, id) = loadModuleSym(graph, fileIdx, AbsoluteFile filename) + result = r + if result == nil: + result = newModule(graph, fileIdx) + result.flags = result.flags + flags + if sfMainModule in result.flags: + graph.config.mainPackageId = result.owner.id + result.id = id + registerModule(graph, result) + else: + partialInitModule(result, graph, fileIdx, filename) + result.id = id + assert result.id < 0 discard processModule(graph, result, if sfMainModule in flags and graph.config.projectIsStdin: stdin.llStreamOpen else: nil) elif graph.isDirty(result): @@ -128,6 +136,7 @@ proc compileProject*(graph: ModuleGraph; projectFileIdx = InvalidFileIDX) = proc makeModule*(graph: ModuleGraph; filename: AbsoluteFile): PSym = result = graph.newModule(fileInfoIdx(graph.config, filename)) result.id = getID() + registerModule(graph, result) proc makeModule*(graph: ModuleGraph; filename: string): PSym = result = makeModule(graph, AbsoluteFile filename) diff --git a/compiler/msgs.nim b/compiler/msgs.nim index d817b2956..7e6b67cbe 100644 --- a/compiler/msgs.nim +++ b/compiler/msgs.nim @@ -139,8 +139,8 @@ const proc getInfoContextLen*(conf: ConfigRef): int = return conf.m.msgContext.len proc setInfoContextLen*(conf: ConfigRef; L: int) = setLen(conf.m.msgContext, L) -proc pushInfoContext*(conf: ConfigRef; info: TLineInfo) = - conf.m.msgContext.add(info) +proc pushInfoContext*(conf: ConfigRef; info: TLineInfo; detail: string = "") = + conf.m.msgContext.add((info, detail)) proc popInfoContext*(conf: ConfigRef) = setLen(conf.m.msgContext, len(conf.m.msgContext) - 1) @@ -149,7 +149,7 @@ proc getInfoContext*(conf: ConfigRef; index: int): TLineInfo = let L = conf.m.msgContext.len let i = if index < 0: L + index else: index if i >=% L: result = unknownLineInfo() - else: result = conf.m.msgContext[i] + else: result = conf.m.msgContext[i].info template toFilename*(conf: ConfigRef; fileIdx: FileIndex): string = if fileIdx.int32 < 0 or conf == nil: @@ -190,10 +190,13 @@ template toFullPath*(conf: ConfigRef; info: TLineInfo): string = proc toMsgFilename*(conf: ConfigRef; info: TLineInfo): string = if info.fileIndex.int32 < 0: result = "???" - elif optListFullPaths in conf.globalOptions: - result = conf.m.fileInfos[info.fileIndex.int32].fullPath.string + return + let absPath = conf.m.fileInfos[info.fileIndex.int32].fullPath.string + let relPath = conf.m.fileInfos[info.fileIndex.int32].projPath.string + if optListFullPaths in conf.globalOptions: + result = absPath else: - result = conf.m.fileInfos[info.fileIndex.int32].projPath.string + result = if absPath.len < relPath.len: absPath else: relPath proc toLinenumber*(info: TLineInfo): int {.inline.} = result = int info.line @@ -340,20 +343,26 @@ proc exactEquals*(a, b: TLineInfo): bool = proc writeContext(conf: ConfigRef; lastinfo: TLineInfo) = const instantiationFrom = "template/generic instantiation from here" + const instantiationOfFrom = "template/generic instantiation of `$1` from here" var info = lastinfo for i in 0 ..< len(conf.m.msgContext): - if conf.m.msgContext[i] != lastinfo and conf.m.msgContext[i] != info: + let context = conf.m.msgContext[i] + if context.info != lastinfo and context.info != info: if conf.structuredErrorHook != nil: - conf.structuredErrorHook(conf, conf.m.msgContext[i], instantiationFrom, - Severity.Error) + conf.structuredErrorHook(conf, context.info, instantiationFrom, + Severity.Error) else: + let message = if context.detail == "": + instantiationFrom + else: + instantiationOfFrom.format(context.detail) styledMsgWriteln(styleBright, - PosFormat % [toMsgFilename(conf, conf.m.msgContext[i]), - coordToStr(conf.m.msgContext[i].line.int), - coordToStr(conf.m.msgContext[i].col+1)], + PosFormat % [toMsgFilename(conf, context.info), + coordToStr(context.info.line.int), + coordToStr(context.info.col+1)], resetStyle, - instantiationFrom) - info = conf.m.msgContext[i] + message) + info = context.info proc ignoreMsgBecauseOfIdeTools(conf: ConfigRef; msg: TMsgKind): bool = msg >= errGenerated and conf.cmd == cmdIdeTools and optIdeDebug notin conf.globalOptions diff --git a/compiler/nim.nim b/compiler/nim.nim index 5f3347255..1c4dbd3be 100644 --- a/compiler/nim.nim +++ b/compiler/nim.nim @@ -54,7 +54,8 @@ proc processCmdLine(pass: TCmdLinePass, cmd: string; config: ConfigRef) = of cmdArgument: if processArgument(pass, p, argsCount, config): break if pass == passCmd2: - if optRun notin config.globalOptions and config.arguments.len > 0 and config.command.normalize != "run": + if {optRun, optWasNimscript} * config.globalOptions == {} and + config.arguments.len > 0 and config.command.normalize notin ["run", "e"]: rawMessage(config, errGenerated, errArgsNeedRunOption) proc handleCmdLine(cache: IdentCache; conf: ConfigRef) = diff --git a/compiler/nimeval.nim b/compiler/nimeval.nim index 841c38a46..1d7157cdf 100644 --- a/compiler/nimeval.nim +++ b/compiler/nimeval.nim @@ -89,6 +89,13 @@ proc findNimStdLib*(): string = except OSError, ValueError: return "" +proc findNimStdLibCompileTime*(): string = + ## Same as ``findNimStdLib`` but uses source files used at compile time, + ## and asserts on error. + const sourcePath = currentSourcePath() + result = sourcePath.parentDir.parentDir / "lib" + doAssert fileExists(result / "system.nim"), "result:" & result + proc createInterpreter*(scriptName: string; searchPaths: openArray[string]; flags: TSandboxFlags = {}): Interpreter = diff --git a/compiler/options.nim b/compiler/options.nim index b4d2bb64e..80d665d62 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -45,7 +45,7 @@ type # please make sure we have under 32 options TOptions* = set[TOption] TGlobalOption* = enum # **keep binary compatible** gloptNone, optForceFullMake, - optDeadCodeElimUnused, # deprecated, always on + optWasNimscript, optListCmd, optCompileOnly, optNoLinking, optCDebug, # turn on debugging information optGenDynLib, # generate a dynamic library @@ -54,7 +54,8 @@ type # please make sure we have under 32 options optGenScript, # generate a script file to compile the *.c files optGenMapping, # generate a mapping file optRun, # run the compiled project - optCheckNep1, # check that the names adhere to NEP-1 + optStyleHint, # check that the names adhere to NEP-1 + optStyleError, # enforce that the names adhere to NEP-1 optSkipSystemConfigFile, # skip the system's cfg/nims config file optSkipProjConfigFile, # skip the project's cfg/nims config file optSkipUserConfigFile, # skip the users's cfg/nims config file @@ -73,6 +74,7 @@ type # please make sure we have under 32 options optIdeTerse # idetools: use terse descriptions optNoCppExceptions # use C exception handling even with CPP optExcessiveStackTrace # fully qualified module filenames + optShowAllMismatches # show all overloading resolution candidates optWholeProject # for 'doc2': output any dependency optMixedMode # true if some module triggered C++ codegen optListFullPaths @@ -104,8 +106,10 @@ type cmdJsonScript # compile a .json build file TStringSeq* = seq[string] TGCMode* = enum # the selected GC - gcNone, gcBoehm, gcGo, gcRegions, gcMarkAndSweep, gcDestructors, - gcRefc, gcV2 + gcNone, gcBoehm, gcRegions, gcMarkAndSweep, gcDestructors, + gcRefc, gcV2, gcGo + # gcRefc and the GCs that follow it use a write barrier, + # as far as usesWriteBarrier() is concerned IdeCmd* = enum ideNone, ideSug, ideCon, ideDef, ideUse, ideDus, ideChk, ideMod, @@ -398,7 +402,8 @@ proc importantComments*(conf: ConfigRef): bool {.inline.} = conf.cmd in {cmdDoc, proc usesWriteBarrier*(conf: ConfigRef): bool {.inline.} = conf.selectedGC >= gcRefc template compilationCachePresent*(conf: ConfigRef): untyped = - conf.symbolFiles in {v2Sf, writeOnlySf} + false +# conf.symbolFiles in {v2Sf, writeOnlySf} template optPreserveOrigSource*(conf: ConfigRef): untyped = optEmbedOrigSrc in conf.globalOptions diff --git a/compiler/parser.nim b/compiler/parser.nim index 69e372e4e..54b360a24 100644 --- a/compiler/parser.nim +++ b/compiler/parser.nim @@ -37,8 +37,7 @@ type TParser* = object # A TParser object represents a file that # is being parsed currInd: int # current indentation level - firstTok, strongSpaces: bool # Has the first token been read? - # Is strongSpaces on? + firstTok: bool # Has the first token been read? hasProgress: bool # some while loop requires progress ensurance lex*: TLexer # The lexer that is used for parsing tok*: TToken # The current token @@ -46,7 +45,7 @@ type inSemiStmtList*: int emptyNode: PNode when defined(nimpretty2): - em: Emitter + em*: Emitter SymbolMode = enum smNormal, smAllowNil, smAfterDot @@ -102,8 +101,7 @@ proc getTok(p: var TParser) = emitTok(p.em, p.lex, p.tok) proc openParser*(p: var TParser, fileIdx: FileIndex, inputStream: PLLStream, - cache: IdentCache; config: ConfigRef; - strongSpaces=false) = + cache: IdentCache; config: ConfigRef) = ## Open a parser, using the given arguments to set up its internal state. ## initToken(p.tok) @@ -112,13 +110,11 @@ proc openParser*(p: var TParser, fileIdx: FileIndex, inputStream: PLLStream, openEmitter(p.em, cache, config, fileIdx) getTok(p) # read the first token p.firstTok = true - p.strongSpaces = strongSpaces p.emptyNode = newNode(nkEmpty) proc openParser*(p: var TParser, filename: AbsoluteFile, inputStream: PLLStream, - cache: IdentCache; config: ConfigRef; - strongSpaces=false) = - openParser(p, fileInfoIdx(config, filename), inputStream, cache, config, strongSpaces) + cache: IdentCache; config: ConfigRef) = + openParser(p, fileInfoIdx(config, filename), inputStream, cache, config) proc closeParser(p: var TParser) = ## Close a parser, freeing up its resources. @@ -286,7 +282,7 @@ proc checkBinary(p: TParser) {.inline.} = #| #| prefixOperator = operator #| -#| optInd = COMMENT? +#| optInd = COMMENT? IND? #| optPar = (IND{>} | IND{=})? #| #| simpleExpr = arrowExpr (OP0 optInd arrowExpr)* pragma? @@ -602,7 +598,7 @@ proc identOrLiteral(p: var TParser, mode: TPrimaryMode): PNode = #| tupleConstr = '(' optInd (exprColonEqExpr comma?)* optPar ')' #| arrayConstr = '[' optInd (exprColonEqExpr comma?)* optPar ']' case p.tok.tokType - of tkSymbol, tkBuiltInMagics: + of tkSymbol, tkBuiltInMagics, tkOut: result = newIdentNodeP(p.tok.ident, p) getTok(p) result = parseGStrLit(p, result) @@ -706,8 +702,11 @@ proc namedParams(p: var TParser, callee: PNode, # progress guaranteed exprColonEqExprListAux(p, endTok, result) -proc commandParam(p: var TParser, isFirstParam: var bool): PNode = - result = parseExpr(p) +proc commandParam(p: var TParser, isFirstParam: var bool; mode: TPrimaryMode): PNode = + if mode == pmTypeDesc: + result = simpleExpr(p, mode) + else: + result = parseExpr(p) if p.tok.tokType == tkDo: result = postExprBlocks(p, result) elif p.tok.tokType == tkEquals and not isFirstParam: @@ -780,7 +779,7 @@ proc primarySuffix(p: var TParser, r: PNode, when true: # progress NOT guaranteed p.hasProgress = false - addSon result, commandParam(p, isFirstParam) + addSon result, commandParam(p, isFirstParam, mode) if not p.hasProgress: break else: while p.tok.tokType != tkEof: @@ -798,7 +797,7 @@ proc parseOperators(p: var TParser, headNode: PNode, limit: int, mode: TPrimaryMode): PNode = result = headNode # expand while operators have priorities higher than 'limit' - var opPrec = getPrecedence(p.tok, p.strongSpaces) + var opPrec = getPrecedence(p.tok, false) let modeB = if mode == pmTypeDef: pmTypeDesc else: mode # the operator itself must not start on a new line: # progress guaranteed @@ -815,7 +814,7 @@ proc parseOperators(p: var TParser, headNode: PNode, addSon(a, result) addSon(a, b) result = a - opPrec = getPrecedence(p.tok, p.strongSpaces) + opPrec = getPrecedence(p.tok, false) proc simpleExprAux(p: var TParser, limit: int, mode: TPrimaryMode): PNode = result = primary(p, mode) @@ -1037,6 +1036,7 @@ proc parseParamList(p: var TParser, retColon = true): PNode = addSon(result, p.emptyNode) # return type when defined(nimpretty2): inc p.em.doIndentMore + inc p.em.keepIndents let hasParLe = p.tok.tokType == tkParLe and p.tok.indent < 0 if hasParLe: getTok(p) @@ -1073,6 +1073,7 @@ proc parseParamList(p: var TParser, retColon = true): PNode = result = p.emptyNode when defined(nimpretty2): dec p.em.doIndentMore + dec p.em.keepIndents proc optPragmas(p: var TParser): PNode = if p.tok.tokType == tkCurlyDotLe and (p.tok.indent < 0 or realInd(p)): @@ -1244,7 +1245,6 @@ proc primary(p: var TParser, mode: TPrimaryMode): PNode = optInd(p, result) addSon(result, primary(p, pmNormal)) of tkVar: result = parseTypeDescKAux(p, nkVarTy, mode) - of tkOut: result = parseTypeDescKAux(p, nkVarTy, mode) of tkRef: result = parseTypeDescKAux(p, nkRefTy, mode) of tkPtr: result = parseTypeDescKAux(p, nkPtrTy, mode) of tkDistinct: result = parseTypeDescKAux(p, nkDistinctTy, mode) @@ -1254,14 +1254,29 @@ proc primary(p: var TParser, mode: TPrimaryMode): PNode = if mode != pmSkipSuffix: result = primarySuffix(p, result, baseInd, mode) +proc binaryNot(p: var TParser; a: PNode): PNode = + if p.tok.tokType == tkNot: + let notOpr = newIdentNodeP(p.tok.ident, p) + getTok(p) + optInd(p, notOpr) + let b = parseExpr(p) + result = newNodeP(nkInfix, p) + result.add notOpr + result.add a + result.add b + else: + result = a + proc parseTypeDesc(p: var TParser): PNode = - #| typeDesc = simpleExpr + #| typeDesc = simpleExpr ('not' expr)? result = simpleExpr(p, pmTypeDesc) + result = binaryNot(p, result) proc parseTypeDefAux(p: var TParser): PNode = - #| typeDefAux = simpleExpr + #| typeDefAux = simpleExpr ('not' expr)? #| | 'concept' typeClass result = simpleExpr(p, pmTypeDef) + result = binaryNot(p, result) proc makeCall(n: PNode): PNode = ## Creates a call if the given node isn't already a call. @@ -1369,12 +1384,12 @@ proc parseExprStmt(p: var TParser): PNode = while true: getTok(p) optInd(p, result) - addSon(result, commandParam(p, isFirstParam)) + addSon(result, commandParam(p, isFirstParam, pmNormal)) if p.tok.tokType != tkComma: break elif p.tok.indent < 0 and isExprStart(p): result = newNode(nkCommand, a.info, @[a]) while true: - addSon(result, commandParam(p, isFirstParam)) + addSon(result, commandParam(p, isFirstParam, pmNormal)) if p.tok.tokType != tkComma: break getTok(p) optInd(p, result) @@ -1578,7 +1593,6 @@ proc parseTry(p: var TParser; isExpr: bool): PNode = colcom(p, b) addSon(b, parseStmt(p)) addSon(result, b) - if b.kind == nkFinally: break if b == nil: parMessage(p, "expected 'except'") proc parseExceptBlock(p: var TParser, kind: TNodeKind): PNode = @@ -2236,10 +2250,8 @@ proc parseString*(s: string; cache: IdentCache; config: ConfigRef; stream.lineOffset = line var parser: TParser - # XXX for now the builtin 'parseStmt/Expr' functions do not know about strong - # spaces... parser.lex.errorHandler = errorHandler - openParser(parser, AbsoluteFile filename, stream, cache, config, false) + openParser(parser, AbsoluteFile filename, stream, cache, config) result = parser.parseAll closeParser(parser) diff --git a/compiler/passaux.nim b/compiler/passaux.nim index eabce8822..09f656d58 100644 --- a/compiler/passaux.nim +++ b/compiler/passaux.nim @@ -12,10 +12,10 @@ import strutils, ast, astalgo, passes, idents, msgs, options, idgen, lineinfos -from modulegraphs import ModuleGraph +from modulegraphs import ModuleGraph, PPassContext type - VerboseRef = ref object of TPassContext + VerboseRef = ref object of PPassContext config: ConfigRef proc verboseOpen(graph: ModuleGraph; s: PSym): PPassContext = diff --git a/compiler/passes.nim b/compiler/passes.nim index 718b42c2a..9ccd2240a 100644 --- a/compiler/passes.nim +++ b/compiler/passes.nim @@ -16,21 +16,8 @@ import nimsets, syntaxes, times, idgen, modulegraphs, reorder, rod, lineinfos, pathutils - type - TPassContext* = object of RootObj # the pass's context - - PPassContext* = ref TPassContext - - TPassOpen* = proc (graph: ModuleGraph; module: PSym): PPassContext {.nimcall.} - TPassClose* = proc (graph: ModuleGraph; p: PPassContext, n: PNode): PNode {.nimcall.} - TPassProcess* = proc (p: PPassContext, topLevelStmt: PNode): PNode {.nimcall.} - - TPass* = tuple[open: TPassOpen, process: TPassProcess, close: TPassClose, - isFrontend: bool] - TPassData* = tuple[input: PNode, closeOutput: PNode] - TPasses* = openArray[TPass] # a pass is a tuple of procedure vars ``TPass.close`` may produce additional # nodes. These are passed to the other close procedures. @@ -57,16 +44,12 @@ const type TPassContextArray = array[0..maxPasses - 1, PPassContext] -var - gPasses: array[0..maxPasses - 1, TPass] - gPassesLen*: int - proc clearPasses*(g: ModuleGraph) = - gPassesLen = 0 + g.passes.setLen(0) proc registerPass*(g: ModuleGraph; p: TPass) = - gPasses[gPassesLen] = p - inc(gPassesLen) + internalAssert g.config, g.passes.len < maxPasses + g.passes.add(p) proc carryPass*(g: ModuleGraph; p: TPass, module: PSym; m: TPassData): TPassData = @@ -76,7 +59,7 @@ proc carryPass*(g: ModuleGraph; p: TPass, module: PSym; else: m.closeOutput proc carryPasses*(g: ModuleGraph; nodes: PNode, module: PSym; - passes: TPasses) = + passes: openArray[TPass]) = var passdata: TPassData passdata.input = nodes for pass in passes: @@ -84,23 +67,23 @@ proc carryPasses*(g: ModuleGraph; nodes: PNode, module: PSym; proc openPasses(g: ModuleGraph; a: var TPassContextArray; module: PSym) = - for i in countup(0, gPassesLen - 1): - if not isNil(gPasses[i].open): - a[i] = gPasses[i].open(g, module) + for i in countup(0, g.passes.len - 1): + if not isNil(g.passes[i].open): + a[i] = g.passes[i].open(g, module) else: a[i] = nil proc closePasses(graph: ModuleGraph; a: var TPassContextArray) = var m: PNode = nil - for i in countup(0, gPassesLen - 1): - if not isNil(gPasses[i].close): m = gPasses[i].close(graph, a[i], m) + for i in countup(0, graph.passes.len - 1): + if not isNil(graph.passes[i].close): m = graph.passes[i].close(graph, a[i], m) a[i] = nil # free the memory here -proc processTopLevelStmt(n: PNode, a: var TPassContextArray): bool = +proc processTopLevelStmt(graph: ModuleGraph, n: PNode, a: var TPassContextArray): bool = # this implements the code transformation pipeline var m = n - for i in countup(0, gPassesLen - 1): - if not isNil(gPasses[i].process): - m = gPasses[i].process(a[i], m) + for i in countup(0, graph.passes.len - 1): + if not isNil(graph.passes[i].process): + m = graph.passes[i].process(a[i], m) if isNil(m): return false result = true @@ -111,19 +94,19 @@ proc resolveMod(conf: ConfigRef; module, relativeTo: string): FileIndex = else: result = fileInfoIdx(conf, fullPath) -proc processImplicits(conf: ConfigRef; implicits: seq[string], nodeKind: TNodeKind, +proc processImplicits(graph: ModuleGraph; implicits: seq[string], nodeKind: TNodeKind, a: var TPassContextArray; m: PSym) = # XXX fixme this should actually be relative to the config file! let gCmdLineInfo = newLineInfo(FileIndex(0), 1, 1) - let relativeTo = toFullPath(conf, m.info) + let relativeTo = toFullPath(graph.config, m.info) for module in items(implicits): # implicit imports should not lead to a module importing itself - if m.position != resolveMod(conf, module, relativeTo).int32: + if m.position != resolveMod(graph.config, module, relativeTo).int32: var importStmt = newNodeI(nodeKind, gCmdLineInfo) var str = newStrNode(nkStrLit, module) str.info = gCmdLineInfo importStmt.addSon str - if not processTopLevelStmt(importStmt, a): break + if not processTopLevelStmt(graph, importStmt, a): break const imperativeCode = {low(TNodeKind)..high(TNodeKind)} - {nkTemplateDef, nkProcDef, nkMethodDef, @@ -139,25 +122,25 @@ proc processModule*(graph: ModuleGraph; module: PSym, stream: PLLStream): bool { fileIdx = module.fileIdx if module.id < 0: # new module caching mechanism: - for i in 0..<gPassesLen: - if not isNil(gPasses[i].open) and not gPasses[i].isFrontend: - a[i] = gPasses[i].open(graph, module) + for i in 0 ..< graph.passes.len: + if not isNil(graph.passes[i].open) and not graph.passes[i].isFrontend: + a[i] = graph.passes[i].open(graph, module) else: a[i] = nil if not graph.stopCompile(): let n = loadNode(graph, module) var m = n - for i in 0..<gPassesLen: - if not isNil(gPasses[i].process) and not gPasses[i].isFrontend: - m = gPasses[i].process(a[i], m) + for i in 0 ..< graph.passes.len: + if not isNil(graph.passes[i].process) and not graph.passes[i].isFrontend: + m = graph.passes[i].process(a[i], m) if isNil(m): break var m: PNode = nil - for i in 0..<gPassesLen: - if not isNil(gPasses[i].close) and not gPasses[i].isFrontend: - m = gPasses[i].close(graph, a[i], m) + for i in 0 ..< graph.passes.len: + if not isNil(graph.passes[i].close) and not graph.passes[i].isFrontend: + m = graph.passes[i].close(graph, a[i], m) a[i] = nil else: openPasses(graph, a, module) @@ -177,8 +160,8 @@ proc processModule*(graph: ModuleGraph; module: PSym, stream: PLLStream): bool { # modules to include between compilation runs? we'd need to track that # in ROD files. I think we should enable this feature only # for the interactive mode. - processImplicits graph.config, graph.config.implicitImports, nkImportStmt, a, module - processImplicits graph.config, graph.config.implicitIncludes, nkIncludeStmt, a, module + processImplicits graph, graph.config.implicitImports, nkImportStmt, a, module + processImplicits graph, graph.config.implicitIncludes, nkIncludeStmt, a, module while true: if graph.stopCompile(): break @@ -194,7 +177,7 @@ proc processModule*(graph: ModuleGraph; module: PSym, stream: PLLStream): bool { sl.add n if sfReorder in module.flags: sl = reorder(graph, sl, module) - discard processTopLevelStmt(sl, a) + discard processTopLevelStmt(graph, sl, a) break elif n.kind in imperativeCode: # read everything until the next proc declaration etc. @@ -208,13 +191,13 @@ proc processModule*(graph: ModuleGraph; module: PSym, stream: PLLStream): bool { break sl.add n #echo "-----\n", sl - if not processTopLevelStmt(sl, a): break + if not processTopLevelStmt(graph, sl, a): break if rest != nil: #echo "-----\n", rest - if not processTopLevelStmt(rest, a): break + if not processTopLevelStmt(graph, rest, a): break else: #echo "----- single\n", n - if not processTopLevelStmt(n, a): break + if not processTopLevelStmt(graph, n, a): break closeParsers(p) if s.kind != llsStdIn: break closePasses(graph, a) diff --git a/compiler/pathutils.nim b/compiler/pathutils.nim index 03fcfe07e..703467bc4 100644 --- a/compiler/pathutils.nim +++ b/compiler/pathutils.nim @@ -73,23 +73,6 @@ iterator dirs(x: string): (int, int) = var it: PathIter while hasNext(it, x): yield next(it, x) -when false: - iterator dirs(x: string): (int, int) = - var i = 0 - var first = true - while i < x.len: - let prev = i - if first and x[i] in {DirSep, AltSep}: - # absolute path: - inc i - else: - while i < x.len and x[i] notin {DirSep, AltSep}: inc i - if i > prev: - yield (prev, i-1) - first = false - # skip all separators: - while i < x.len and x[i] in {DirSep, AltSep}: inc i - proc isDot(x: string; bounds: (int, int)): bool = bounds[1] == bounds[0] and x[bounds[0]] == '.' @@ -99,24 +82,26 @@ proc isDotDot(x: string; bounds: (int, int)): bool = proc isSlash(x: string; bounds: (int, int)): bool = bounds[1] == bounds[0] and x[bounds[0]] in {DirSep, AltSep} +const canonDirSep = when isMainModule: '/' else: DirSep + proc canon(x: string; result: var string; state: var int) = # state: 0th bit set if isAbsolute path. Other bits count # the number of path components. for b in dirs(x): if (state shr 1 == 0) and isSlash(x, b): - result.add DirSep + result.add canonDirSep state = state or 1 elif result.len > (state and 1) and isDotDot(x, b): var d = result.len # f/.. - while d > (state and 1) and result[d-1] != DirSep: + while (d-1) > (state and 1) and result[d-1] notin {DirSep, AltSep}: dec d if d > 0: setLen(result, d-1) elif isDot(x, b): discard "discard the dot" elif b[1] >= b[0]: - if result.len > 0 and result[^1] != DirSep: - result.add DirSep + if result.len > 0 and result[^1] notin {DirSep, AltSep}: + result.add canonDirSep result.add substr(x, b[0], b[1]) inc state, 2 @@ -133,7 +118,7 @@ when FileSystemCaseSensitive: else: template `!=?`(a, b: char): bool = a != b -proc relativeTo(full, base: string; sep = DirSep): string = +proc relativeTo(full, base: string; sep = canonDirSep): string = if full.len == 0: return "" var f, b: PathIter var ff = (0, -1) @@ -207,7 +192,7 @@ when true: canon(f.string, result.string, state) proc relativeTo*(fullPath: AbsoluteFile, baseFilename: AbsoluteDir; - sep = DirSep): RelativeFile = + sep = canonDirSep): RelativeFile = RelativeFile(relativeTo(fullPath.string, baseFilename.string, sep)) proc toAbsolute*(file: string; base: AbsoluteDir): AbsoluteFile = @@ -222,7 +207,7 @@ when true: proc writeFile*(x: AbsoluteFile; content: string) {.borrow.} -when isMainModule and defined(posix): +when isMainModule: doAssert canon"/foo/../bar" == "/bar" doAssert canon"foo/../bar" == "bar" @@ -257,4 +242,4 @@ when isMainModule and defined(posix): when isMainModule and defined(windows): let nasty = string(AbsoluteDir(r"C:\Users\rumpf\projects\nim\tests\nimble\nimbleDir\linkedPkgs\pkgB-#head\../../simplePkgs/pkgB-#head/") / RelativeFile"pkgA/module.nim") - doAssert nasty == r"C:\Users\rumpf\projects\nim\tests\nimble\nimbleDir\simplePkgs\pkgB-#head\pkgA\module.nim" + doAssert nasty.replace('/', '\\') == r"C:\Users\rumpf\projects\nim\tests\nimble\nimbleDir\simplePkgs\pkgB-#head\pkgA\module.nim" diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim index 9a624fcce..3d8e5645b 100644 --- a/compiler/pragmas.nim +++ b/compiler/pragmas.nim @@ -38,7 +38,7 @@ const wImportc, wExportc, wNodecl, wMagic, wDeprecated, wBorrow, wExtern, wImportCpp, wImportObjC, wError, wDiscardable, wGensym, wInject, wRaises, wTags, wLocks, wGcSafe, wExportNims, wUsed} - exprPragmas* = {wLine, wLocks, wNoRewrite, wGcSafe} + exprPragmas* = {wLine, wLocks, wNoRewrite, wGcSafe, wNosideeffect} stmtPragmas* = {wChecks, wObjChecks, wFieldChecks, wRangechecks, wBoundchecks, wOverflowchecks, wNilchecks, wMovechecks, wAssertions, wWarnings, wHints, @@ -598,14 +598,7 @@ proc pragmaLine(c: PContext, n: PNode) = elif y.kind != nkIntLit: localError(c.config, n.info, errIntLiteralExpected) else: - if c.config.projectPath.isEmpty: - n.info.fileIndex = fileInfoIdx(c.config, AbsoluteFile(x.strVal)) - else: - # XXX this is still suspicous: - let dir = toFullPath(c.config, n.info).splitFile.dir - let rel = if isAbsolute(x.strVal): relativeTo(AbsoluteFile(x.strVal), c.config.projectPath) - else: RelativeFile(x.strVal) - n.info.fileIndex = fileInfoIdx(c.config, AbsoluteDir(dir) / rel) + n.info.fileIndex = fileInfoIdx(c.config, AbsoluteFile(x.strVal)) n.info.line = uint16(y.intVal) else: localError(c.config, n.info, "tuple expected") @@ -730,13 +723,13 @@ proc semCustomPragma(c: PContext, n: PNode): PNode = elif n.kind == nkExprColonExpr: # pragma: arg -> pragma(arg) result = newTree(nkCall, n[0], n[1]) - elif n.kind in nkPragmaCallKinds + {nkIdent}: + elif n.kind in nkPragmaCallKinds: result = n else: invalidPragma(c, n) return n - let r = c.semOverloadedCall(c, result, n, {skTemplate}, {}) + let r = c.semOverloadedCall(c, result, n, {skTemplate}, {efNoUndeclared}) if r.isNil or sfCustomPragma notin r[0].sym.flags: invalidPragma(c, n) else: @@ -810,9 +803,11 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int, if sym.typ == nil: invalidPragma(c, it) var size = expectIntLit(c, it) if not isPowerOfTwo(size) or size <= 0 or size > 8: - localError(c.config, it.info, "power of two expected") + localError(c.config, it.info, "size may only be 1, 2, 4 or 8") else: sym.typ.size = size + # TODO, this is not correct + sym.typ.align = int16(size) of wNodecl: noVal(c, it) incl(sym.loc.flags, lfNoDecl) @@ -860,8 +855,9 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int, sym.flags.incl sfOverriden of wNosideeffect: noVal(c, it) - incl(sym.flags, sfNoSideEffect) - if sym.typ != nil: incl(sym.typ.flags, tfNoSideEffect) + if sym != nil: + incl(sym.flags, sfNoSideEffect) + if sym.typ != nil: incl(sym.typ.flags, tfNoSideEffect) of wSideeffect: noVal(c, it) incl(sym.flags, sfSideEffect) @@ -954,13 +950,14 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int, recordPragma(c, it, "warning", s) message(c.config, it.info, warnUser, s) of wError: - if sym != nil and (sym.isRoutine or sym.kind == skType): + if sym != nil and (sym.isRoutine or sym.kind == skType) and wUsed in validPragmas: # This is subtle but correct: the error *statement* is only - # allowed for top level statements. Seems to be easier than - # distinguishing properly between + # allowed when 'wUsed' is not in validPragmas. Here this is the easiest way to + # distinguish properly between # ``proc p() {.error}`` and ``proc p() = {.error: "msg".}`` if it.kind in nkPragmaCallKinds: discard getStrLitNode(c, it) incl(sym.flags, sfError) + excl(sym.flags, sfForward) else: let s = expectStrLit(c, it) recordPragma(c, it, "error", s) @@ -1023,8 +1020,10 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int, else: incl(sym.typ.flags, tfIncompleteStruct) of wUnchecked: noVal(c, it) - if sym.typ == nil: invalidPragma(c, it) - else: incl(sym.typ.flags, tfUncheckedArray) + if sym.typ == nil or sym.typ.kind notin {tyArray, tyUncheckedArray}: + invalidPragma(c, it) + else: + sym.typ.kind = tyUncheckedArray of wUnion: noVal(c, it) if sym.typ == nil: invalidPragma(c, it) @@ -1064,6 +1063,8 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int, invalidPragma(c, it) else: sym.bitsize = expectIntLit(c, it) + if sym.bitsize <= 0: + localError(c.config, it.info, "bitsize needs to be positive") of wGuard: if sym == nil or sym.kind notin {skVar, skLet, skField}: invalidPragma(c, it) @@ -1110,10 +1111,19 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int, else: sym.flags.incl sfUsed of wLiftLocals: discard else: invalidPragma(c, it) - elif sym.kind in {skVar,skLet,skParam,skField,skProc,skFunc,skConverter,skMethod,skType}: + elif sym != nil and sym.kind in {skVar, skLet, skParam, skField, skProc, + skFunc, skConverter, skMethod, skType}: n.sons[i] = semCustomPragma(c, it) - else: + elif sym != nil: illegalCustomPragma(c, it, sym) + else: + invalidPragma(c, it) + +proc mergePragmas(n, pragmas: PNode) = + if n[pragmasPos].kind == nkEmpty: + n[pragmasPos] = pragmas + else: + for p in pragmas: n.sons[pragmasPos].add p proc implicitPragmas*(c: PContext, sym: PSym, n: PNode, validPragmas: TSpecialWords) = @@ -1123,11 +1133,12 @@ proc implicitPragmas*(c: PContext, sym: PSym, n: PNode, if not o.isNil: pushInfoContext(c.config, n.info) var i = 0 - while i < o.len(): + while i < o.len: if singlePragma(c, sym, o, i, validPragmas): internalError(c.config, n.info, "implicitPragmas") inc i popInfoContext(c.config) + if sym.kind in routineKinds: mergePragmas(sym.ast, o) if lfExportLib in sym.loc.flags and sfExportc notin sym.flags: localError(c.config, n.info, ".dynlib requires .exportc") diff --git a/compiler/renderer.nim b/compiler/renderer.nim index 964af0591..ce47cf219 100644 --- a/compiler/renderer.nim +++ b/compiler/renderer.nim @@ -319,7 +319,7 @@ proc litAux(g: TSrcGen; n: PNode, x: BiggestInt, size: int): string = let enumfields = typ.n # we need a slow linear search because of enums with holes: for e in items(enumfields): - if e.sym.position == x: + if e.sym.position == x: result &= e.sym.name.s return @@ -868,19 +868,19 @@ proc isBracket*(n: PNode): bool = of nkSym: result = n.sym.name.s == "[]" else: result = false -proc skipHiddenNodes(n: PNode): PNode = +proc skipHiddenNodes(n: PNode): PNode = result = n while result != nil: - if result.kind in {nkHiddenStdConv, nkHiddenSubConv, nkHiddenCallConv} and result.len > 1: + if result.kind in {nkHiddenStdConv, nkHiddenSubConv, nkHiddenCallConv} and result.len > 1: result = result[1] elif result.kind in {nkCheckedFieldExpr, nkHiddenAddr, nkHiddenDeref, nkStringToCString, nkCStringToString} and - result.len > 0: + result.len > 0: result = result[0] else: break proc accentedName(g: var TSrcGen, n: PNode) = if n == nil: return - let isOperator = + let isOperator = if n.kind == nkIdent and n.ident.s.len > 0 and n.ident.s[0] in OpChars: true elif n.kind == nkSym and n.sym.name.s.len > 0 and n.sym.name.s[0] in OpChars: true else: false @@ -900,7 +900,7 @@ proc infixArgument(g: var TSrcGen, n: PNode, i: int) = if n_next.kind == nkInfix: if n_next[0].kind in {nkSym, nkIdent} and n[0].kind in {nkSym, nkIdent}: let nextId = if n_next[0].kind == nkSym: n_next[0].sym.name else: n_next[0].ident - let nnId = if n[0].kind == nkSym: n[0].sym.name else: n[0].ident + let nnId = if n[0].kind == nkSym: n[0].sym.name else: n[0].ident if getPrecedence(nextId) < getPrecedence(nnId): needsParenthesis = true if needsParenthesis: @@ -908,7 +908,7 @@ proc infixArgument(g: var TSrcGen, n: PNode, i: int) = gsub(g, n, i) if needsParenthesis: put(g, tkParRi, ")") - + proc gsub(g: var TSrcGen, n: PNode, c: TContext) = if isNil(n): return var @@ -1115,7 +1115,7 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) = infixArgument(g, n, 1) put(g, tkSpaces, Space) gsub(g, n, 0) # binary operator - if not fits(g, lsub(g, n.sons[2]) + lsub(g, n.sons[0]) + 1): + if n.len == 3 and not fits(g, lsub(g, n.sons[2]) + lsub(g, n.sons[0]) + 1): optNL(g, g.indent + longIndentWid) else: put(g, tkSpaces, Space) diff --git a/compiler/reorder.nim b/compiler/reorder.nim index 27b19a373..8c4d0d307 100644 --- a/compiler/reorder.nim +++ b/compiler/reorder.nim @@ -1,6 +1,6 @@ import - intsets, ast, idents, algorithm, renderer, parser, ospaths, strutils, + intsets, ast, idents, algorithm, renderer, parser, os, strutils, sequtils, msgs, modulegraphs, syntaxes, options, modulepaths, tables, lineinfos @@ -238,7 +238,7 @@ proc mergeSections(conf: ConfigRef; comps: seq[seq[DepN]], res: PNode) = var sn = newNode(ckind) sn.add cs[i].pnode[0] inc i - while i < cs.len and cs[i].pnode.kind == ckind : + while i < cs.len and cs[i].pnode.kind == ckind: sn.add cs[i].pnode[0] inc i res.add sn diff --git a/compiler/rod.nim b/compiler/rod.nim index f9208f5dc..bbd2f0c6c 100644 --- a/compiler/rod.nim +++ b/compiler/rod.nim @@ -9,19 +9,21 @@ ## This module implements the canonalization for the various caching mechanisms. -import ast, idgen, lineinfos, msgs, incremental, modulegraphs +import ast, idgen, lineinfos, msgs, incremental, modulegraphs, pathutils when not nimIncremental: template setupModuleCache*(g: ModuleGraph) = discard template storeNode*(g: ModuleGraph; module: PSym; n: PNode) = discard template loadNode*(g: ModuleGraph; module: PSym): PNode = newNode(nkStmtList) - template getModuleId*(g: ModuleGraph; fileIdx: FileIndex; fullpath: string): int = getID() + proc loadModuleSym*(g: ModuleGraph; fileIdx: FileIndex; fullpath: AbsoluteFile): (PSym, int) {.inline.} = (nil, getID()) template addModuleDep*(g: ModuleGraph; module, fileIdx: FileIndex; isIncludeFile: bool) = discard template storeRemaining*(g: ModuleGraph; module: PSym) = discard + template registerModule*(g: ModuleGraph; module: PSym) = discard + else: include rodimpl diff --git a/compiler/rodimpl.nim b/compiler/rodimpl.nim index b5891fcfd..147e8c3d6 100644 --- a/compiler/rodimpl.nim +++ b/compiler/rodimpl.nim @@ -34,10 +34,10 @@ proc encodeConfig(g: ModuleGraph): string = depConfigFields(serialize) -proc needsRecompile(g: ModuleGraph; fileIdx: FileIndex; fullpath: string; +proc needsRecompile(g: ModuleGraph; fileIdx: FileIndex; fullpath: AbsoluteFile; cycleCheck: var IntSet): bool = let root = db.getRow(sql"select id, fullhash from filenames where fullpath = ?", - fullpath) + fullpath.string) if root[0].len == 0: return true if root[1] != hashFileCached(g.config, fileIdx, fullpath): return true @@ -47,30 +47,33 @@ proc needsRecompile(g: ModuleGraph; fileIdx: FileIndex; fullpath: string; # check dependencies (recursively): for row in db.fastRows(sql"select fullpath from filenames where id in (select dependency from deps where module = ?)", root[0]): - let dep = row[0] + let dep = AbsoluteFile row[0] if needsRecompile(g, g.config.fileInfoIdx(dep), dep, cycleCheck): return true return false -proc getModuleId*(g: ModuleGraph; fileIdx: FileIndex; fullpath: string): int = - if g.config.symbolFiles in {disabledSf, writeOnlySf} or - g.incr.configChanged: - return getID() +proc getModuleId(g: ModuleGraph; fileIdx: FileIndex; fullpath: AbsoluteFile): int = + ## Analyse the known dependency graph. + if g.config.symbolFiles == disabledSf: return getID() + when false: + if g.config.symbolFiles in {disabledSf, writeOnlySf} or + g.incr.configChanged: + return getID() let module = g.incr.db.getRow( - sql"select id, fullHash, nimid from modules where fullpath = ?", fullpath) + sql"select id, fullHash, nimid from modules where fullpath = ?", string fullpath) let currentFullhash = hashFileCached(g.config, fileIdx, fullpath) if module[0].len == 0: result = getID() db.exec(sql"insert into modules(fullpath, interfHash, fullHash, nimid) values (?, ?, ?, ?)", - fullpath, "", currentFullhash, result) + string fullpath, "", currentFullhash, result) else: result = parseInt(module[2]) if currentFullhash == module[1]: # not changed, so use the cached AST: doAssert(result != 0) var cycleCheck = initIntSet() - if not needsRecompile(g, fileIdx, fullpath, cycleCheck): - echo "cached successfully! ", fullpath + if not needsRecompile(g, fileIdx, fullpath, cycleCheck) and not g.incr.configChanged: + echo "cached successfully! ", string fullpath return -result db.exec(sql"update modules set fullHash = ? where id = ?", currentFullhash, module[0]) db.exec(sql"delete from deps where module = ?", module[0]) @@ -79,8 +82,12 @@ proc getModuleId*(g: ModuleGraph; fileIdx: FileIndex; fullpath: string): int = db.exec(sql"delete from toplevelstmts where module = ?", module[0]) db.exec(sql"delete from statics where module = ?", module[0]) +proc loadModuleSym*(g: ModuleGraph; fileIdx: FileIndex; fullpath: AbsoluteFile): (PSym, int) = + let id = getModuleId(g, fileIdx, fullpath) + result = (g.incr.r.syms.getOrDefault(abs id), id) + proc pushType(w: var Writer, t: PType) = - if not containsOrIncl(w.tmarks, t.id): + if not containsOrIncl(w.tmarks, t.uniqueId): w.tstack.add(t) proc pushSym(w: var Writer, s: PSym) = @@ -106,7 +113,8 @@ proc encodeNode(g: ModuleGraph; fInfo: TLineInfo, n: PNode, result.add(',') encodeVInt(int n.info.line, result) result.add(',') - encodeVInt(toDbFileId(g.incr, g.config, n.info.fileIndex), result) + #encodeVInt(toDbFileId(g.incr, g.config, n.info.fileIndex), result) + encodeVInt(n.info.fileIndex.int, result) elif fInfo.line != n.info.line: result.add('?') encodeVInt(n.info.col, result) @@ -123,7 +131,7 @@ proc encodeNode(g: ModuleGraph; fInfo: TLineInfo, n: PNode, encodeVInt(cast[int32](f), result) if n.typ != nil: result.add('^') - encodeVInt(n.typ.id, result) + encodeVInt(n.typ.uniqueId, result) pushType(w, n.typ) case n.kind of nkCharLit..nkUInt64Lit: @@ -184,7 +192,10 @@ proc encodeType(g: ModuleGraph, t: PType, result: var string) = add(result, '[') encodeVInt(ord(t.kind), result) add(result, '+') - encodeVInt(t.id, result) + encodeVInt(t.uniqueId, result) + if t.id != t.uniqueId: + add(result, '+') + encodeVInt(t.id, result) if t.n != nil: encodeNode(g, unknownLineInfo(), t.n, result) if t.flags != {}: @@ -233,12 +244,16 @@ proc encodeType(g: ModuleGraph, t: PType, result: var string) = encodeVInt(s.id, result) pushSym(w, s) encodeLoc(g, t.loc, result) + if t.typeInst != nil: + add(result, '\21') + encodeVInt(t.typeInst.uniqueId, result) + pushType(w, t.typeInst) for i in countup(0, sonsLen(t) - 1): if t.sons[i] == nil: add(result, "^()") else: add(result, '^') - encodeVInt(t.sons[i].id, result) + encodeVInt(t.sons[i].uniqueId, result) pushType(w, t.sons[i]) proc encodeLib(g: ModuleGraph, lib: PLib, info: TLineInfo, result: var string) = @@ -257,7 +272,7 @@ proc encodeInstantiations(g: ModuleGraph; s: seq[PInstantiation]; pushSym(w, t.sym) for tt in t.concreteTypes: result.add('\17') - encodeVInt(tt.id, result) + encodeVInt(tt.uniqueId, result) pushType(w, tt) result.add('\20') encodeVInt(t.compilesId, result) @@ -275,14 +290,15 @@ proc encodeSym(g: ModuleGraph, s: PSym, result: var string) = encodeStr(s.name.s, result) if s.typ != nil: result.add('^') - encodeVInt(s.typ.id, result) + encodeVInt(s.typ.uniqueId, result) pushType(w, s.typ) result.add('?') if s.info.col != -1'i16: encodeVInt(s.info.col, result) result.add(',') encodeVInt(int s.info.line, result) result.add(',') - encodeVInt(toDbFileId(g.incr, g.config, s.info.fileIndex), result) + #encodeVInt(toDbFileId(g.incr, g.config, s.info.fileIndex), result) + encodeVInt(s.info.fileIndex.int, result) if s.owner != nil: result.add('*') encodeVInt(s.owner.id, result) @@ -310,7 +326,7 @@ proc encodeSym(g: ModuleGraph, s: PSym, result: var string) = of skType, skGenericParam: for t in s.typeInstCache: result.add('\14') - encodeVInt(t.id, result) + encodeVInt(t.uniqueId, result) pushType(w, t) of routineKinds: encodeInstantiations(g, s.procInstCache, result) @@ -318,6 +334,9 @@ proc encodeSym(g: ModuleGraph, s: PSym, result: var string) = result.add('\16') encodeVInt(s.gcUnsafetyReason.id, result) pushSym(w, s.gcUnsafetyReason) + if s.transformedBody != nil: + result.add('\24') + encodeNode(g, s.info, s.transformedBody, result) of skModule, skPackage: encodeInstantiations(g, s.usedGenerics, result) # we don't serialize: @@ -358,18 +377,12 @@ proc storeType(g: ModuleGraph; t: PType) = let m = if t.owner != nil: getModule(t.owner) else: nil let mid = if m == nil: 0 else: abs(m.id) db.exec(sql"insert into types(nimid, module, data) values (?, ?, ?)", - t.id, mid, buf) + t.uniqueId, mid, buf) -proc storeNode*(g: ModuleGraph; module: PSym; n: PNode) = - if g.config.symbolFiles == disabledSf: return - var buf = newStringOfCap(160) - encodeNode(g, module.info, n, buf) - db.exec(sql"insert into toplevelstmts(module, position, data) values (?, ?, ?)", - abs(module.id), module.offset, buf) - inc module.offset +proc transitiveClosure(g: ModuleGraph) = var i = 0 while true: - if i > 10_000: + if i > 100_000: doAssert false, "loop never ends!" if w.sstack.len > 0: let s = w.sstack.pop() @@ -380,14 +393,30 @@ proc storeNode*(g: ModuleGraph; module: PSym; n: PNode) = let t = w.tstack.pop() storeType(g, t) when false: - echo "popped type ", typeToString(t), " ", t.id + echo "popped type ", typeToString(t), " ", t.uniqueId else: break inc i +proc storeNode*(g: ModuleGraph; module: PSym; n: PNode) = + if g.config.symbolFiles == disabledSf: return + var buf = newStringOfCap(160) + encodeNode(g, module.info, n, buf) + db.exec(sql"insert into toplevelstmts(module, position, data) values (?, ?, ?)", + abs(module.id), module.offset, buf) + inc module.offset + transitiveClosure(g) + proc recordStmt*(g: ModuleGraph; module: PSym; n: PNode) = storeNode(g, module, n) +proc storeFilename(g: ModuleGraph; fullpath: AbsoluteFile; fileIdx: FileIndex) = + let id = db.getValue(sql"select id from filenames where fullpath = ?", fullpath.string) + if id.len == 0: + let fullhash = hashFileCached(g.config, fileIdx, fullpath) + db.exec(sql"insert into filenames(nimid, fullpath, fullhash) values (?, ?, ?)", + int(fileIdx), fullpath.string, fullhash) + proc storeRemaining*(g: ModuleGraph; module: PSym) = if g.config.symbolFiles == disabledSf: return var stillForwarded: seq[PSym] = @[] @@ -397,6 +426,13 @@ proc storeRemaining*(g: ModuleGraph; module: PSym) = else: stillForwarded.add s swap w.forwardedSyms, stillForwarded + transitiveClosure(g) + var nimid = 0 + for x in items(g.config.m.fileInfos): + # don't store the "command line" entry: + if nimid != 0: + storeFilename(g, x.fullPath, FileIndex(nimid)) + inc nimid # ---------------- decoder ----------------------------------- @@ -423,9 +459,11 @@ proc decodeLineInfo(g; b; info: var TLineInfo) = else: info.line = uint16(decodeVInt(b.s, b.pos)) if b.s[b.pos] == ',': inc(b.pos) - info.fileIndex = fromDbFileId(g.incr, g.config, decodeVInt(b.s, b.pos)) + #info.fileIndex = fromDbFileId(g.incr, g.config, decodeVInt(b.s, b.pos)) + info.fileIndex = FileIndex decodeVInt(b.s, b.pos) proc skipNode(b) = + # ')' itself cannot be part of a string literal so that this is correct. assert b.s[b.pos] == '(' var par = 0 var pos = b.pos+1 @@ -560,13 +598,18 @@ proc loadType(g; id: int; info: TLineInfo): PType = result.kind = TTypeKind(decodeVInt(b.s, b.pos)) if b.s[b.pos] == '+': inc(b.pos) - result.id = decodeVInt(b.s, b.pos) - setId(result.id) + result.uniqueId = decodeVInt(b.s, b.pos) + setId(result.uniqueId) #if debugIds: registerID(result) else: internalError(g.config, info, "decodeType: no id") + if b.s[b.pos] == '+': + inc(b.pos) + result.id = decodeVInt(b.s, b.pos) + else: + result.id = result.uniqueId # here this also avoids endless recursion for recursive type - g.incr.r.types.add(result.id, result) + g.incr.r.types.add(result.uniqueId, result) if b.s[b.pos] == '(': result.n = decodeNode(g, b, unknownLineInfo()) if b.s[b.pos] == '$': inc(b.pos) @@ -615,8 +658,12 @@ proc loadType(g; id: int; info: TLineInfo): PType = doAssert b.s[b.pos] == '\20' inc(b.pos) let y = loadSym(g, decodeVInt(b.s, b.pos), info) - result.methods.safeAdd((x, y)) + result.methods.add((x, y)) decodeLoc(g, b, result.loc, info) + if b.s[b.pos] == '\21': + inc(b.pos) + let d = decodeVInt(b.s, b.pos) + result.typeInst = loadType(g, d, info) while b.s[b.pos] == '^': inc(b.pos) if b.s[b.pos] == '(': @@ -655,7 +702,7 @@ proc decodeInstantiations(g; b; info: TLineInfo; if b.s[b.pos] == '\20': inc(b.pos) ii.compilesId = decodeVInt(b.s, b.pos) - s.safeAdd ii + s.add ii proc loadSymFromBlob(g; b; info: TLineInfo): PSym = if b.s[b.pos] == '{': @@ -716,12 +763,15 @@ proc loadSymFromBlob(g; b; info: TLineInfo): PSym = of skType, skGenericParam: while b.s[b.pos] == '\14': inc(b.pos) - result.typeInstCache.safeAdd loadType(g, decodeVInt(b.s, b.pos), result.info) + result.typeInstCache.add loadType(g, decodeVInt(b.s, b.pos), result.info) of routineKinds: decodeInstantiations(g, b, result.info, result.procInstCache) if b.s[b.pos] == '\16': inc(b.pos) result.gcUnsafetyReason = loadSym(g, decodeVInt(b.s, b.pos), result.info) + if b.s[b.pos] == '\24': + inc b.pos + result.transformedBody = decodeNode(g, b, result.info) of skModule, skPackage: decodeInstantiations(g, b, result.info, result.usedGenerics) of skLet, skVar, skField, skForVar: @@ -749,6 +799,9 @@ proc loadSym(g; id: int; info: TLineInfo): PSym = result = loadSymFromBlob(g, b, info) doAssert id == result.id, "symbol ID is not consistent!" +proc registerModule*(g; module: PSym) = + g.incr.r.syms.add(abs module.id, module) + proc loadModuleSymTab(g; module: PSym) = ## goal: fill module.tab g.incr.r.syms.add(module.id, module) @@ -762,7 +815,8 @@ proc loadModuleSymTab(g; module: PSym) = b.s.add '\0' s = loadSymFromBlob(g, b, module.info) assert s != nil - strTableAdd(module.tab, s) + if s.kind != skField: + strTableAdd(module.tab, s) if sfSystemModule in module.flags: g.systemModule = module @@ -792,7 +846,7 @@ proc replay(g: ModuleGraph; module: PSym; n: PNode) = of "error": localError(g.config, n.info, errUser, n[1].strVal) of "compile": internalAssert g.config, n.len == 3 and n[2].kind == nkStrLit - var cf = Cfile(cname: n[1].strVal, obj: n[2].strVal, + var cf = Cfile(cname: AbsoluteFile n[1].strVal, obj: AbsoluteFile n[2].strVal, flags: {CfileFlag.External}) extccomp.addExternalFileToCompile(g.config, cf) of "link": @@ -840,8 +894,9 @@ proc replay(g: ModuleGraph; module: PSym; n: PNode) = internalAssert g.config, false of nkImportStmt: for x in n: - internalAssert g.config, x.kind == nkStrLit - let imported = g.importModuleCallback(g, module, fileInfoIdx(g.config, n[0].strVal)) + internalAssert g.config, x.kind == nkSym + let modpath = AbsoluteFile toFullPath(g.config, x.sym.info) + let imported = g.importModuleCallback(g, module, fileInfoIdx(g.config, modpath)) internalAssert g.config, imported.id < 0 of nkStmtList, nkStmtListExpr: for x in n: replay(g, module, x) @@ -852,32 +907,38 @@ proc loadNode*(g: ModuleGraph; module: PSym): PNode = result = newNodeI(nkStmtList, module.info) for row in db.rows(sql"select data from toplevelstmts where module = ? order by position asc", abs module.id): - var b = BlobReader(pos: 0) # ensure we can read without index checks: b.s = row[0] & '\0' result.add decodeNode(g, b, module.info) - db.exec(sql"insert into controlblock(idgen) values (?)", gFrontEndId) replay(g, module, result) proc setupModuleCache*(g: ModuleGraph) = if g.config.symbolFiles == disabledSf: return g.recordStmt = recordStmt - let dbfile = getNimcacheDir(g.config) / "rodfiles.db" + let dbfile = getNimcacheDir(g.config) / RelativeFile"rodfiles.db" if g.config.symbolFiles == writeOnlySf: removeFile(dbfile) + createDir getNimcacheDir(g.config) + let ec = encodeConfig(g) if not fileExists(dbfile): - db = open(connection=dbfile, user="nim", password="", + db = open(connection=string dbfile, user="nim", password="", database="nim") createDb(db) - db.exec(sql"insert into config(config) values (?)", encodeConfig(g)) + db.exec(sql"insert into config(config) values (?)", ec) else: - db = open(connection=dbfile, user="nim", password="", + db = open(connection=string dbfile, user="nim", password="", database="nim") let oldConfig = db.getValue(sql"select config from config") - g.incr.configChanged = oldConfig != encodeConfig(g) + g.incr.configChanged = oldConfig != ec + # ensure the filename IDs stay consistent: + for row in db.rows(sql"select fullpath, nimid from filenames order by nimid"): + let id = fileInfoIdx(g.config, AbsoluteFile row[0]) + doAssert id.int == parseInt(row[1]) + db.exec(sql"update config set config = ?", ec) db.exec(sql"pragma journal_mode=off") + # This MUST be turned off, otherwise it's way too slow even for testing purposes: db.exec(sql"pragma SYNCHRONOUS=off") db.exec(sql"pragma LOCKING_MODE=exclusive") let lastId = db.getValue(sql"select max(idgen) from controlblock") diff --git a/compiler/scriptconfig.nim b/compiler/scriptconfig.nim index cf69e29f1..bfff86479 100644 --- a/compiler/scriptconfig.nim +++ b/compiler/scriptconfig.nim @@ -96,6 +96,12 @@ proc setupVM*(module: PSym; cache: IdentCache; scriptName: string; cbconf fileExists: setResult(a, os.fileExists(a.getString 0)) + cbconf projectName: + setResult(a, conf.projectName) + cbconf projectDir: + setResult(a, conf.projectPath.string) + cbconf projectPath: + setResult(a, conf.projectFull.string) cbconf thisDir: setResult(a, vthisDir) cbconf put: @@ -117,6 +123,7 @@ proc setupVM*(module: PSym; cache: IdentCache; scriptName: string; cbconf setCommand: conf.command = a.getString 0 let arg = a.getString 1 + incl(conf.globalOptions, optWasNimscript) if arg.len > 0: conf.projectName = arg let path = @@ -152,6 +159,8 @@ proc setupVM*(module: PSym; cache: IdentCache; scriptName: string; proc runNimScript*(cache: IdentCache; scriptName: AbsoluteFile; freshDefines=true; conf: ConfigRef) = rawMessage(conf, hintConf, scriptName.string) + let oldSymbolFiles = conf.symbolFiles + conf.symbolFiles = disabledSf let graph = newModuleGraph(cache, conf) connectCallbacks(graph) @@ -159,11 +168,8 @@ proc runNimScript*(cache: IdentCache; scriptName: AbsoluteFile; defineSymbol(conf.symbols, "nimscript") defineSymbol(conf.symbols, "nimconfig") - var registeredPasses {.global.} = false - if not registeredPasses: - registerPass(graph, semPass) - registerPass(graph, evalPass) - registeredPasses = true + registerPass(graph, semPass) + registerPass(graph, evalPass) conf.searchPaths.add(conf.libpath) @@ -180,3 +186,4 @@ proc runNimScript*(cache: IdentCache; scriptName: AbsoluteFile; #initDefines() undefSymbol(conf.symbols, "nimscript") undefSymbol(conf.symbols, "nimconfig") + conf.symbolFiles = oldSymbolFiles diff --git a/compiler/sem.nim b/compiler/sem.nim index dbc174d50..924e53b66 100644 --- a/compiler/sem.nim +++ b/compiler/sem.nim @@ -16,13 +16,16 @@ import procfind, lookups, pragmas, passes, semdata, semtypinst, sigmatch, intsets, transf, vmdef, vm, idgen, aliases, cgmeth, lambdalifting, evaltempl, patterns, parampatterns, sempass2, linter, semmacrosanity, - semparallel, lowerings, pluginsupport, plugins/active, rod, lineinfos + lowerings, pluginsupport, plugins/active, rod, lineinfos -from modulegraphs import ModuleGraph +from modulegraphs import ModuleGraph, PPassContext, onUse, onDef, onDefResolveForward when defined(nimfix): import nimfix/prettybase +when not defined(leanCompiler): + import semparallel + # implementation proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode {.procvar.} @@ -201,7 +204,7 @@ proc newSymG*(kind: TSymKind, n: PNode, c: PContext): PSym = if n.kind == nkSym: # and sfGenSym in n.sym.flags: result = n.sym - if result.kind != kind: + if result.kind notin {kind, skTemp}: localError(c.config, n.info, "cannot use symbol of kind '" & $result.kind & "' as a '" & $kind & "'") if sfGenSym in result.flags and result.kind notin {skTemplate, skMacro, skParam}: @@ -444,10 +447,10 @@ const proc semMacroExpr(c: PContext, n, nOrig: PNode, sym: PSym, flags: TExprFlags = {}): PNode = - pushInfoContext(c.config, nOrig.info) + pushInfoContext(c.config, nOrig.info, sym.detailedInfo) markUsed(c.config, n.info, sym, c.graph.usageSym) - styleCheckUse(n.info, sym) + onUse(n.info, sym) if sym == c.p.owner: globalError(c.config, n.info, "recursive dependency: '$1'" % sym.name.s) @@ -583,7 +586,7 @@ proc semStmtAndGenerateGenerics(c: PContext, n: PNode): PNode = result = buildEchoStmt(c, result) if c.config.cmd == cmdIdeTools: appendToModule(c.module, result) - result = transformStmt(c.graph, c.module, result) + trackTopLevelStmt(c.graph, c.module, result) proc recoverContext(c: PContext) = # clean up in case of a semantic error: We clean up the stacks, etc. This is diff --git a/compiler/semasgn.nim b/compiler/semasgn.nim index f781972a5..3947e4f6c 100644 --- a/compiler/semasgn.nim +++ b/compiler/semasgn.nim @@ -81,8 +81,8 @@ proc genAddr(c: PContext; x: PNode): PNode = addSon(result, x) proc newAsgnCall(c: PContext; op: PSym; x, y: PNode): PNode = - if sfError in op.flags: - localError(c.config, x.info, "usage of '$1' is a user-defined error" % op.name.s) + #if sfError in op.flags: + # localError(c.config, x.info, "usage of '$1' is a user-defined error" % op.name.s) result = newNodeI(nkCall, x.info) result.add newSymNode(op) result.add genAddr(c, x) @@ -121,8 +121,11 @@ proc considerAsgnOrSink(c: var TLiftCtx; t: PType; body, x, y: PNode; op = field if op == nil: op = liftBody(c.c, t, c.kind, c.info) - markUsed(c.c.config, c.info, op, c.c.graph.usageSym) - styleCheckUse(c.info, op) + if sfError in op.flags: + incl c.fn.flags, sfError + else: + markUsed(c.c.config, c.info, op, c.c.graph.usageSym) + onUse(c.info, op) body.add newAsgnCall(c.c, op, x, y) result = true @@ -132,7 +135,7 @@ proc considerOverloadedOp(c: var TLiftCtx; t: PType; body, x, y: PNode): bool = let op = t.destructor if op != nil: markUsed(c.c.config, c.info, op, c.c.graph.usageSym) - styleCheckUse(c.info, op) + onUse(c.info, op) body.add destructorCall(c.c, op, x) result = true of attachedAsgn: @@ -143,7 +146,7 @@ proc considerOverloadedOp(c: var TLiftCtx; t: PType; body, x, y: PNode): bool = let op = t.deepCopy if op != nil: markUsed(c.c.config, c.info, op, c.c.graph.usageSym) - styleCheckUse(c.info, op) + onUse(c.info, op) body.add newDeepCopyCall(op, x, y) result = true @@ -197,10 +200,10 @@ proc liftBodyAux(c: var TLiftCtx; t: PType; body, x, y: PNode) = case t.kind of tyNone, tyEmpty, tyVoid: discard of tyPointer, tySet, tyBool, tyChar, tyEnum, tyInt..tyUInt64, tyCString, - tyPtr, tyRef, tyOpt: + tyPtr, tyRef, tyOpt, tyUncheckedArray: defaultOp(c, t, body, x, y) of tyArray: - if {tfHasAsgn, tfUncheckedArray} * t.flags == {tfHasAsgn}: + if tfHasAsgn in t.flags: let i = declareCounter(c, body, firstOrd(c.c.config, t)) let whileLoop = genWhileLoop(c, i, x) let elemType = t.lastSon @@ -260,7 +263,7 @@ proc liftBodyAux(c: var TLiftCtx; t: PType; body, x, y: PNode) = of tyOrdinal, tyRange, tyInferred, tyGenericInst, tyStatic, tyVar, tyLent, tyAlias, tySink: liftBodyAux(c, lastSon(t), body, x, y) - of tyUnused, tyOptAsRef: internalError(c.c.config, "liftBodyAux") + of tyOptAsRef: internalError(c.c.config, "liftBodyAux") proc newProcType(info: TLineInfo; owner: PSym): PType = result = newType(tyProc, owner) diff --git a/compiler/semcall.nim b/compiler/semcall.nim index 53f7045dd..7e0ea5490 100644 --- a/compiler/semcall.nim +++ b/compiler/semcall.nim @@ -105,7 +105,7 @@ proc pickBestCandidate(c: PContext, headSymbol: PNode, if cmp < 0: best = z # x is better than the best so far elif cmp == 0: alt = z # x is as good as the best so far elif errorsEnabled or z.diagnosticsEnabled: - errors.safeAdd(CandidateError( + errors.add(CandidateError( sym: sym, unmatchedVarParam: int z.mutabilityProblem, firstMismatch: z.firstMismatch, @@ -138,6 +138,7 @@ proc effectProblem(f, a: PType; result: var string) = proc renderNotLValue(n: PNode): string = result = $n + let n = if n.kind == nkHiddenDeref: n[0] else: n if n.kind == nkHiddenCallConv and n.len > 1: result = $n[0] & "(" & result & ")" elif n.kind in {nkHiddenStdConv, nkHiddenSubConv} and n.len == 2: @@ -166,20 +167,22 @@ proc presentFailedCandidates(c: PContext, n: PNode, errors: CandidateErrors): prefer = preferModuleInfo break - when false: - # we pretend procs are attached to the type of the first - # argument in order to remove plenty of candidates. This is - # comparable to what C# does and C# is doing fine. - var filterOnlyFirst = false + # we pretend procs are attached to the type of the first + # argument in order to remove plenty of candidates. This is + # comparable to what C# does and C# is doing fine. + var filterOnlyFirst = false + if optShowAllMismatches notin c.config.globalOptions: for err in errors: if err.firstMismatch > 1: filterOnlyFirst = true break var candidates = "" + var skipped = 0 for err in errors: - when false: - if filterOnlyFirst and err.firstMismatch == 1: continue + if filterOnlyFirst and err.firstMismatch == 1: + inc skipped + continue if err.sym.kind in routineKinds and err.sym.ast != nil: add(candidates, renderTree(err.sym.ast, {renderNoBody, renderNoComments, renderNoPragmas})) @@ -216,7 +219,9 @@ proc presentFailedCandidates(c: PContext, n: PNode, errors: CandidateErrors): "' is immutable\n") for diag in err.diagnostics: candidates.add(diag & "\n") - + if skipped > 0: + candidates.add($skipped & " other mismatching symbols have been " & + " suppressed; compile with --showAllMismatches:on to see them\n") result = (prefer, candidates) const @@ -224,6 +229,7 @@ const errButExpected = "but expected one of: " errUndeclaredField = "undeclared field: '$1'" errUndeclaredRoutine = "attempting to call undeclared routine: '$1'" + errBadRoutine = "attempting to call routine: '$1'$2" errAmbiguousCallXYZ = "ambiguous call; both $1 and $2 match for: $3" proc notFoundError*(c: PContext, n: PNode, errors: CandidateErrors) = @@ -262,6 +268,30 @@ proc bracketNotFoundError(c: PContext; n: PNode) = else: notFoundError(c, n, errors) +proc getMsgDiagnostic(c: PContext, flags: TExprFlags, n, f: PNode): string = + if c.compilesContextId > 0: + # we avoid running more diagnostic when inside a `compiles(expr)`, to + # errors while running diagnostic (see test D20180828T234921), and + # also avoid slowdowns in evaluating `compiles(expr)`. + discard + else: + var o: TOverloadIter + var sym = initOverloadIter(o, c, f) + while sym != nil: + proc toHumanStr(kind: TSymKind): string = + result = $kind + assert result.startsWith "sk" + result = result[2..^1].toLowerAscii + result &= "\n found '$1' of kind '$2'" % [getSymRepr(c.config, sym), sym.kind.toHumanStr] + sym = nextOverloadIter(o, c, n) + + let ident = considerQuotedIdent(c, f, n).s + if nfDotField in n.flags and nfExplicitCall notin n.flags: + result = errUndeclaredField % ident & result + else: + if result.len == 0: result = errUndeclaredRoutine % ident + else: result = errBadRoutine % [ident, result] + proc resolveOverloads(c: PContext, n, orig: PNode, filter: TSymKinds, flags: TExprFlags, errors: var CandidateErrors, @@ -330,10 +360,8 @@ proc resolveOverloads(c: PContext, n, orig: PNode, pickBest(callOp) if overloadsState == csEmpty and result.state == csEmpty: - if nfDotField in n.flags and nfExplicitCall notin n.flags: - localError(c.config, n.info, errUndeclaredField % considerQuotedIdent(c, f, n).s) - else: - localError(c.config, n.info, errUndeclaredRoutine % considerQuotedIdent(c, f, n).s) + if efNoUndeclared notin flags: # for tests/pragmas/tcustom_pragma.nim + localError(c.config, n.info, getMsgDiagnostic(c, flags, n, f)) return elif result.state != csMatch: if nfExprCall in n.flags: @@ -367,6 +395,7 @@ proc resolveOverloads(c: PContext, n, orig: PNode, args]) proc instGenericConvertersArg*(c: PContext, a: PNode, x: TCandidate) = + let a = if a.kind == nkHiddenDeref: a[0] else: a if a.kind == nkHiddenCallConv and a.sons[0].kind == nkSym: let s = a.sons[0].sym if s.ast != nil and s.ast[genericParamsPos].kind != nkEmpty: @@ -427,7 +456,7 @@ proc semResolvedCall(c: PContext, x: TCandidate, assert x.state == csMatch var finalCallee = x.calleeSym markUsed(c.config, n.sons[0].info, finalCallee, c.graph.usageSym) - styleCheckUse(n.sons[0].info, finalCallee) + onUse(n.sons[0].info, finalCallee) assert finalCallee.ast != nil if x.hasFauxMatch: result = x.call @@ -500,14 +529,14 @@ proc semOverloadedCall(c: PContext, n, nOrig: PNode, # repeat the overload resolution, # this time enabling all the diagnostic output (this should fail again) discard semOverloadedCall(c, n, nOrig, filter, flags + {efExplain}) - else: + elif efNoUndeclared notin flags: notFoundError(c, n, errors) else: if efExplain notin flags: # repeat the overload resolution, # this time enabling all the diagnostic output (this should fail again) discard semOverloadedCall(c, n, nOrig, filter, flags + {efExplain}) - else: + elif efNoUndeclared notin flags: notFoundError(c, n, errors) proc explicitGenericInstError(c: PContext; n: PNode): PNode = @@ -535,14 +564,17 @@ proc explicitGenericSym(c: PContext, n: PNode, s: PSym): PNode = var newInst = generateInstance(c, s, m.bindings, n.info) newInst.typ.flags.excl tfUnresolved markUsed(c.config, n.info, s, c.graph.usageSym) - styleCheckUse(n.info, s) + onUse(n.info, s) result = newSymNode(newInst, n.info) proc explicitGenericInstantiation(c: PContext, n: PNode, s: PSym): PNode = assert n.kind == nkBracketExpr for i in 1..sonsLen(n)-1: let e = semExpr(c, n.sons[i]) - n.sons[i].typ = e.typ.skipTypes({tyTypeDesc}) + if e.typ == nil: + localError(c.config, e.info, "expression has no type") + else: + n.sons[i].typ = e.typ.skipTypes({tyTypeDesc}) var s = s var a = n.sons[0] if a.kind == nkSym: diff --git a/compiler/semdata.nim b/compiler/semdata.nim index 6d6627690..bec4a59a4 100644 --- a/compiler/semdata.nim +++ b/compiler/semdata.nim @@ -65,7 +65,10 @@ type # to the user. efWantStmt, efAllowStmt, efDetermineType, efExplain, efAllowDestructor, efWantValue, efOperand, efNoSemCheck, - efNoEvaluateGeneric, efInCall, efFromHlo + efNoEvaluateGeneric, efInCall, efFromHlo, + efNoUndeclared + # Use this if undeclared identifiers should not raise an error during + # overload resolution. TExprFlags* = set[TExprFlag] diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index e683984c5..623af9784 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -25,8 +25,8 @@ const proc semTemplateExpr(c: PContext, n: PNode, s: PSym, flags: TExprFlags = {}): PNode = markUsed(c.config, n.info, s, c.graph.usageSym) - styleCheckUse(n.info, s) - pushInfoContext(c.config, n.info) + onUse(n.info, s) + pushInfoContext(c.config, n.info, s.detailedInfo) result = evalTemplate(n, s, getCurrOwner(c), c.config, efFromHlo in flags) if efNoSemCheck notin flags: result = semAfterMacroCall(c, n, result, s, flags) popInfoContext(c.config) @@ -158,6 +158,10 @@ proc isCastable(conf: ConfigRef; dst, src: PType): bool = var dstSize, srcSize: BiggestInt dstSize = computeSize(conf, dst) srcSize = computeSize(conf, src) + if dstSize == -3 or srcSize == -3: # szUnknownSize + # The Nim compiler can't detect if it's legal or not. + # Just assume the programmer knows what he is doing. + return true if dstSize < 0: result = false elif srcSize < 0: @@ -197,7 +201,7 @@ proc semConv(c: PContext, n: PNode): PNode = if targetType.kind == tyTypeDesc: internalAssert c.config, targetType.len > 0 if targetType.base.kind == tyNone: - return semTypeOf(c, n[1]) + return semTypeOf(c, n) else: targetType = targetType.base elif targetType.kind == tyStatic: @@ -261,7 +265,7 @@ proc semConv(c: PContext, n: PNode): PNode = let status = checkConvertible(c, result.typ, it.typ) if status in {convOK, convNotNeedeed}: markUsed(c.config, n.info, it.sym, c.graph.usageSym) - styleCheckUse(n.info, it.sym) + onUse(n.info, it.sym) markIndirect(c, it.sym) return it errorUseQualifier(c, n.info, op.sons[0].sym) @@ -308,15 +312,6 @@ proc semLowHigh(c: PContext, n: PNode, m: TMagic): PNode = localError(c.config, n.info, "invalid argument for: " & opToStr[m]) result = n -proc semSizeof(c: PContext, n: PNode): PNode = - if sonsLen(n) != 2: - localError(c.config, n.info, errXExpectsTypeOrValue % "sizeof") - else: - n.sons[1] = semExprWithType(c, n.sons[1], {efDetermineType}) - #restoreOldStyleType(n.sons[1]) - n.typ = getSysType(c.graph, n.info, tyInt) - result = n - proc fixupStaticType(c: PContext, n: PNode) = # This proc can be applied to evaluated expressions to assign # them a static type. @@ -408,10 +403,8 @@ proc semIs(c: PContext, n: PNode, flags: TExprFlags): PNode = n[1] = makeTypeSymNode(c, lhsType, n[1].info) lhsType = n[1].typ else: - if lhsType.base.kind == tyNone: - # this is a typedesc variable, leave for evals - return - if lhsType.base.containsGenericType: + internalAssert c.config, lhsType.base.kind != tyNone + if c.inGenericContext > 0 and lhsType.base.containsGenericType: # BUGFIX: don't evaluate this too early: ``T is void`` return @@ -446,7 +439,7 @@ proc changeType(c: PContext; n: PNode, newType: PType, check: bool) = for i in countup(0, sonsLen(n) - 1): changeType(c, n.sons[i], elemType(newType), check) of nkPar, nkTupleConstr: - let tup = newType.skipTypes({tyGenericInst, tyAlias, tySink}) + let tup = newType.skipTypes({tyGenericInst, tyAlias, tySink, tyDistinct}) if tup.kind != tyTuple: if tup.kind == tyObject: return globalError(c.config, n.info, "no tuple type for constructor") @@ -621,7 +614,8 @@ proc analyseIfAddressTakenInCall(c: PContext, n: PNode) = const FakeVarParams = {mNew, mNewFinalize, mInc, ast.mDec, mIncl, mExcl, mSetLengthStr, mSetLengthSeq, mAppendStrCh, mAppendStrStr, mSwap, - mAppendSeqElem, mNewSeq, mReset, mShallowCopy, mDeepCopy} + mAppendSeqElem, mNewSeq, mReset, mShallowCopy, mDeepCopy, mMove, + mWasMoved} # get the real type of the callee # it may be a proc var with a generic alias type, so we skip over them @@ -647,6 +641,7 @@ proc analyseIfAddressTakenInCall(c: PContext, n: PNode) = return for i in countup(1, sonsLen(n) - 1): + let n = if n.kind == nkHiddenDeref: n[0] else: n if n.sons[i].kind == nkHiddenCallConv: # we need to recurse explicitly here as converters can create nested # calls and then they wouldn't be analysed otherwise @@ -1043,7 +1038,7 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode = case s.kind of skConst: markUsed(c.config, n.info, s, c.graph.usageSym) - styleCheckUse(n.info, s) + onUse(n.info, s) case skipTypes(s.typ, abstractInst-{tyTypeDesc}).kind of tyNil, tyChar, tyInt..tyInt64, tyFloat..tyFloat128, tyTuple, tySet, tyUInt..tyUInt64: @@ -1068,7 +1063,7 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode = if efNoEvaluateGeneric in flags and s.ast[genericParamsPos].len > 0 or (n.kind notin nkCallKinds and s.requiredParams > 0): markUsed(c.config, n.info, s, c.graph.usageSym) - styleCheckUse(n.info, s) + onUse(n.info, s) result = symChoice(c, n, s, scClosed) else: result = semMacroExpr(c, n, n, s, flags) @@ -1077,13 +1072,13 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode = (n.kind notin nkCallKinds and s.requiredParams > 0) or sfCustomPragma in sym.flags: markUsed(c.config, n.info, s, c.graph.usageSym) - styleCheckUse(n.info, s) + onUse(n.info, s) result = symChoice(c, n, s, scClosed) else: result = semTemplateExpr(c, n, s, flags) of skParam: markUsed(c.config, n.info, s, c.graph.usageSym) - styleCheckUse(n.info, s) + onUse(n.info, s) if s.typ != nil and s.typ.kind == tyStatic and s.typ.n != nil: # XXX see the hack in sigmatch.nim ... return s.typ.n @@ -1105,14 +1100,14 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode = localError(c.config, n.info, "illegal context for 'nimvm' magic") markUsed(c.config, n.info, s, c.graph.usageSym) - styleCheckUse(n.info, s) + onUse(n.info, s) result = newSymNode(s, n.info) # We cannot check for access to outer vars for example because it's still # not sure the symbol really ends up being used: # var len = 0 # but won't be called # genericThatUsesLen(x) # marked as taking a closure? of skGenericParam: - styleCheckUse(n.info, s) + onUse(n.info, s) if s.typ.kind == tyStatic: result = newSymNode(s, n.info) result.typ = s.typ @@ -1123,7 +1118,7 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode = return n of skType: markUsed(c.config, n.info, s, c.graph.usageSym) - styleCheckUse(n.info, s) + onUse(n.info, s) if s.typ.kind == tyStatic and s.typ.base.kind != tyNone and s.typ.n != nil: return s.typ.n result = newSymNode(s, n.info) @@ -1145,7 +1140,7 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode = # is the access to a public field or in the same module or in a friend? doAssert f == s markUsed(c.config, n.info, f, c.graph.usageSym) - styleCheckUse(n.info, f) + onUse(n.info, f) result = newNodeIT(nkDotExpr, n.info, f.typ) result.add makeDeref(newSymNode(p.selfSym)) result.add newSymNode(f) # we now have the correct field @@ -1158,11 +1153,11 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode = ty = skipTypes(ty.sons[0], skipPtrs) # old code, not sure if it's live code: markUsed(c.config, n.info, s, c.graph.usageSym) - styleCheckUse(n.info, s) + onUse(n.info, s) result = newSymNode(s, n.info) else: markUsed(c.config, n.info, s, c.graph.usageSym) - styleCheckUse(n.info, s) + onUse(n.info, s) result = newSymNode(s, n.info) proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode = @@ -1185,7 +1180,7 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode = else: markUsed(c.config, n.sons[1].info, s, c.graph.usageSym) result = semSym(c, n, s, flags) - styleCheckUse(n.sons[1].info, s) + onUse(n.sons[1].info, s) return n.sons[0] = semExprWithType(c, n.sons[0], flags+{efDetermineType}) @@ -1248,7 +1243,7 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode = result.info = n.info result.typ = ty markUsed(c.config, n.info, f, c.graph.usageSym) - styleCheckUse(n.info, f) + onUse(n.info, f) return of tyObject, tyTuple: if ty.n != nil and ty.n.kind == nkRecList: @@ -1279,7 +1274,7 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode = if fieldVisible(c, f): # is the access to a public field or in the same module or in a friend? markUsed(c.config, n.sons[1].info, f, c.graph.usageSym) - styleCheckUse(n.sons[1].info, f) + onUse(n.sons[1].info, f) n.sons[0] = makeDeref(n.sons[0]) n.sons[1] = newSymNode(f) # we now have the correct field n.typ = f.typ @@ -1293,7 +1288,7 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode = f = getSymFromList(ty.n, i) if f != nil: markUsed(c.config, n.sons[1].info, f, c.graph.usageSym) - styleCheckUse(n.sons[1].info, f) + onUse(n.sons[1].info, f) n.sons[0] = makeDeref(n.sons[0]) n.sons[1] = newSymNode(f) n.typ = f.typ @@ -1364,20 +1359,26 @@ proc semSubscript(c: PContext, n: PNode, flags: TExprFlags): PNode = arr = arr.base case arr.kind - of tyArray, tyOpenArray, tyVarargs, tySequence, tyString, - tyCString: + of tyArray, tyOpenArray, tyVarargs, tySequence, tyString, tyCString, + tyUncheckedArray: if n.len != 2: return nil n.sons[0] = makeDeref(n.sons[0]) for i in countup(1, sonsLen(n) - 1): n.sons[i] = semExprWithType(c, n.sons[i], flags*{efInTypeof, efDetermineType}) - var indexType = if arr.kind == tyArray: arr.sons[0] else: getSysType(c.graph, n.info, tyInt) - var arg = indexTypesMatch(c, indexType, n.sons[1].typ, n.sons[1]) - if arg != nil: - n.sons[1] = arg + # Arrays index type is dictated by the range's type + if arr.kind == tyArray: + var indexType = arr.sons[0] + var arg = indexTypesMatch(c, indexType, n.sons[1].typ, n.sons[1]) + if arg != nil: + n.sons[1] = arg + result = n + result.typ = elemType(arr) + # Other types have a bit more of leeway + elif n.sons[1].typ.skipTypes(abstractRange-{tyDistinct}).kind in + {tyInt..tyInt64, tyUInt..tyUInt64}: result = n result.typ = elemType(arr) - #GlobalError(n.info, errIndexTypesDoNotMatch) of tyTypeDesc: # The result so far is a tyTypeDesc bound # a tyGenericBody. The line below will substitute @@ -1489,6 +1490,13 @@ proc asgnToResultVar(c: PContext, n, le, ri: PNode) {.inline.} = template resultTypeIsInferrable(typ: PType): untyped = typ.isMetaType and typ.kind != tyTypeDesc + +proc goodLineInfo(arg: PNode): TLineinfo = + if arg.kind == nkStmtListExpr and arg.len > 0: + goodLineInfo(arg[^1]) + else: + arg.info + proc semAsgn(c: PContext, n: PNode; mode=asgnNormal): PNode = checkSonsLen(n, 2, c.config) var a = n.sons[0] @@ -1539,7 +1547,9 @@ proc semAsgn(c: PContext, n: PNode; mode=asgnNormal): PNode = # a = b # both are vars, means: a[] = b[] # a = b # b no 'var T' means: a = addr(b) var le = a.typ - if (skipTypes(le, {tyGenericInst, tyAlias, tySink}).kind != tyVar and + if le == nil: + localError(c.config, a.info, "expression has no type") + elif (skipTypes(le, {tyGenericInst, tyAlias, tySink}).kind != tyVar and isAssignable(c, a) == arNone) or skipTypes(le, abstractVar).kind in {tyOpenArray, tyVarargs}: # Direct assignment to a discriminant is allowed! @@ -1568,7 +1578,7 @@ proc semAsgn(c: PContext, n: PNode; mode=asgnNormal): PNode = else: typeMismatch(c.config, n.info, lhs.typ, rhsTyp) - n.sons[1] = fitNode(c, le, rhs, n.info) + n.sons[1] = fitNode(c, le, rhs, goodLineInfo(n[1])) liftTypeBoundOps(c, lhs.typ, lhs.info) #liftTypeBoundOps(c, n.sons[0].typ, n.sons[0].info) @@ -1757,7 +1767,7 @@ proc semExpandToAst(c: PContext, n: PNode): PNode = macroCall.sons[0] = newSymNode(expandedSym, macroCall.info) markUsed(c.config, n.info, expandedSym, c.graph.usageSym) - styleCheckUse(n.info, expandedSym) + onUse(n.info, expandedSym) if isCallExpr(macroCall): for i in countup(1, macroCall.len-1): @@ -1782,7 +1792,7 @@ proc semExpandToAst(c: PContext, n: PNode): PNode = let info = macroCall.sons[0].info macroCall.sons[0] = newSymNode(cand, info) markUsed(c.config, info, cand, c.graph.usageSym) - styleCheckUse(info, cand) + onUse(info, cand) # we just perform overloading resolution here: #n.sons[1] = semOverloadedCall(c, macroCall, macroCall, {skTemplate, skMacro}) @@ -1812,6 +1822,7 @@ proc processQuotations(c: PContext; n: var PNode, op: string, ids.add n return + if n.kind == nkPrefix: checkSonsLen(n, 2, c.config) if n[0].kind == nkIdent: @@ -1822,6 +1833,9 @@ proc processQuotations(c: PContext; n: var PNode, op: string, n.sons[0] = newIdentNode(getIdent(c.cache, examinedOp.substr(op.len)), n.info) elif n.kind == nkAccQuoted and op == "``": returnQuote n[0] + elif n.kind == nkIdent: + if n.ident.s == "result": + n = ids[0] for i in 0 ..< n.safeLen: processQuotations(c, n.sons[i], op, quotes, ids) @@ -1833,15 +1847,18 @@ proc semQuoteAst(c: PContext, n: PNode): PNode = var quotedBlock = n[^1] op = if n.len == 3: expectString(c, n[1]) else: "``" - quotes = newSeq[PNode](1) + quotes = newSeq[PNode](2) # the quotes will be added to a nkCall statement - # leave some room for the callee symbol - ids = newSeq[PNode]() + # leave some room for the callee symbol and the result symbol + ids = newSeq[PNode](1) # this will store the generated param names + # leave some room for the result symbol if quotedBlock.kind != nkStmtList: localError(c.config, n.info, errXExpected, "block") + # This adds a default first field to pass the result symbol + ids[0] = newAnonSym(c, skParam, n.info).newSymNode processQuotations(c, quotedBlock, op, quotes, ids) var dummyTemplate = newProcNode( @@ -1860,6 +1877,8 @@ proc semQuoteAst(c: PContext, n: PNode): PNode = var tmpl = semTemplateDef(c, dummyTemplate) quotes[0] = tmpl[namePos] + # This adds a call to newIdentNode("result") as the first argument to the template call + quotes[1] = newNode(nkCall, n.info, @[newIdentNode(getIdent(c.cache, "newIdentNode"), n.info), newStrNode(nkStrLit, "result")]) result = newNode(nkCall, n.info, @[ createMagic(c.graph, "getAst", mExpandToAst).newSymNode, newNode(nkCall, n.info, quotes)]) @@ -1958,15 +1977,13 @@ proc setMs(n: PNode, s: PSym): PNode = proc semMagic(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode = # this is a hotspot in the compiler! - # DON'T forget to update ast.SpecialSemMagics if you add a magic here! result = n case s.magic # magics that need special treatment of mAddr: checkSonsLen(n, 2, c.config) result = semAddr(c, n.sons[1], s.name.s == "unsafeAddr") of mTypeOf: - checkSonsLen(n, 2, c.config) - result = semTypeOf(c, n.sons[1]) + result = semTypeOf(c, n) #of mArrGet: result = semArrGet(c, n, flags) #of mArrPut: result = semArrPut(c, n, flags) #of mAsgn: result = semAsgnOpr(c, n) @@ -1975,7 +1992,6 @@ proc semMagic(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode = of mCompiles: result = semCompiles(c, setMs(n, s), flags) #of mLow: result = semLowHigh(c, setMs(n, s), mLow) #of mHigh: result = semLowHigh(c, setMs(n, s), mHigh) - of mSizeOf: result = semSizeof(c, setMs(n, s)) of mIs: result = semIs(c, setMs(n, s), flags) #of mOf: result = semOf(c, setMs(n, s)) of mShallowCopy: result = semShallowCopy(c, n, flags) @@ -2030,6 +2046,7 @@ proc semMagic(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode = of mRunnableExamples: if c.config.cmd == cmdDoc and n.len >= 2 and n.lastSon.kind == nkStmtList: when false: + # some of this dead code was moved to `prepareExamples` if sfMainModule in c.module.flags: let inp = toFullPath(c.config, c.module.info) if c.runnableExamples == nil: @@ -2043,6 +2060,9 @@ proc semMagic(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode = result = setMs(n, s) else: result = c.graph.emptyNode + of mOmpParFor: + checkMinSonsLen(n, 3, c.config) + result = semDirectOp(c, n, flags) else: result = semDirectOp(c, n, flags) @@ -2255,6 +2275,7 @@ proc semBlock(c: PContext, n: PNode; flags: TExprFlags): PNode = n.sons[0] = newSymNode(labl, n.sons[0].info) suggestSym(c.config, n.sons[0].info, labl, c.graph.usageSym) styleCheckDef(c.config, labl) + onDef(n[0].info, labl) n.sons[1] = semExpr(c, n.sons[1], flags) n.typ = n.sons[1].typ if isEmptyType(n.typ): n.kind = nkBlockStmt @@ -2333,7 +2354,6 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = {checkUndeclared, checkModule, checkAmbiguity, checkPureEnumFields} var s = qualifiedLookUp(c, n, checks) if c.matchedConcept == nil: semCaptureSym(s, c.p.owner) - result = semSym(c, n, s, flags) if s.kind in {skProc, skFunc, skMethod, skConverter, skIterator}: #performProcvarCheck(c, n, s) result = symChoice(c, n, s, scClosed) @@ -2341,6 +2361,8 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = markIndirect(c, result.sym) # if isGenericRoutine(result.sym): # localError(c.config, n.info, errInstantiateXExplicitly, s.name.s) + else: + result = semSym(c, n, s, flags) of nkSym: # because of the changed symbol binding, this does not mean that we # don't have to check the symbol for semantics here again! diff --git a/compiler/semfold.nim b/compiler/semfold.nim index 0018f0755..5ec702257 100644 --- a/compiler/semfold.nim +++ b/compiler/semfold.nim @@ -37,10 +37,7 @@ proc newIntNodeT*(intVal: BiggestInt, n: PNode; g: ModuleGraph): PNode = proc newFloatNodeT*(floatVal: BiggestFloat, n: PNode; g: ModuleGraph): PNode = result = newFloatNode(nkFloatLit, floatVal) - if skipTypes(n.typ, abstractVarRange).kind == tyFloat: - result.typ = getFloatLitType(g, result) - else: - result.typ = n.typ + result.typ = n.typ result.info = n.info proc newStrNodeT*(strVal: string, n: PNode; g: ModuleGraph): PNode = @@ -177,43 +174,6 @@ proc makeRangeF(typ: PType, first, last: BiggestFloat; g: ModuleGraph): PType = result.n = n addSonSkipIntLit(result, skipTypes(typ, {tyRange})) -proc evalIs(n: PNode, lhs: PSym, g: ModuleGraph): PNode = - # XXX: This should use the standard isOpImpl - internalAssert g.config, - n.sonsLen == 3 and - lhs.typ != nil and - n[2].kind in {nkStrLit..nkTripleStrLit, nkType} - - var - res = false - t1 = lhs.typ - t2 = n[2].typ - - if t1.kind == tyTypeDesc and t2.kind != tyTypeDesc: - t1 = t1.base - - if n[2].kind in {nkStrLit..nkTripleStrLit}: - case n[2].strVal.normalize - of "closure": - let t = skipTypes(t1, abstractRange) - res = t.kind == tyProc and - t.callConv == ccClosure and - tfIterator notin t.flags - of "iterator": - let t = skipTypes(t1, abstractRange) - res = t.kind == tyProc and - t.callConv == ccClosure and - tfIterator in t.flags - else: - res = false - else: - # XXX semexprs.isOpImpl is slightly different and requires a context. yay. - let t2 = n[2].typ - res = sameType(t1, t2) - - result = newIntNode(nkIntLit, ord(res)) - result.typ = n.typ - proc fitLiteral(c: ConfigRef, n: PNode): PNode = # Trim the literal value in order to make it fit in the destination type if n == nil: @@ -307,12 +267,7 @@ proc evalOp(m: TMagic, n, a, b, c: PNode; g: ModuleGraph): PNode = of mSubF64: result = newFloatNodeT(getFloat(a) - getFloat(b), n, g) of mMulF64: result = newFloatNodeT(getFloat(a) * getFloat(b), n, g) of mDivF64: - if getFloat(b) == 0.0: - if getFloat(a) == 0.0: result = newFloatNodeT(NaN, n, g) - elif getFloat(b).classify == fcNegZero: result = newFloatNodeT(-Inf, n, g) - else: result = newFloatNodeT(Inf, n, g) - else: - result = newFloatNodeT(getFloat(a) / getFloat(b), n, g) + result = newFloatNodeT(getFloat(a) / getFloat(b), n, g) of mMaxF64: if getFloat(a) > getFloat(b): result = newFloatNodeT(getFloat(a), n, g) else: result = newFloatNodeT(getFloat(b), n, g) @@ -382,7 +337,7 @@ proc evalOp(m: TMagic, n, a, b, c: PNode; g: ModuleGraph): PNode = result = newStrNodeT(s, n, g) else: result = newStrNodeT(getStrOrChar(a), n, g) - of mStrToStr: result = a + of mStrToStr: result = newStrNodeT(getStrOrChar(a), n, g) of mEnumToStr: result = newStrNodeT(ordinalValToString(a, g), n, g) of mArrToSeq: result = copyTree(a) @@ -460,7 +415,7 @@ proc rangeCheck(n: PNode, value: BiggestInt; g: ModuleGraph) = err = value < firstOrd(g.config, n.typ) or value > lastOrd(g.config, n.typ) if err: localError(g.config, n.info, "cannot convert " & $value & - " to " & typeToString(n.typ)) + " to " & typeToString(n.typ)) proc foldConv(n, a: PNode; g: ModuleGraph; check = false): PNode = let dstTyp = skipTypes(n.typ, abstractRange) @@ -649,18 +604,6 @@ proc getConstExpr(m: PSym, n: PNode; g: ModuleGraph): PNode = of mNone: # If it has no sideEffect, it should be evaluated. But not here. return - of mSizeOf: - var a = n.sons[1] - if computeSize(g.config, a.typ) < 0: - localError(g.config, a.info, "cannot evaluate 'sizeof' because its type is not defined completely") - result = nil - elif skipTypes(a.typ, typedescInst+{tyRange, tyArray}).kind in - IntegralTypes+NilableTypes+{tySet}: - #{tyArray,tyObject,tyTuple}: - result = newIntNodeT(getSize(g.config, a.typ), n, g) - else: - result = nil - # XXX: size computation for complex types is still wrong of mLow: result = newIntNodeT(firstOrd(g.config, n.sons[1].typ), n, g) of mHigh: @@ -688,9 +631,9 @@ proc getConstExpr(m: PSym, n: PNode; g: ModuleGraph): PNode = of mConStrStr: result = foldConStrStr(m, n, g) of mIs: - let lhs = getConstExpr(m, n[1], g) - if lhs != nil and lhs.kind == nkSym: - result = evalIs(n, lhs.sym, g) + # The only kind of mIs node that comes here is one depending on some + # generic parameter and that's (hopefully) handled at instantiation time + discard else: result = magicCall(m, n, g) except OverflowError: diff --git a/compiler/semgnrc.nim b/compiler/semgnrc.nim index 7be0610a2..5972f3b55 100644 --- a/compiler/semgnrc.nim +++ b/compiler/semgnrc.nim @@ -76,14 +76,14 @@ proc semGenericStmtSymbol(c: PContext, n: PNode, s: PSym, result = symChoice(c, n, s, scOpen) of skTemplate: if macroToExpandSym(s): - styleCheckUse(n.info, s) + onUse(n.info, s) result = semTemplateExpr(c, n, s, {efNoSemCheck}) result = semGenericStmt(c, result, {}, ctx) else: result = symChoice(c, n, s, scOpen) of skMacro: if macroToExpandSym(s): - styleCheckUse(n.info, s) + onUse(n.info, s) result = semMacroExpr(c, n, n, s, {efNoSemCheck}) result = semGenericStmt(c, result, {}, ctx) else: @@ -96,20 +96,20 @@ proc semGenericStmtSymbol(c: PContext, n: PNode, s: PSym, result = n else: result = newSymNodeTypeDesc(s, n.info) - styleCheckUse(n.info, s) + onUse(n.info, s) of skParam: result = n - styleCheckUse(n.info, s) + onUse(n.info, s) of skType: if (s.typ != nil) and (s.typ.flags * {tfGenericTypeParam, tfImplicitTypeParam} == {}): result = newSymNodeTypeDesc(s, n.info) else: result = n - styleCheckUse(n.info, s) + onUse(n.info, s) else: result = newSymNode(s, n.info) - styleCheckUse(n.info, s) + onUse(n.info, s) proc lookup(c: PContext, n: PNode, flags: TSemGenericFlags, ctx: var GenericCtx): PNode = @@ -172,6 +172,7 @@ proc addTempDecl(c: PContext; n: PNode; kind: TSymKind) = let s = newSymS(skUnknown, getIdentNode(c, n), c) addPrelimDecl(c, s) styleCheckDef(c.config, n.info, s, kind) + onDef(n.info, s) proc semGenericStmt(c: PContext, n: PNode, flags: TSemGenericFlags, ctx: var GenericCtx): PNode = @@ -225,12 +226,12 @@ proc semGenericStmt(c: PContext, n: PNode, var mixinContext = false if s != nil: incl(s.flags, sfUsed) - mixinContext = s.magic in {mDefined, mDefinedInScope, mCompiles, mRunnableExamples} + mixinContext = s.magic in {mDefined, mDefinedInScope, mCompiles} let sc = symChoice(c, fn, s, if s.isMixedIn: scForceOpen else: scOpen) case s.kind of skMacro: if macroToExpand(s) and sc.safeLen <= 1: - styleCheckUse(fn.info, s) + onUse(fn.info, s) result = semMacroExpr(c, n, n, s, {efNoSemCheck}) result = semGenericStmt(c, result, flags, ctx) else: @@ -239,7 +240,7 @@ proc semGenericStmt(c: PContext, n: PNode, mixinContext = true of skTemplate: if macroToExpand(s) and sc.safeLen <= 1: - styleCheckUse(fn.info, s) + onUse(fn.info, s) result = semTemplateExpr(c, n, s, {efNoSemCheck}) result = semGenericStmt(c, result, flags, ctx) else: @@ -255,24 +256,24 @@ proc semGenericStmt(c: PContext, n: PNode, discard of skProc, skFunc, skMethod, skIterator, skConverter, skModule: result.sons[0] = sc - # do not check of 's.magic==mRoof' here because it might be some - # other '^' but after overload resolution the proper one: - if ctx.bracketExpr != nil and n.len == 2 and s.name.s == "^": - result.add ctx.bracketExpr first = 1 + # We're not interested in the example code during this pass so let's + # skip it + if s.magic == mRunnableExamples: + inc first of skGenericParam: result.sons[0] = newSymNodeTypeDesc(s, fn.info) - styleCheckUse(fn.info, s) + onUse(fn.info, s) first = 1 of skType: # bad hack for generics: if (s.typ != nil) and (s.typ.kind != tyGenericParam): result.sons[0] = newSymNodeTypeDesc(s, fn.info) - styleCheckUse(fn.info, s) + onUse(fn.info, s) first = 1 else: result.sons[0] = newSymNode(s, fn.info) - styleCheckUse(fn.info, s) + onUse(fn.info, s) first = 1 elif fn.kind == nkDotExpr: result.sons[0] = fuzzyLookup(c, fn, flags, ctx, mixinContext) @@ -322,7 +323,14 @@ proc semGenericStmt(c: PContext, n: PNode, n.sons[i] = semGenericStmtScope(c, n.sons[i], flags, ctx) of nkWhenStmt: for i in countup(0, sonsLen(n)-1): - n.sons[i] = semGenericStmt(c, n.sons[i], flags+{withinMixin}, ctx) + # bug #8603: conditions of 'when' statements are not + # in a 'mixin' context: + let it = n[i] + if it.kind in {nkElifExpr, nkElifBranch}: + n.sons[i].sons[0] = semGenericStmt(c, it[0], flags, ctx) + n.sons[i].sons[1] = semGenericStmt(c, it[1], flags+{withinMixin}, ctx) + else: + n.sons[i] = semGenericStmt(c, it, flags+{withinMixin}, ctx) of nkWhileStmt: openScope(c) for i in countup(0, sonsLen(n)-1): diff --git a/compiler/seminst.nim b/compiler/seminst.nim index 4bf1e6ef2..17f61c7dd 100644 --- a/compiler/seminst.nim +++ b/compiler/seminst.nim @@ -118,7 +118,7 @@ proc freshGenSyms(n: PNode, owner, orig: PSym, symMap: var TIdTable) = n.sym = x elif s.owner.kind == skPackage: #echo "copied this ", s.name.s - x = copySym(s, false) + x = copySym(s) x.owner = owner idTablePut(symMap, s, x) n.sym = x @@ -146,9 +146,8 @@ proc instantiateBody(c: PContext, n, params: PNode, result, orig: PSym) = idTablePut(symMap, params[i].sym, result.typ.n[param.position+1].sym) freshGenSyms(b, result, orig, symMap) b = semProcBody(c, b) - b = hloBody(c, b) - n.sons[bodyPos] = transformBody(c.graph, c.module, b, result) - #echo "code instantiated ", result.name.s + result.ast[bodyPos] = hloBody(c, b) + trackProc(c.graph, result, result.ast[bodyPos]) excl(result.flags, sfForward) dec c.inGenericInst @@ -338,7 +337,7 @@ proc generateInstance(c: PContext, fn: PSym, pt: TIdTable, c.matchedConcept = nil let oldScope = c.currentScope while not isTopLevel(c): c.currentScope = c.currentScope.parent - result = copySym(fn, false) + result = copySym(fn) incl(result.flags, sfFromGeneric) result.owner = fn result.ast = n @@ -348,7 +347,7 @@ proc generateInstance(c: PContext, fn: PSym, pt: TIdTable, let gp = n.sons[genericParamsPos] internalAssert c.config, gp.kind != nkEmpty n.sons[namePos] = newSymNode(result) - pushInfoContext(c.config, info) + pushInfoContext(c.config, info, fn.detailedInfo) var entry = TInstantiation.new entry.sym = result # we need to compare both the generic types and the concrete types: @@ -377,7 +376,7 @@ proc generateInstance(c: PContext, fn: PSym, pt: TIdTable, #if c.compilesContextId == 0: rawHandleSelf(c, result) entry.compilesId = c.compilesContextId - fn.procInstCache.safeAdd(entry) + fn.procInstCache.add(entry) c.generics.add(makeInstPair(fn, entry)) if n.sons[pragmasPos].kind != nkEmpty: pragma(c, result, n.sons[pragmasPos], allRoutinePragmas) diff --git a/compiler/semmagic.nim b/compiler/semmagic.nim index 8bfa5545e..df2c084a1 100644 --- a/compiler/semmagic.nim +++ b/compiler/semmagic.nim @@ -21,8 +21,15 @@ proc semAddr(c: PContext; n: PNode; isUnsafeAddr=false): PNode = result.typ = makePtrType(c, x.typ) proc semTypeOf(c: PContext; n: PNode): PNode = + var m = BiggestInt 1 # typeOfIter + if n.len == 3: + let mode = semConstExpr(c, n[2]) + if mode.kind != nkIntLit: + localError(c.config, n.info, "typeof: cannot evaluate 'mode' parameter at compile-time") + else: + m = mode.intVal result = newNodeI(nkTypeOfExpr, n.info) - let typExpr = semExprWithType(c, n, {efInTypeof}) + let typExpr = semExprWithType(c, n[1], if m == 1: {efInTypeof} else: {}) result.add typExpr result.typ = makeTypeDesc(c, typExpr.typ) @@ -299,7 +306,13 @@ proc semOf(c: PContext, n: PNode): PNode = result.typ = getSysType(c.graph, n.info, tyBool) return result elif diff == high(int): - localError(c.config, n.info, "'$1' cannot be of this subtype" % typeToString(a)) + if commonSuperclass(a, b) == nil: + localError(c.config, n.info, "'$1' cannot be of this subtype" % typeToString(a)) + else: + message(c.config, n.info, hintConditionAlwaysFalse, renderTree(n)) + result = newIntNode(nkIntLit, 0) + result.info = n.info + result.typ = getSysType(c.graph, n.info, tyBool) else: localError(c.config, n.info, "'of' takes 2 arguments") n.typ = getSysType(c.graph, n.info, tyBool) @@ -307,20 +320,68 @@ proc semOf(c: PContext, n: PNode): PNode = proc magicsAfterOverloadResolution(c: PContext, n: PNode, flags: TExprFlags): PNode = + ## This is the preferred code point to implement magics. + ## This function basically works like a macro, with the difference + ## that it is implemented in the compiler and not on the nimvm. + ## ``c`` the current module, a symbol table to a very good approximation + ## ``n`` the ast like it would be passed to a real macro + ## ``flags`` Some flags for more contextual information on how the + ## "macro" is calld. + case n[0].sym.magic of mAddr: checkSonsLen(n, 2, c.config) result = semAddr(c, n.sons[1], n[0].sym.name.s == "unsafeAddr") of mTypeOf: - checkSonsLen(n, 2, c.config) - result = semTypeOf(c, n.sons[1]) - of mArrGet: result = semArrGet(c, n, flags) - of mArrPut: result = semArrPut(c, n, flags) + result = semTypeOf(c, n) + of mSizeOf: + # TODO there is no proper way to find out if a type cannot be queried for the size. + let size = getSize(c.config, n[1].typ) + # We just assume here that the type might come from the c backend + if size == szUnknownSize: + # Forward to the c code generation to emit a `sizeof` in the C code. + result = n + elif size >= 0: + result = newIntNode(nkIntLit, size) + result.info = n.info + result.typ = n.typ + else: + localError(c.config, n.info, "cannot evaluate 'sizeof' because its type is not defined completely, type: " & n[1].typ.typeToString) + result = n + of mAlignOf: + result = newIntNode(nkIntLit, getAlign(c.config, n[1].typ)) + result.info = n.info + result.typ = n.typ + of mOffsetOf: + var dotExpr: PNode + + block findDotExpr: + if n[1].kind == nkDotExpr: + dotExpr = n[1] + elif n[1].kind == nkCheckedFieldExpr: + dotExpr = n[1][0] + else: + illFormedAst(n, c.config) + + assert dotExpr != nil + + let value = dotExpr[0] + let member = dotExpr[1] + + discard computeSize(c.config, value.typ) + + result = newIntNode(nkIntLit, member.sym.offset) + result.info = n.info + result.typ = n.typ + of mArrGet: + result = semArrGet(c, n, flags) + of mArrPut: + result = semArrPut(c, n, flags) of mAsgn: if n[0].sym.name.s == "=": result = semAsgnOpr(c, n) else: - result = n + result = semShallowCopy(c, n, flags) of mIsPartOf: result = semIsPartOf(c, n, flags) of mTypeTrait: result = semTypeTraits(c, n) of mAstToStr: diff --git a/compiler/semparallel.nim b/compiler/semparallel.nim index 0d780bdee..137c5d336 100644 --- a/compiler/semparallel.nim +++ b/compiler/semparallel.nim @@ -493,6 +493,6 @@ proc liftParallel*(g: ModuleGraph; owner: PSym; n: PNode): PNode = result = newNodeI(nkStmtList, n.info) generateAliasChecks(a, result) result.add varSection - result.add callCodegenProc(g, "openBarrier", barrier) + result.add callCodegenProc(g, "openBarrier", barrier.info, barrier) result.add transformSpawn(g, owner, body, barrier) - result.add callCodegenProc(g, "closeBarrier", barrier) + result.add callCodegenProc(g, "closeBarrier", barrier.info, barrier) diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim index 0a9de674b..75dea069f 100644 --- a/compiler/sempass2.nim +++ b/compiler/sempass2.nim @@ -9,9 +9,12 @@ import intsets, ast, astalgo, msgs, renderer, magicsys, types, idents, trees, - wordrecg, strutils, options, guards, writetracking, lineinfos, + wordrecg, strutils, options, guards, lineinfos, semfold, modulegraphs +when not defined(leanCompiler): + import writetracking + when defined(useDfa): import dfa @@ -48,10 +51,12 @@ type tags: PNode # list of tags bottom, inTryStmt: int owner: PSym + owner_module: PSym init: seq[int] # list of initialized variables guards: TModel # nested guards locked: seq[PNode] # locked locations gcUnsafe, isRecursive, isToplevel, hasSideEffect, inEnforcedGcSafe: bool + inEnforcedNoSideEffects: bool maxLockLevel, currLockLevel: TLockLevel config: ConfigRef graph: ModuleGraph @@ -190,10 +195,10 @@ proc markGcUnsafe(a: PEffects; reason: PNode) = when true: template markSideEffect(a: PEffects; reason: typed) = - a.hasSideEffect = true + if not a.inEnforcedNoSideEffects: a.hasSideEffect = true else: template markSideEffect(a: PEffects; reason: typed) = - a.hasSideEffect = true + if not a.inEnforcedNoSideEffects: a.hasSideEffect = true markGcUnsafe(a, reason) proc listGcUnsafety(s: PSym; onlyWarning: bool; cycleCheck: var IntSet; conf: ConfigRef) = @@ -562,8 +567,10 @@ proc trackOperand(tracked: PEffects, n: PNode, paramType: PType) = if op != nil and op.kind == tyProc and n.skipConv.kind != nkNilLit: internalAssert tracked.config, op.n.sons[0].kind == nkEffectList var effectList = op.n.sons[0] - let s = n.skipConv - if s.kind == nkSym and s.sym.kind in routineKinds: + var s = n.skipConv + if s.kind == nkCast and s[1].typ.kind == tyProc: + s = s[1] + if s.kind == nkSym and s.sym.kind in routineKinds and isNoEffectList(effectList): propagateEffects(tracked, n, s.sym) elif isNoEffectList(effectList): if isForwardedProc(n): @@ -709,9 +716,13 @@ proc track(tracked: PEffects, n: PNode) = for i in 0 ..< safeLen(n): track(tracked, n.sons[i]) of nkCallKinds: + if getConstExpr(tracked.owner_module, n, tracked.graph) != nil: + return # p's effects are ours too: - let a = n.sons[0] + var a = n.sons[0] let op = a.typ + if a.kind == nkCast and a[1].typ.kind == tyProc: + a = a[1] # XXX: in rare situations, templates and macros will reach here after # calling getAst(templateOrMacro()). Currently, templates and macros # are indistinguishable from normal procs (both have tyProc type) and @@ -836,15 +847,20 @@ proc track(tracked: PEffects, n: PNode) = let oldLocked = tracked.locked.len let oldLockLevel = tracked.currLockLevel var enforcedGcSafety = false + var enforceNoSideEffects = false for i in 0 ..< pragmaList.len: let pragma = whichPragma(pragmaList.sons[i]) if pragma == wLocks: lockLocations(tracked, pragmaList.sons[i]) elif pragma == wGcSafe: enforcedGcSafety = true + elif pragma == wNosideeffect: + enforceNoSideEffects = true if enforcedGcSafety: tracked.inEnforcedGcSafe = true + if enforceNoSideEffects: tracked.inEnforcedNoSideEffects = true track(tracked, n.lastSon) if enforcedGcSafety: tracked.inEnforcedGcSafe = false + if enforceNoSideEffects: tracked.inEnforcedNoSideEffects = false setLen(tracked.locked, oldLocked) tracked.currLockLevel = oldLockLevel of nkTypeSection, nkProcDef, nkConverterDef, nkMethodDef, nkIteratorDef, @@ -934,6 +950,7 @@ proc initEffects(g: ModuleGraph; effects: PNode; s: PSym; t: var TEffects) = t.exc = effects.sons[exceptionEffects] t.tags = effects.sons[tagEffects] t.owner = s + t.owner_module = s.getModule t.init = @[] t.guards.s = @[] t.guards.o = initOperators(g) diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index d5c5b7f86..e0542e1e7 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -41,8 +41,13 @@ proc semDiscard(c: PContext, n: PNode): PNode = checkSonsLen(n, 1, c.config) if n.sons[0].kind != nkEmpty: n.sons[0] = semExprWithType(c, n.sons[0]) - if isEmptyType(n.sons[0].typ) or n.sons[0].typ.kind == tyNone or n.sons[0].kind == nkTypeOfExpr: + let sonType = n.sons[0].typ + let sonKind = n.sons[0].kind + if isEmptyType(sonType) or sonType.kind == tyNone or n.sons[0].kind == nkTypeOfExpr: localError(c.config, n.info, errInvalidDiscard) + if sonType.kind == tyProc and sonKind notin nkCallKinds: + # tyProc is disallowed to prevent ``discard foo`` to be valid, when ``discard foo()`` is meant. + localError(c.config, n.info, "illegal discard proc, did you mean: " & $n[0] & "()") proc semBreakOrContinue(c: PContext, n: PNode): PNode = result = n @@ -61,12 +66,12 @@ proc semBreakOrContinue(c: PContext, n: PNode): PNode = incl(s.flags, sfUsed) n.sons[0] = x suggestSym(c.config, x.info, s, c.graph.usageSym) - styleCheckUse(x.info, s) + onUse(x.info, s) else: localError(c.config, n.info, errInvalidControlFlowX % s.name.s) else: localError(c.config, n.info, errGenerated, "'continue' cannot have a label") - elif (c.p.nestedLoopCounter <= 0) and (c.p.nestedBlockCounter <= 0): + elif (c.p.nestedLoopCounter <= 0) and ((c.p.nestedBlockCounter <= 0) or n.kind == nkContinueStmt): localError(c.config, n.info, errInvalidControlFlowX % renderTree(n, {renderNoComments})) @@ -207,6 +212,8 @@ proc semTry(c: PContext, n: PNode; flags: TExprFlags): PNode = typ = commonType(typ, n[0].typ) var last = sonsLen(n) - 1 + var catchAllExcepts = 0 + for i in countup(1, last): let a = n.sons[i] checkMinSonsLen(a, 1, c.config) @@ -227,8 +234,16 @@ proc semTry(c: PContext, n: PNode; flags: TExprFlags): PNode = # Overwrite symbol in AST with the symbol in the symbol table. a[0][2] = newSymNode(symbol, a[0][2].info) + elif a.len == 1: + # count number of ``except: body`` blocks + inc catchAllExcepts + else: # support ``except KeyError, ValueError, ... : body`` + if catchAllExcepts > 0: + # if ``except: body`` already encountered, + # cannot be followed by a ``except KeyError, ... : body`` block + inc catchAllExcepts var is_native, is_imported: bool for j in 0..a.len-2: let tmp = semExceptBranchType(a[j]) @@ -238,9 +253,18 @@ proc semTry(c: PContext, n: PNode; flags: TExprFlags): PNode = if is_native and is_imported: localError(c.config, a[0].info, "Mix of imported and native exception types is not allowed in one except branch") - elif a.kind != nkFinally: + elif a.kind == nkFinally: + if i != n.len-1: + localError(c.config, a.info, "Only one finally is allowed after all other branches") + + else: illFormedAst(n, c.config) + if catchAllExcepts > 1: + # if number of ``except: body`` blocks is greater than 1 + # or more specific exception follows a general except block, it is invalid + localError(c.config, a.info, "Only one general except clause is allowed after more specific exceptions") + # last child of an nkExcept/nkFinally branch is a statement: a[^1] = semExprBranchScope(c, a[^1]) if a.kind != nkFinally: typ = commonType(typ, a[^1]) @@ -299,7 +323,6 @@ proc semIdentDef(c: PContext, n: PNode, kind: TSymKind): PSym = if result.owner.kind == skModule: incl(result.flags, sfGlobal) suggestSym(c.config, n.info, result, c.graph.usageSym) - styleCheckDef(c.config, result) proc checkNilable(c: PContext; v: PSym) = if {sfGlobal, sfImportC} * v.flags == {sfGlobal} and @@ -340,6 +363,8 @@ proc semUsing(c: PContext; n: PNode): PNode = let typ = semTypeNode(c, a.sons[length-2], nil) for j in countup(0, length-3): let v = semIdentDef(c, a.sons[j], skParam) + styleCheckDef(c.config, v) + onDef(a[j].info, v) v.typ = typ strTableIncl(c.signatures, v) else: @@ -470,6 +495,8 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode = addToVarSection(c, result, n, a) continue var v = semIdentDef(c, a.sons[j], symkind) + styleCheckDef(c.config, v) + onDef(a[j].info, v) if sfGenSym notin v.flags and not isDiscardUnderscore(v): addInterfaceDecl(c, v) when oKeepVariableNames: @@ -523,6 +550,8 @@ proc semConst(c: PContext, n: PNode): PNode = if a.kind != nkConstDef: illFormedAst(a, c.config) checkSonsLen(a, 3, c.config) var v = semIdentDef(c, a.sons[0], skConst) + styleCheckDef(c.config, v) + onDef(a[0].info, v) var typ: PType = nil if a.sons[1].kind != nkEmpty: typ = semTypeNode(c, a.sons[1], nil) @@ -566,6 +595,7 @@ proc symForVar(c: PContext, n: PNode): PSym = let m = if n.kind == nkPragmaExpr: n.sons[0] else: n result = newSymG(skForVar, m, c) styleCheckDef(c.config, result) + onDef(n.info, result) if n.kind == nkPragmaExpr: pragma(c, result, n.sons[1], forVarPragmas) @@ -671,7 +701,7 @@ proc handleCaseStmtMacro(c: PContext; n: PNode): PNode = if r.state == csMatch: var match = r.calleeSym markUsed(c.config, n[0].info, match, c.graph.usageSym) - styleCheckUse(n[0].info, match) + onUse(n[0].info, match) # but pass 'n' to the 'match' macro, not 'n[0]': r.call.sons[1] = n @@ -804,9 +834,10 @@ proc semRaise(c: PContext, n: PNode): PNode = checkSonsLen(n, 1, c.config) if n[0].kind != nkEmpty: n[0] = semExprWithType(c, n[0]) - let typ = n[0].typ + var typ = n[0].typ if not isImportedException(typ, c.config): - if typ.kind != tyRef or typ.lastSon.kind != tyObject: + typ = typ.skipTypes({tyAlias, tyGenericInst}) + if typ.kind != tyRef: localError(c.config, n.info, errExprCannotBeRaised) if typ.len > 0 and not isException(typ.lastSon): localError(c.config, n.info, "raised object of type $1 does not inherit from Exception", @@ -853,6 +884,8 @@ proc typeSectionLeftSidePass(c: PContext, n: PNode) = let typsym = pkg.tab.strTableGet(typName) if typsym.isNil: s = semIdentDef(c, name[1], skType) + styleCheckDef(c.config, s) + onDef(name[1].info, s) s.typ = newTypeS(tyObject, c) s.typ.sym = s s.flags.incl sfForward @@ -866,6 +899,8 @@ proc typeSectionLeftSidePass(c: PContext, n: PNode) = s = typsym else: s = semIdentDef(c, name, skType) + styleCheckDef(c.config, s) + onDef(name.info, s) s.typ = newTypeS(tyForward, c) s.typ.sym = s # process pragmas: if name.kind == nkPragmaExpr: @@ -1199,13 +1234,6 @@ proc copyExcept(n: PNode, i: int): PNode = for j in 0..<n.len: if j != i: result.add(n.sons[j]) -proc lookupMacro(c: PContext, n: PNode): PSym = - if n.kind == nkSym: - result = n.sym - if result.kind notin {skMacro, skTemplate}: result = nil - else: - result = searchInScopes(c, considerQuotedIdent(c, n), {skMacro, skTemplate}) - proc semProcAnnotation(c: PContext, prc: PNode; validPragmas: TSpecialWords): PNode = var n = prc.sons[pragmasPos] @@ -1213,39 +1241,53 @@ proc semProcAnnotation(c: PContext, prc: PNode; for i in countup(0, n.len-1): var it = n.sons[i] var key = if it.kind in nkPragmaCallKinds and it.len >= 1: it.sons[0] else: it - let m = lookupMacro(c, key) - if m == nil: - if key.kind == nkIdent and key.ident.id == ord(wDelegator): - if considerQuotedIdent(c, prc.sons[namePos]).s == "()": - prc.sons[namePos] = newIdentNode(c.cache.idDelegator, prc.info) - prc.sons[pragmasPos] = copyExcept(n, i) - else: - localError(c.config, prc.info, "only a call operator can be a delegator") + + if whichPragma(it) != wInvalid: + # Not a custom pragma + continue + elif strTableGet(c.userPragmas, considerQuotedIdent(c, key)) != nil: + # User-defined pragma continue - elif sfCustomPragma in m.flags: - continue # semantic check for custom pragma happens later in semProcAux # we transform ``proc p {.m, rest.}`` into ``m(do: proc p {.rest.})`` and # let the semantic checker deal with it: - var x = newNodeI(nkCall, n.info) - x.add(newSymNode(m)) - prc.sons[pragmasPos] = copyExcept(n, i) - if prc[pragmasPos].kind != nkEmpty and prc[pragmasPos].len == 0: - prc.sons[pragmasPos] = c.graph.emptyNode + var x = newNodeI(nkCall, key.info) + x.add(key) if it.kind in nkPragmaCallKinds and it.len > 1: # pass pragma arguments to the macro too: for i in 1..<it.len: x.add(it.sons[i]) + + # Drop the pragma from the list, this prevents getting caught in endless + # recursion when the nkCall is semanticized + prc.sons[pragmasPos] = copyExcept(n, i) + if prc[pragmasPos].kind != nkEmpty and prc[pragmasPos].len == 0: + prc.sons[pragmasPos] = c.graph.emptyNode + x.add(prc) # recursion assures that this works for multiple macro annotations too: - result = semExpr(c, x) + var r = semOverloadedCall(c, x, x, {skMacro}, {efNoUndeclared}) + if r == nil: + # Restore the old list of pragmas since we couldn't process this + prc.sons[pragmasPos] = n + # No matching macro was found but there's always the possibility this may + # be a .pragma. template instead + continue + + doAssert r.sons[0].kind == nkSym + # Expand the macro here + result = semMacroExpr(c, r, r, r.sons[0].sym, {}) + + doAssert result != nil + # since a proc annotation can set pragmas, we process these here again. # This is required for SqueakNim-like export pragmas. if result.kind in procDefs and result[namePos].kind == nkSym and result[pragmasPos].kind != nkEmpty: pragma(c, result[namePos].sym, result[pragmasPos], validPragmas) + return proc setGenericParamsMisc(c: PContext; n: PNode): PNode = @@ -1301,8 +1343,8 @@ proc semLambda(c: PContext, n: PNode, flags: TExprFlags): PNode = pushProcCon(c, s) addResult(c, s.typ.sons[0], n.info, skProc) addResultNode(c, n) - let semBody = hloBody(c, semProcBody(c, n.sons[bodyPos])) - n.sons[bodyPos] = transformBody(c.graph, c.module, semBody, s) + s.ast[bodyPos] = hloBody(c, semProcBody(c, n.sons[bodyPos])) + trackProc(c.graph, s, s.ast[bodyPos]) popProcCon(c) elif efOperand notin flags: localError(c.config, n.info, errGenericLambdaNotAllowed) @@ -1342,8 +1384,8 @@ proc semInferredLambda(c: PContext, pt: TIdTable, n: PNode): PNode = pushProcCon(c, s) addResult(c, n.typ.sons[0], n.info, skProc) addResultNode(c, n) - let semBody = hloBody(c, semProcBody(c, n.sons[bodyPos])) - n.sons[bodyPos] = transformBody(c.graph, c.module, semBody, s) + s.ast[bodyPos] = hloBody(c, semProcBody(c, n.sons[bodyPos])) + trackProc(c.graph, s, s.ast[bodyPos]) popProcCon(c) popOwner(c) closeScope(c) @@ -1490,7 +1532,7 @@ proc semMethodPrototype(c: PContext; s: PSym; n: PNode) = tyAlias, tySink}) if x.kind == tyObject and t.len-1 == n.sons[genericParamsPos].len: foundObj = true - x.methods.safeAdd((col,s)) + x.methods.add((col,s)) if not foundObj: message(c.config, n.info, warnDeprecated, "generic method not attachable to object type") else: @@ -1589,6 +1631,8 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind, pragma(c, s, n.sons[pragmasPos], validPragmas) else: implicitPragmas(c, s, n, validPragmas) + styleCheckDef(c.config, s) + onDef(n[namePos].info, s) else: if n.sons[pragmasPos].kind != nkEmpty: pragma(c, s, n.sons[pragmasPos], validPragmas) @@ -1601,6 +1645,8 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind, if proto.typ.callConv != s.typ.callConv or proto.typ.flags < s.typ.flags: localError(c.config, n.sons[pragmasPos].info, errPragmaOnlyInHeaderOfProcX % ("'" & proto.name.s & "' from " & c.config$proto.info)) + styleCheckDef(c.config, s) + onDefResolveForward(n[namePos].info, proto) if sfForward notin proto.flags and proto.magic == mNone: wrongRedefinition(c, n.info, proto.name.s, proto.info) excl(proto.flags, sfForward) @@ -1633,7 +1679,7 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind, localError(c.config, n.info, "the overloaded " & s.name.s & " operator has to be enabled with {.experimental: \"callOperator\".}") - if n.sons[bodyPos].kind != nkEmpty: + if n.sons[bodyPos].kind != nkEmpty and sfError notin s.flags: # for DLL generation it is annoying to check for sfImportc! if sfBorrow in s.flags: localError(c.config, n.sons[bodyPos].info, errImplOfXNotAllowed % s.name.s) @@ -1656,10 +1702,10 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind, if lfDynamicLib notin s.loc.flags: # no semantic checking for importc: - let semBody = hloBody(c, semProcBody(c, n.sons[bodyPos])) + s.ast[bodyPos] = hloBody(c, semProcBody(c, n.sons[bodyPos])) # unfortunately we cannot skip this step when in 'system.compiles' # context as it may even be evaluated in 'system.compiles': - n.sons[bodyPos] = transformBody(c.graph, c.module, semBody, s) + trackProc(c.graph, s, s.ast[bodyPos]) else: if s.typ.sons[0] != nil and kind != skIterator: addDecl(c, newSym(skUnknown, getIdent(c.cache, "result"), nil, n.info)) @@ -1677,7 +1723,7 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind, else: if s.kind == skMethod: semMethodPrototype(c, s, n) if proto != nil: localError(c.config, n.info, errImplOfXexpected % proto.name.s) - if {sfImportc, sfBorrow} * s.flags == {} and s.magic == mNone: + if {sfImportc, sfBorrow, sfError} * s.flags == {} and s.magic == mNone: incl(s.flags, sfForward) elif sfBorrow in s.flags: semBorrow(c, n, s) sideEffectsCheck(c, s) @@ -1814,7 +1860,7 @@ proc semPragmaBlock(c: PContext, n: PNode): PNode = for i in 0 ..< pragmaList.len: case whichPragma(pragmaList.sons[i]) of wLine: setLine(result, pragmaList.sons[i].info) - of wLocks, wGcSafe: + of wLocks, wGcSafe, wNosideeffect: result = n result.typ = n.sons[1].typ of wNoRewrite: @@ -1917,7 +1963,8 @@ proc semStmtList(c: PContext, n: PNode, flags: TExprFlags): PNode = case n.sons[j].kind of nkPragma, nkCommentStmt, nkNilLit, nkEmpty, nkBlockExpr, nkBlockStmt, nkState: discard - else: localError(c.config, n.sons[j].info, "unreachable statement after 'return'") + else: localError(c.config, n.sons[j].info, + "unreachable statement after 'return' statement or '{.noReturn.}' proc") else: discard if result.len == 1 and diff --git a/compiler/semtempl.nim b/compiler/semtempl.nim index 396696422..14507cf9d 100644 --- a/compiler/semtempl.nim +++ b/compiler/semtempl.nim @@ -64,6 +64,7 @@ proc symChoice(c: PContext, n: PNode, s: PSym, r: TSymChoiceRule): PNode = # for instance 'nextTry' is both in tables.nim and astalgo.nim ... result = newSymNode(s, n.info) markUsed(c.config, n.info, s, c.graph.usageSym) + onUse(n.info, s) else: # semantic checking requires a type; ``fitNode`` deals with it # appropriately @@ -75,6 +76,7 @@ proc symChoice(c: PContext, n: PNode, s: PSym, r: TSymChoiceRule): PNode = if a.kind != skModule: incl(a.flags, sfUsed) addSon(result, newSymNode(a, n.info)) + onUse(n.info, a) a = nextOverloadIter(o, c, n) proc semBindStmt(c: PContext, n: PNode, toBind: var IntSet): PNode = @@ -160,7 +162,7 @@ proc onlyReplaceParams(c: var TemplCtx, n: PNode): PNode = if s.owner == c.owner and s.kind == skParam: incl(s.flags, sfUsed) result = newSymNode(s, n.info) - styleCheckUse(n.info, s) + onUse(n.info, s) else: for i in 0 ..< n.safeLen: result.sons[i] = onlyReplaceParams(c, n.sons[i]) @@ -208,19 +210,20 @@ proc addLocalDecl(c: var TemplCtx, n: var PNode, k: TSymKind) = # So we need only check the *current* scope. let s = localSearchInScope(c.c, considerQuotedIdent(c.c, ident)) if s != nil and s.owner == c.owner and sfGenSym in s.flags: - styleCheckUse(n.info, s) + onUse(n.info, s) replaceIdentBySym(c.c, n, newSymNode(s, n.info)) elif not (n.kind == nkSym and sfGenSym in n.sym.flags): let local = newGenSym(k, ident, c) addPrelimDecl(c.c, local) styleCheckDef(c.c.config, n.info, local) + onDef(n.info, local) replaceIdentBySym(c.c, n, newSymNode(local, n.info)) else: replaceIdentBySym(c.c, n, ident) proc semTemplSymbol(c: PContext, n: PNode, s: PSym): PNode = incl(s.flags, sfUsed) - # we do not call styleCheckUse here, as the identifier is not really + # we do not call onUse here, as the identifier is not really # resolved here. We will fixup the used identifiers later. case s.kind of skUnknown: @@ -245,7 +248,7 @@ proc semRoutineInTemplName(c: var TemplCtx, n: PNode): PNode = if s.owner == c.owner and (s.kind == skParam or sfGenSym in s.flags): incl(s.flags, sfUsed) result = newSymNode(s, n.info) - styleCheckUse(n.info, s) + onUse(n.info, s) else: for i in countup(0, safeLen(n) - 1): result.sons[i] = semRoutineInTemplName(c, n.sons[i]) @@ -261,6 +264,7 @@ proc semRoutineInTemplBody(c: var TemplCtx, n: PNode, k: TSymKind): PNode = s.ast = n addPrelimDecl(c.c, s) styleCheckDef(c.c.config, n.info, s) + onDef(n.info, s) n.sons[namePos] = newSymNode(s, n.sons[namePos].info) else: n.sons[namePos] = ident @@ -314,7 +318,7 @@ proc semTemplBody(c: var TemplCtx, n: PNode): PNode = if s.owner == c.owner and s.kind == skParam: incl(s.flags, sfUsed) result = newSymNode(s, n.info) - styleCheckUse(n.info, s) + onUse(n.info, s) elif contains(c.toBind, s.id): result = symChoice(c.c, n, s, scClosed) elif contains(c.toMixin, s.name.id): @@ -324,7 +328,7 @@ proc semTemplBody(c: var TemplCtx, n: PNode): PNode = # var yz: T incl(s.flags, sfUsed) result = newSymNode(s, n.info) - styleCheckUse(n.info, s) + onUse(n.info, s) else: result = semTemplSymbol(c.c, n, s) of nkBind: @@ -382,6 +386,7 @@ proc semTemplBody(c: var TemplCtx, n: PNode): PNode = let s = newGenSym(skLabel, n.sons[0], c) addPrelimDecl(c.c, s) styleCheckDef(c.c.config, s) + onDef(n[0].info, s) n.sons[0] = newSymNode(s, n.sons[0].info) n.sons[1] = semTemplBody(c, n.sons[1]) closeScope(c) @@ -493,7 +498,9 @@ proc semTemplBody(c: var TemplCtx, n: PNode): PNode = else: result = semTemplBodySons(c, n) of nkCallKinds-{nkPostfix}: - result = semTemplBodySons(c, n) + # do not transform runnableExamples (bug #9143) + if not isRunnableExamples(n[0]): + result = semTemplBodySons(c, n) of nkDotExpr, nkAccQuoted: # dotExpr is ambiguous: note that we explicitly allow 'x.TemplateParam', # so we use the generic code for nkDotExpr too @@ -503,7 +510,7 @@ proc semTemplBody(c: var TemplCtx, n: PNode): PNode = if s.owner == c.owner and s.kind == skParam and n.kind == nkAccQuoted and n.len == 1: incl(s.flags, sfUsed) - styleCheckUse(n.info, s) + onUse(n.info, s) return newSymNode(s, n.info) elif contains(c.toBind, s.id): return symChoice(c.c, n, s, scClosed) @@ -551,6 +558,7 @@ proc semTemplateDef(c: PContext, n: PNode): PNode = else: s = semIdentVis(c, skTemplate, n.sons[0], {}) styleCheckDef(c.config, s) + onDef(n[0].info, s) # check parameter list: #s.scope = c.currentScope pushOwner(c, s) @@ -633,7 +641,7 @@ proc semPatternBody(c: var TemplCtx, n: PNode): PNode = # semtypes.addParamOrResult). Within the pattern we have to ensure # to use the param with the proper type though: incl(s.flags, sfUsed) - styleCheckUse(n.info, s) + onUse(n.info, s) let x = c.owner.typ.n.sons[s.position+1].sym assert x.name == s.name result = newSymNode(x, n.info) @@ -704,28 +712,26 @@ proc semPatternBody(c: var TemplCtx, n: PNode): PNode = elif templToExpand(s): return semPatternBody(c, semTemplateExpr(c.c, n, s, {efNoSemCheck})) - if n.kind == nkInfix and n.sons[0].kind == nkIdent: + if n.kind == nkInfix and (let id = considerQuotedIdent(c.c, n[0]); id != nil): # we interpret `*` and `|` only as pattern operators if they occur in # infix notation, so that '`*`(a, b)' can be used for verbatim matching: - let opr = n.sons[0] - if opr.ident.s == "*" or opr.ident.s == "**": + if id.s == "*" or id.s == "**": result = newNodeI(nkPattern, n.info, n.len) - result.sons[0] = opr + result.sons[0] = newIdentNode(id, n.info) result.sons[1] = semPatternBody(c, n.sons[1]) result.sons[2] = expectParam(c, n.sons[2]) return - elif opr.ident.s == "|": + elif id.s == "|": result = newNodeI(nkPattern, n.info, n.len) - result.sons[0] = opr + result.sons[0] = newIdentNode(id, n.info) result.sons[1] = semPatternBody(c, n.sons[1]) result.sons[2] = semPatternBody(c, n.sons[2]) return - if n.kind == nkPrefix and n.sons[0].kind == nkIdent: - let opr = n.sons[0] - if opr.ident.s == "~": + if n.kind == nkPrefix and (let id = considerQuotedIdent(c.c, n[0]); id != nil): + if id.s == "~": result = newNodeI(nkPattern, n.info, n.len) - result.sons[0] = opr + result.sons[0] = newIdentNode(id, n.info) result.sons[1] = semPatternBody(c, n.sons[1]) return diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index 5394e291f..a011a8fc8 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -122,6 +122,7 @@ proc semEnum(c: PContext, n: PNode, prev: PType): PType = if not isPure: strTableAdd(c.module.tab, e) addSon(result.n, newSymNode(e)) styleCheckDef(c.config, e) + onDef(e.info, e) if sfGenSym notin e.flags: if not isPure: addDecl(c, e) else: importPureEnumField(c, e) @@ -377,7 +378,7 @@ proc semTypeIdent(c: PContext, n: PNode): PSym = result = qualifiedLookUp(c, n, {checkAmbiguity, checkUndeclared}) if result != nil: markUsed(c.config, n.info, result, c.graph.usageSym) - styleCheckUse(n.info, result) + onUse(n.info, result) if result.kind == skParam and result.typ.kind == tyTypeDesc: # This is a typedesc param. is it already bound? @@ -391,7 +392,7 @@ proc semTypeIdent(c: PContext, n: PNode): PSym = localError(c.config, n.info, errTypeExpected) return errorSym(c, n) result = result.typ.sym.copySym - result.typ = copyType(result.typ, result.typ.owner, true) + result.typ = exactReplica(result.typ) result.typ.flags.incl tfUnresolved if result.kind == skGenericParam: @@ -466,6 +467,7 @@ proc semTuple(c: PContext, n: PNode, prev: PType): PType = addSon(result.n, newSymNode(field)) addSonSkipIntLit(result, typ) styleCheckDef(c.config, a.sons[j].info, field) + onDef(field.info, field) if result.n.len == 0: result.n = nil proc semIdentVis(c: PContext, kind: TSymKind, n: PNode, @@ -505,7 +507,6 @@ proc semIdentWithPragma(c: PContext, kind: TSymKind, n: PNode, else: discard else: result = semIdentVis(c, kind, n, allowed) - styleCheckDef(c.config, n.info, result) proc checkForOverlap(c: PContext, t: PNode, currentEx, branchIndex: int) = let ex = t[branchIndex][currentEx].skipConv @@ -587,12 +588,12 @@ proc semCaseBranch(c: PContext, t, branch: PNode, branchIndex: int, checkForOverlap(c, t, i, branchIndex) proc semRecordNodeAux(c: PContext, n: PNode, check: var IntSet, pos: var int, - father: PNode, rectype: PType) + father: PNode, rectype: PType, hasCaseFields = false) proc semRecordCase(c: PContext, n: PNode, check: var IntSet, pos: var int, father: PNode, rectype: PType) = var a = copyNode(n) checkMinSonsLen(n, 2, c.config) - semRecordNodeAux(c, n.sons[0], check, pos, a, rectype) + semRecordNodeAux(c, n.sons[0], check, pos, a, rectype, hasCaseFields = true) if a.sons[0].kind != nkSym: internalError(c.config, "semRecordCase: discriminant is no symbol") return @@ -619,13 +620,13 @@ proc semRecordCase(c: PContext, n: PNode, check: var IntSet, pos: var int, checkSonsLen(b, 1, c.config) else: illFormedAst(n, c.config) delSon(b, sonsLen(b) - 1) - semRecordNodeAux(c, lastSon(n.sons[i]), check, pos, b, rectype) + semRecordNodeAux(c, lastSon(n.sons[i]), check, pos, b, rectype, hasCaseFields = true) if chckCovered and covered != lengthOrd(c.config, a.sons[0].typ): localError(c.config, a.info, "not all cases are covered") addSon(father, a) proc semRecordNodeAux(c: PContext, n: PNode, check: var IntSet, pos: var int, - father: PNode, rectype: PType) = + father: PNode, rectype: PType, hasCaseFields = false) = if n == nil: return case n.kind of nkRecWhen: @@ -694,7 +695,7 @@ proc semRecordNodeAux(c: PContext, n: PNode, check: var IntSet, pos: var int, f.position = pos if fieldOwner != nil and {sfImportc, sfExportc} * fieldOwner.flags != {} and - f.loc.r == nil: + not hasCaseFields and f.loc.r == nil: f.loc.r = rope(f.name.s) f.flags = f.flags + ({sfImportc, sfExportc} * fieldOwner.flags) inc(pos) @@ -703,6 +704,7 @@ proc semRecordNodeAux(c: PContext, n: PNode, check: var IntSet, pos: var int, if a.kind == nkEmpty: addSon(father, newSymNode(f)) else: addSon(a, newSymNode(f)) styleCheckDef(c.config, f) + onDef(f.info, f) if a.kind != nkEmpty: addSon(father, a) of nkSym: # This branch only valid during generic object @@ -963,7 +965,8 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode, let liftBody = liftingWalk(paramType.lastSon, true) if liftBody != nil: result = liftBody - result.shouldHaveMeta + result.flags.incl tfHasMeta + #result.shouldHaveMeta of tyGenericInvocation: for i in 1 ..< paramType.len: @@ -987,7 +990,7 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode, of tyGenericParam: markUsed(c.config, info, paramType.sym, c.graph.usageSym) - styleCheckUse(info, paramType.sym) + onUse(info, paramType.sym) if tfWildcard in paramType.flags: paramType.flags.excl tfWildcard paramType.sym.kind = skType @@ -1109,6 +1112,7 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode, rawAddSon(result, finalType) addParamOrResult(c, arg, kind) styleCheckDef(c.config, a.sons[j].info, arg) + onDef(a[j].info, arg) var r: PType if n.sons[0].kind != nkEmpty: @@ -1413,6 +1417,20 @@ proc semTypeof(c: PContext; n: PNode; prev: PType): PType = fixupTypeOf(c, prev, t) result = t.typ +proc semTypeof2(c: PContext; n: PNode; prev: PType): PType = + openScope(c) + var m = BiggestInt 1 # typeOfIter + if n.len == 3: + let mode = semConstExpr(c, n[2]) + if mode.kind != nkIntLit: + localError(c.config, n.info, "typeof: cannot evaluate 'mode' parameter at compile-time") + else: + m = mode.intVal + let t = semExprWithType(c, n[1], if m == 1: {efInTypeof} else: {}) + closeScope(c) + fixupTypeOf(c, prev, t) + result = t.typ + proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = result = nil inc c.inTypeContext @@ -1493,6 +1511,8 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = elif op.id == ord(wType): checkSonsLen(n, 2, c.config) result = semTypeof(c, n[1], prev) + elif op.s == "typeof" and n[0].kind == nkSym and n[0].sym.magic == mTypeof: + result = semTypeOf2(c, n, prev) else: if c.inGenericContext > 0 and n.kind == nkCall: result = makeTypeFromExpr(c, n.copyTree) @@ -1510,6 +1530,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = case s.magic of mArray: result = semArray(c, n, prev) of mOpenArray: result = semContainer(c, n, tyOpenArray, "openarray", prev) + of mUncheckedArray: result = semContainer(c, n, tyUncheckedArray, "UncheckedArray", prev) of mRange: result = semRange(c, n, prev) of mSet: result = semSet(c, n, prev) of mOrdinal: result = semOrdinal(c, n, prev) @@ -1603,8 +1624,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = result = prev of nkSym: let s = getGenSym(c, n.sym) - if s.kind == skType and s.typ != nil or - s.kind == skParam and s.typ.kind == tyTypeDesc: + if s.typ != nil and (s.kind == skType or s.typ.kind == tyTypeDesc): var t = if s.kind == skType: s.typ @@ -1620,7 +1640,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = assignType(prev, t) result = prev markUsed(c.config, n.info, n.sym, c.graph.usageSym) - styleCheckUse(n.info, n.sym) + onUse(n.info, n.sym) else: if s.kind != skError: localError(c.config, n.info, errTypeExpected) result = newOrPrevType(tyError, prev, c) @@ -1708,7 +1728,7 @@ proc processMagicType(c: PContext, m: PSym) = of mBool: setMagicType(c.config, m, tyBool, 1) of mChar: setMagicType(c.config, m, tyChar, 1) of mString: - setMagicType(c.config, m, tyString, c.config.target.ptrSize) + setMagicType(c.config, m, tyString, szUncomputedSize) rawAddSon(m.typ, getSysType(c.graph, m.info, tyChar)) when false: if c.config.selectedGc == gcDestructors: @@ -1727,10 +1747,8 @@ proc processMagicType(c: PContext, m: PSym) = setMagicType(c.config, m, tyAnything, 0) else: setMagicType(c.config, m, tyExpr, 0) - if m.name.s == "expr": m.typ.flags.incl tfOldSchoolExprStmt of mStmt: setMagicType(c.config, m, tyStmt, 0) - if m.name.s == "stmt": m.typ.flags.incl tfOldSchoolExprStmt of mTypeDesc, mType: setMagicType(c.config, m, tyTypeDesc, 0) rawAddSon(m.typ, newTypeS(tyNone, c)) @@ -1740,26 +1758,28 @@ proc processMagicType(c: PContext, m: PSym) = of mVoidType: setMagicType(c.config, m, tyVoid, 0) of mArray: - setMagicType(c.config, m, tyArray, 0) + setMagicType(c.config, m, tyArray, szUncomputedSize) of mOpenArray: - setMagicType(c.config, m, tyOpenArray, 0) + setMagicType(c.config, m, tyOpenArray, szUncomputedSize) of mVarargs: - setMagicType(c.config, m, tyVarargs, 0) + setMagicType(c.config, m, tyVarargs, szUncomputedSize) of mRange: - setMagicType(c.config, m, tyRange, 0) + setMagicType(c.config, m, tyRange, szUncomputedSize) rawAddSon(m.typ, newTypeS(tyNone, c)) of mSet: - setMagicType(c.config, m, tySet, 0) + setMagicType(c.config, m, tySet, szUncomputedSize) + of mUncheckedArray: + setMagicType(c.config, m, tyUncheckedArray, szUncomputedSize) of mSeq: - setMagicType(c.config, m, tySequence, 0) + setMagicType(c.config, m, tySequence, szUncomputedSize) if c.config.selectedGc == gcDestructors: incl m.typ.flags, tfHasAsgn assert c.graph.sysTypes[tySequence] == nil c.graph.sysTypes[tySequence] = m.typ of mOpt: - setMagicType(c.config, m, tyOpt, 0) + setMagicType(c.config, m, tyOpt, szUncomputedSize) of mOrdinal: - setMagicType(c.config, m, tyOrdinal, 0) + setMagicType(c.config, m, tyOrdinal, szUncomputedSize) rawAddSon(m.typ, newTypeS(tyNone, c)) of mPNimrodNode: incl m.typ.flags, tfTriggersCompileTime @@ -1767,7 +1787,7 @@ proc processMagicType(c: PContext, m: PSym) = of mBuiltinType: case m.name.s of "lent": setMagicType(c.config, m, tyLent, c.config.target.ptrSize) - of "sink": setMagicType(c.config, m, tySink, 0) + of "sink": setMagicType(c.config, m, tySink, szUncomputedSize) else: localError(c.config, m.info, errTypeExpected) else: localError(c.config, m.info, errTypeExpected) @@ -1844,4 +1864,3 @@ proc semGenericParamList(c: PContext, n: PNode, father: PType = nil): PNode = s.position = result.len addSon(result, newSymNode(s)) if sfGenSym notin s.flags: addDecl(c, s) - diff --git a/compiler/semtypinst.nim b/compiler/semtypinst.nim index a6067dfc9..ffa913f1d 100644 --- a/compiler/semtypinst.nim +++ b/compiler/semtypinst.nim @@ -69,7 +69,7 @@ proc cacheTypeInst*(inst: PType) = let t = if gt.kind == tyGenericBody: gt.lastSon else: gt if t.kind in {tyStatic, tyGenericParam} + tyTypeClasses: return - gt.sym.typeInstCache.safeAdd(inst) + gt.sym.typeInstCache.add(inst) type @@ -233,7 +233,7 @@ proc replaceTypeVarsS(cl: var TReplTypeVars, s: PSym): PSym = #result = PSym(idTableGet(cl.symMap, s)) #if result == nil: - result = copySym(s, false) + result = copySym(s) incl(result.flags, sfFromGeneric) #idTablePut(cl.symMap, s, result) result.owner = s.owner @@ -373,12 +373,17 @@ proc handleGenericInvocation(cl: var TReplTypeVars, t: PType): PType = else: newbody.lastSon.typeInst = result cl.c.typesWithOps.add((newbody, result)) - let methods = skipTypes(bbody, abstractPtrs).methods - for col, meth in items(methods): - # we instantiate the known methods belonging to that type, this causes - # them to be registered and that's enough, so we 'discard' the result. - discard cl.c.instTypeBoundOp(cl.c, meth, result, cl.info, - attachedAsgn, col) + let mm = skipTypes(bbody, abstractPtrs) + if tfFromGeneric notin mm.flags: + # bug #5479, prevent endless recursions here: + incl mm.flags, tfFromGeneric + let methods = mm.methods + for col, meth in items(methods): + # we instantiate the known methods belonging to that type, this causes + # them to be registered and that's enough, so we 'discard' the result. + discard cl.c.instTypeBoundOp(cl.c, meth, result, cl.info, + attachedAsgn, col) + excl mm.flags, tfFromGeneric proc eraseVoidParams*(t: PType) = # transform '(): void' into '()' because old parts of the compiler really diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index 407e34619..d66e8d121 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -13,9 +13,9 @@ import intsets, ast, astalgo, semdata, types, msgs, renderer, lookups, semtypinst, magicsys, condsyms, idents, lexer, options, parampatterns, strutils, trees, - linter, lineinfos + linter, lineinfos, lowerings, modulegraphs -when defined(booting) or defined(nimsuggest): +when (defined(booting) or defined(nimsuggest)) and not defined(leanCompiler): import docgen type @@ -186,7 +186,7 @@ proc sumGeneric(t: PType): int = var isvar = 1 while true: case t.kind - of tyGenericInst, tyArray, tyRef, tyPtr, tyDistinct, + of tyGenericInst, tyArray, tyRef, tyPtr, tyDistinct, tyUncheckedArray, tyOpenArray, tyVarargs, tySet, tyRange, tySequence, tyGenericBody, tyLent: t = t.lastSon @@ -432,7 +432,7 @@ proc handleFloatRange(f, a: PType): TTypeRelation = else: result = isNone proc genericParamPut(c: var TCandidate; last, fGenericOrigin: PType) = - if fGenericOrigin != nil and last.kind == tyGenericInst and + if fGenericOrigin != nil and last.kind == tyGenericInst and last.len-1 == fGenericOrigin.len: for i in countup(1, sonsLen(fGenericOrigin) - 1): let x = PType(idTableGet(c.bindings, fGenericOrigin.sons[i])) @@ -728,7 +728,7 @@ proc matchUserTypeClass*(m: var TCandidate; ff, a: PType): PType = else: makeTypeDesc(c, typ) - typeParams.safeAdd((param, typ)) + typeParams.add((param, typ)) addDecl(c, param) @@ -757,7 +757,7 @@ proc matchUserTypeClass*(m: var TCandidate; ff, a: PType): PType = if collectDiagnostics: m.c.config.writelnHook = oldWriteHook for msg in diagnostics: - m.diagnostics.safeAdd msg + m.diagnostics.add msg m.diagnosticsEnabled = true if checkedBody == nil: return nil @@ -1027,15 +1027,15 @@ proc typeRelImpl(c: var TCandidate, f, aOrig: PType, result = typeRel(c, aOrig.base, candidate) if result != isNone: - c.inferredTypes.safeAdd aOrig + c.inferredTypes.add aOrig aOrig.sons.add candidate result = isEqual return template doBind: bool = trDontBind notin flags - # var and static arguments match regular modifier-free types - var a = maybeSkipDistinct(c, aOrig.skipTypes({tyStatic, tyVar, tyLent}), c.calleeSym) + # var, sink and static arguments match regular modifier-free types + var a = maybeSkipDistinct(c, aOrig.skipTypes({tyStatic, tyVar, tyLent, tySink}), c.calleeSym) # XXX: Theoretically, maybeSkipDistinct could be called before we even # start the param matching process. This could be done in `prepareOperand` # for example, but unfortunately `prepareOperand` is not called in certain @@ -1045,7 +1045,7 @@ proc typeRelImpl(c: var TCandidate, f, aOrig: PType, return typeRel(c, f, lastSon(aOrig)) if a.kind == tyGenericInst and - skipTypes(f, {tyVar, tyLent}).kind notin { + skipTypes(f, {tyVar, tyLent, tySink}).kind notin { tyGenericBody, tyGenericInvocation, tyGenericInst, tyGenericParam} + tyTypeClasses: return typeRel(c, f, lastSon(a)) @@ -1199,15 +1199,18 @@ proc typeRelImpl(c: var TCandidate, f, aOrig: PType, if lengthOrd(c.c.config, fRange) != lengthOrd(c.c.config, aRange): result = isNone else: discard + of tyUncheckedArray: + if a.kind == tyUncheckedArray: + result = typeRel(c, base(f), base(a)) + if result < isGeneric: result = isNone + else: discard of tyOpenArray, tyVarargs: # varargs[expr] is special too but handled earlier. So we only need to # handle varargs[stmt] which is the same as varargs[typed]: if f.kind == tyVarargs: if tfVarargs in a.flags: return typeRel(c, f.base, a.lastSon) - if tfOldSchoolExprStmt in f.sons[0].flags: - if f.sons[0].kind == tyExpr: return - elif f.sons[0].kind == tyStmt: return + if f.sons[0].kind == tyStmt: return template matchArrayOrSeq(aBase: PType) = let ff = f.base @@ -1292,7 +1295,7 @@ proc typeRelImpl(c: var TCandidate, f, aOrig: PType, of tyDistinct: if a.kind == tyDistinct: if sameDistinctTypes(f, a): result = isEqual - elif f.base.kind == tyAnything: result = isGeneric + #elif f.base.kind == tyAnything: result = isGeneric # issue 4435 elif c.coerceDistincts: result = typeRel(c, f.base, a) elif a.kind == tyNil and f.base.kind in NilableTypes: result = f.allowsNil @@ -1362,6 +1365,8 @@ proc typeRelImpl(c: var TCandidate, f, aOrig: PType, if a.len == 1: let pointsTo = a.sons[0].skipTypes(abstractInst) if pointsTo.kind == tyChar: result = isConvertible + elif pointsTo.kind == tyUncheckedArray and pointsTo.sons[0].kind == tyChar: + result = isConvertible elif pointsTo.kind == tyArray and firstOrd(nil, pointsTo.sons[0]) == 0 and skipTypes(pointsTo.sons[0], {tyRange}).kind in {tyInt..tyInt64} and pointsTo.sons[1].kind == tyChar: @@ -1376,6 +1381,7 @@ proc typeRelImpl(c: var TCandidate, f, aOrig: PType, of tyGenericInst: var prev = PType(idTableGet(c.bindings, f)) + let origF = f var f = if prev == nil: f else: prev let roota = a.skipGenericAlias @@ -1433,7 +1439,8 @@ proc typeRelImpl(c: var TCandidate, f, aOrig: PType, result = isNone else: - result = typeRel(c, lastSon(f), a) + assert lastSon(origF) != nil + result = typeRel(c, lastSon(origF), a) if result != isNone and a.kind != tyNil: put(c, f, a) @@ -1718,7 +1725,7 @@ proc typeRelImpl(c: var TCandidate, f, aOrig: PType, else: result = typeRel(c, f.base, a) if result != isNone: - c.inferredTypes.safeAdd f + c.inferredTypes.add f f.sons.add a of tyTypeDesc: @@ -1749,7 +1756,7 @@ proc typeRelImpl(c: var TCandidate, f, aOrig: PType, result = isNone of tyStmt: - if aOrig != nil and tfOldSchoolExprStmt notin f.flags: + if aOrig != nil: put(c, f, aOrig) result = isGeneric @@ -1817,29 +1824,45 @@ proc userConvMatch(c: PContext, m: var TCandidate, f, a: PType, let srca = typeRel(m, src, a) if srca notin {isEqual, isGeneric, isSubtype}: continue + # What's done below matches the logic in ``matchesAux`` let constraint = c.converters[i].typ.n[1].sym.constraint if not constraint.isNil and not matchNodeKinds(constraint, arg): continue + if src.kind in {tyVar, tyLent} and not arg.isLValue: + continue let destIsGeneric = containsGenericType(dest) if destIsGeneric: dest = generateTypeInstance(c, m.bindings, arg, dest) let fdest = typeRel(m, f, dest) - if fdest in {isEqual, isGeneric}: + if fdest in {isEqual, isGeneric} and not (dest.kind == tyLent and f.kind == tyVar): markUsed(c.config, arg.info, c.converters[i], c.graph.usageSym) var s = newSymNode(c.converters[i]) s.typ = c.converters[i].typ s.info = arg.info result = newNodeIT(nkHiddenCallConv, arg.info, dest) addSon(result, s) + # We build the call expression by ourselves in order to avoid passing this + # expression trough the semantic check phase once again so let's make sure + # it is correct var param: PNode = nil if srca == isSubtype: param = implicitConv(nkHiddenSubConv, src, copyTree(arg), m, c) + elif src.kind == tyVar: + # Analyse the converter return type + param = newNodeIT(nkHiddenAddr, arg.info, s.typ[1]) + param.addSon(copyTree(arg)) else: param = copyTree(arg) addSon(result, param) + + if dest.kind in {tyVar, tyLent}: + dest.flags.incl tfVarIsPtr + result = newDeref(result) + inc(m.convMatches) - m.genericConverter = srca == isGeneric or destIsGeneric + if m.genericConverter == false: + m.genericConverter = srca == isGeneric or destIsGeneric return result proc localConvMatch(c: PContext, m: var TCandidate, f, a: PType, @@ -1909,10 +1932,13 @@ proc paramTypesMatchAux(m: var TCandidate, f, a: PType, else: var evaluated = c.semTryConstExpr(c, arg) if evaluated != nil: - arg.typ = newTypeS(tyStatic, c) - arg.typ.sons = @[evaluated.typ] - arg.typ.n = evaluated - a = arg.typ + # Don't build the type in-place because `evaluated` and `arg` may point + # to the same object and we'd end up creating recursive types (#9255) + let typ = newTypeS(tyStatic, c) + typ.sons = @[evaluated.typ] + typ.n = evaluated + arg.typ = typ + a = typ else: if m.callee.kind == tyGenericBody: if f.kind == tyStatic and typeRel(m, f.base, a) != isNone: @@ -2129,7 +2155,7 @@ proc paramTypesMatch*(m: var TCandidate, f, a: PType, else: # only one valid interpretation found: markUsed(m.c.config, arg.info, arg.sons[best].sym, m.c.graph.usageSym) - styleCheckUse(arg.info, arg.sons[best].sym) + onUse(arg.info, arg.sons[best].sym) result = paramTypesMatchAux(m, f, arg.sons[best].typ, arg.sons[best], argOrig) when false: @@ -2193,8 +2219,7 @@ proc incrIndexType(t: PType) = inc t.sons[0].n.sons[1].intVal template isVarargsUntyped(x): untyped = - x.kind == tyVarargs and x.sons[0].kind == tyExpr and - tfOldSchoolExprStmt notin x.sons[0].flags + x.kind == tyVarargs and x.sons[0].kind == tyExpr proc matchesAux(c: PContext, n, nOrig: PNode, m: var TCandidate, marker: var IntSet) = @@ -2206,8 +2231,15 @@ proc matchesAux(c: PContext, n, nOrig: PNode, else: m.state = csNoMatch return + if formal.typ.kind == tyVar: - if not n.isLValue: + let arg_converter = if arg.kind == nkHiddenDeref: arg[0] else: arg + if arg_converter.kind == nkHiddenCallConv: + if arg_converter.typ.kind != tyVar: + m.state = csNoMatch + m.mutabilityProblem = uint8(f-1) + return + elif not n.isLValue: m.state = csNoMatch m.mutabilityProblem = uint8(f-1) return @@ -2218,6 +2250,7 @@ proc matchesAux(c: PContext, n, nOrig: PNode, else: 0 # iterates over the actual given arguments a = 1 + arg: PNode # current prepared argument m.state = csMatch # until proven otherwise m.call = newNodeI(n.kind, n.info) @@ -2272,7 +2305,7 @@ proc matchesAux(c: PContext, n, nOrig: PNode, m.typedescMatched = false n.sons[a].sons[1] = prepareOperand(c, formal.typ, n.sons[a].sons[1]) n.sons[a].typ = n.sons[a].sons[1].typ - var arg = paramTypesMatch(m, formal.typ, n.sons[a].typ, + arg = paramTypesMatch(m, formal.typ, n.sons[a].typ, n.sons[a].sons[1], n.sons[a].sons[1]) if arg == nil: m.state = csNoMatch @@ -2309,7 +2342,7 @@ proc matchesAux(c: PContext, n, nOrig: PNode, m.typedescMatched = false incl(marker, formal.position) n.sons[a] = prepareOperand(c, formal.typ, n.sons[a]) - var arg = paramTypesMatch(m, formal.typ, n.sons[a].typ, + arg = paramTypesMatch(m, formal.typ, n.sons[a].typ, n.sons[a], nOrig.sons[a]) if arg != nil and m.baseTypeMatch and container != nil: addSon(container, arg) @@ -2343,7 +2376,7 @@ proc matchesAux(c: PContext, n, nOrig: PNode, m.baseTypeMatch = false m.typedescMatched = false n.sons[a] = prepareOperand(c, formal.typ, n.sons[a]) - var arg = paramTypesMatch(m, formal.typ, n.sons[a].typ, + arg = paramTypesMatch(m, formal.typ, n.sons[a].typ, n.sons[a], nOrig.sons[a]) if arg == nil: m.state = csNoMatch @@ -2600,4 +2633,3 @@ tests: yes int, ordinal no string, ordinal - diff --git a/compiler/sizealignoffsetimpl.nim b/compiler/sizealignoffsetimpl.nim new file mode 100644 index 000000000..2f50a99f6 --- /dev/null +++ b/compiler/sizealignoffsetimpl.nim @@ -0,0 +1,416 @@ +# +# +# The Nim Compiler +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# +## code owner: Arne Döring +## e-mail: arne.doering@gmx.net + +proc align(address, alignment: BiggestInt): BiggestInt = + result = (address + (alignment - 1)) and not (alignment - 1) + +const + ## a size is concidered "unknown" when it is an imported type from C + ## or C++. + szUnknownSize* = -3 + szIllegalRecursion* = -2 + szUncomputedSize* = -1 + +proc computeSizeAlign(conf: ConfigRef; typ: PType): void + +proc computeSubObjectAlign(conf: ConfigRef; n: PNode): BiggestInt = + ## returns object alignment + case n.kind + of nkRecCase: + assert(n.sons[0].kind == nkSym) + result = computeSubObjectAlign(conf, n.sons[0]) + for i in 1 ..< sonsLen(n): + let child = n.sons[i] + case child.kind + of nkOfBranch, nkElse: + let align = computeSubObjectAlign(conf, child.lastSon) + if align < 0: + return align + result = max(result, align) + else: + internalError(conf, "computeSubObjectAlign") + of nkRecList: + result = 1 + for i, child in n.sons: + let align = computeSubObjectAlign(conf, n.sons[i]) + if align < 0: + return align + result = max(result, align) + of nkSym: + computeSizeAlign(conf, n.sym.typ) + result = n.sym.typ.align + else: + result = 1 + +proc computeObjectOffsetsFoldFunction(conf: ConfigRef; n: PNode, initialOffset: BiggestInt): tuple[offset, align: BiggestInt] = + ## ``offset`` is the offset within the object, after the node has been written, no padding bytes added + ## ``align`` maximum alignment from all sub nodes + if n.typ != nil and n.typ.size == szIllegalRecursion: + result.offset = szIllegalRecursion + result.align = szIllegalRecursion + return + + result.align = 1 + case n.kind + of nkRecCase: + assert(n.sons[0].kind == nkSym) + let (kindOffset, kindAlign) = computeObjectOffsetsFoldFunction(conf, n.sons[0], initialOffset) + + var maxChildAlign: BiggestInt = 0 + for i in 1 ..< sonsLen(n): + let child = n.sons[i] + case child.kind + of nkOfBranch, nkElse: + # offset parameter cannot be known yet, it needs to know the alignment first + let align = computeSubObjectAlign(conf, n.sons[i].lastSon) + + if align == szIllegalRecursion: + result.offset = szIllegalRecursion + result.align = szIllegalRecursion + return + + if align == szUnknownSize or maxChildAlign == szUnknownSize: + maxChildAlign = szUnknownSize + else: + maxChildAlign = max(maxChildAlign, align) + else: + internalError(conf, "computeObjectOffsetsFoldFunction(record case branch)") + + if maxChildAlign == szUnknownSize: + result.align = szUnknownSize + result.offset = szUnknownSize + else: + # the union neds to be aligned first, before the offsets can be assigned + let kindUnionOffset = align(kindOffset, maxChildAlign) + + var maxChildOffset: BiggestInt = 0 + for i in 1 ..< sonsLen(n): + let (offset, align) = computeObjectOffsetsFoldFunction(conf, n.sons[i].lastSon, kindUnionOffset) + maxChildOffset = max(maxChildOffset, offset) + + result.align = max(kindAlign, maxChildAlign) + result.offset = maxChildOffset + + + of nkRecList: + result.align = 1 # maximum of all member alignments + var offset = initialOffset + + for i, child in n.sons: + let (new_offset, align) = computeObjectOffsetsFoldFunction(conf, child, offset) + + if new_offset == szIllegalRecursion: + result.offset = szIllegalRecursion + result.align = szIllegalRecursion + return + + elif new_offset == szUnknownSize or offset == szUnknownSize: + # if anything is unknown, the rest becomes unknown as well + offset = szUnknownSize + result.align = szUnknownSize + + else: + offset = new_offset + result.align = max(result.align, align) + + # final alignment + if offset == szUnknownSize: + result.offset = szUnknownSize + else: + result.offset = align(offset, result.align) + + of nkSym: + var size = szUnknownSize + var align = szUnknownSize + + if n.sym.bitsize == 0: # 0 represents bitsize not set + computeSizeAlign(conf, n.sym.typ) + size = n.sym.typ.size.int + align = n.sym.typ.align.int + + result.align = align + if initialOffset == szUnknownSize or size == szUnknownSize: + n.sym.offset = szUnknownSize + result.offset = szUnknownSize + else: + n.sym.offset = align(initialOffset, align).int + result.offset = n.sym.offset + n.sym.typ.size + else: + result.align = szUnknownSize + result.offset = szUnknownSize + +proc computePackedObjectOffsetsFoldFunction(conf: ConfigRef; n: PNode, initialOffset: BiggestInt, debug: bool): BiggestInt = + ## ``result`` is the offset within the object, after the node has been written, no padding bytes added + case n.kind + of nkRecCase: + assert(n.sons[0].kind == nkSym) + let kindOffset = computePackedObjectOffsetsFoldFunction(conf, n.sons[0], initialOffset, debug) + # the union neds to be aligned first, before the offsets can be assigned + let kindUnionOffset = kindOffset + + var maxChildOffset: BiggestInt = kindUnionOffset + for i in 1 ..< sonsLen(n): + let offset = computePackedObjectOffsetsFoldFunction(conf, n.sons[i].lastSon, kindUnionOffset, debug) + maxChildOffset = max(maxChildOffset, offset) + result = maxChildOffset + of nkRecList: + result = initialOffset + for i, child in n.sons: + result = computePackedObjectOffsetsFoldFunction(conf, child, result, debug) + if result == szIllegalRecursion: + break + of nkSym: + computeSizeAlign(conf, n.sym.typ) + n.sym.offset = initialOffset.int + result = n.sym.offset + n.sym.typ.size + else: + result = szUnknownSize + +# TODO this one needs an alignment map of the individual types + +proc computeSizeAlign(conf: ConfigRef; typ: PType) = + ## computes and sets ``size`` and ``align`` members of ``typ`` + + let hasSize = typ.size != szUncomputedSize + let hasAlign = typ.align != szUncomputedSize + + if hasSize and hasAlign: + # nothing to do, size and align already computed + return + + # This function can only calculate both, size and align at the same time. + # If one of them is already set this value is stored here and reapplied + let revertSize = typ.size + let revertAlign = typ.align + defer: + if hasSize: + typ.size = revertSize + if hasAlign: + typ.align = revertAlign + + if typ.size == szIllegalRecursion or typ.align == szIllegalRecursion: + # we are already computing the size of the type + # --> illegal recursion in type + return + + # mark computation in progress + typ.size = szIllegalRecursion + typ.align = szIllegalRecursion + + var maxAlign, sizeAccum, length: BiggestInt + + var tk = typ.kind + case tk + of tyProc: + if typ.callConv == ccClosure: + typ.size = 2 * conf.target.ptrSize + else: + typ.size = conf.target.ptrSize + typ.align = int16(conf.target.ptrSize) + + of tyNil: + typ.size = conf.target.ptrSize + typ.align = int16(conf.target.ptrSize) + + of tyString: + if conf.selectedGC == gcDestructors: + typ.size = conf.target.ptrSize * 2 + else: + typ.size = conf.target.ptrSize + typ.align = int16(conf.target.ptrSize) + + of tyCString, tySequence, tyPtr, tyRef, tyVar, tyLent, tyOpenArray: + let base = typ.lastSon + if base == typ: + # this is not the correct location to detect ``type A = ptr A`` + typ.size = szIllegalRecursion + typ.align = szIllegalRecursion + return + + # recursive tuplers are not allowed and should be detected in the frontend + if base.kind == tyTuple: + computeSizeAlign(conf, base) + if base.size < 0: + typ.size = base.size + typ.align = base.align + return + + typ.align = int16(conf.target.ptrSize) + if typ.kind == tySequence and conf.selectedGC == gcDestructors: + typ.size = conf.target.ptrSize * 2 + else: + typ.size = conf.target.ptrSize + + of tyArray: + computeSizeAlign(conf, typ.sons[1]) + let elemSize = typ.sons[1].size + if elemSize < 0: + typ.size = elemSize + typ.align = int16(elemSize) + else: + typ.size = lengthOrd(conf, typ.sons[0]) * elemSize + typ.align = typ.sons[1].align + + of tyUncheckedArray: + let base = typ.lastSon + computeSizeAlign(conf, base) + typ.size = szUnknownSize + typ.align = base.align + of tyEnum: + if firstOrd(conf, typ) < 0: + typ.size = 4 # use signed int32 + typ.align = 4 + else: + length = lastOrd(conf, typ) # BUGFIX: use lastOrd! + if length + 1 < `shl`(1, 8): + typ.size = 1 + typ.align = 1 + elif length + 1 < `shl`(1, 16): + typ.size = 2 + typ.align = 2 + elif length + 1 < `shl`(BiggestInt(1), 32): + typ.size = 4 + typ.align = 4 + else: + typ.size = 8 + typ.align = 8 + of tySet: + if typ.sons[0].kind == tyGenericParam: + typ.size = szUncomputedSize + typ.align = szUncomputedSize # in original version this was 1 + else: + length = lengthOrd(conf, typ.sons[0]) + if length <= 8: + typ.size = 1 + elif length <= 16: + typ.size = 2 + elif length <= 32: + typ.size = 4 + elif length <= 64: + typ.size = 8 + elif align(length, 8) mod 8 == 0: + typ.size = align(length, 8) div 8 + else: + typ.size = align(length, 8) div 8 + 1 + typ.align = int16(typ.size) + of tyRange: + computeSizeAlign(conf, typ.sons[0]) + typ.size = typ.sons[0].size + typ.align = typ.sons[0].align + of tyTuple: + maxAlign = 1 + sizeAccum = 0 + for i in countup(0, sonsLen(typ) - 1): + let child = typ.sons[i] + computeSizeAlign(conf, child) + if child.size < 0: + typ.size = child.size + typ.align = child.align + return + maxAlign = max(maxAlign, child.align) + sizeAccum = align(sizeAccum, child.align) + child.size + typ.size = align(sizeAccum, maxAlign) + typ.align = int16(maxAlign) + of tyObject: + var headerSize: BiggestInt + var headerAlign: int16 + if typ.sons[0] != nil: + # compute header size + + if conf.cmd == cmdCompileToCpp: + # if the target is C++ the members of this type are written + # into the padding byets at the end of the parent type. At the + # moment it is not supported to calculate that. + headerSize = szUnknownSize + headerAlign = szUncomputedSize + else: + var st = typ.sons[0] + while st.kind in skipPtrs: + st = st.sons[^1] + computeSizeAlign(conf, st) + if st.size == szIllegalRecursion: + typ.size = st.size + typ.align = st.align + return + headerSize = st.size + headerAlign = st.align + elif isObjectWithTypeFieldPredicate(typ): + # this branch is taken for RootObj + headerSize = conf.target.intSize + headerAlign = conf.target.intSize.int16 + else: + headerSize = 0 + headerAlign = 1 + let (offset, align) = + if tfPacked in typ.flags: + (computePackedObjectOffsetsFoldFunction(conf, typ.n, headerSize, false), BiggestInt(1)) + else: + computeObjectOffsetsFoldFunction(conf, typ.n, headerSize) + if offset == szIllegalRecursion: + typ.size = szIllegalRecursion + typ.align = szIllegalRecursion + return + if offset == szUnknownSize or ( + typ.sym != nil and + typ.sym.flags * {sfCompilerProc, sfImportc} == {sfImportc}): + typ.size = szUnknownSize + typ.align = szUnknownSize + return + + # header size is already in size from computeObjectOffsetsFoldFunction + # maxAlign is probably not changed at all from headerAlign + if tfPacked in typ.flags: + typ.size = offset + typ.align = 1 + else: + typ.align = int16(max(align, headerAlign)) + typ.size = align(offset, typ.align) + + of tyInferred: + if typ.len > 1: + computeSizeAlign(conf, typ.lastSon) + typ.size = typ.lastSon.size + typ.align = typ.lastSon.align + + of tyGenericInst, tyDistinct, tyGenericBody, tyAlias, tySink: + computeSizeAlign(conf, typ.lastSon) + typ.size = typ.lastSon.size + typ.align = typ.lastSon.align + + of tyTypeClasses: + if typ.isResolvedUserTypeClass: + computeSizeAlign(conf, typ.lastSon) + typ.size = typ.lastSon.size + typ.align = typ.lastSon.align + else: + typ.size = szUncomputedSize + typ.align = szUncomputedSize + + of tyTypeDesc: + computeSizeAlign(conf, typ.base) + typ.size = typ.base.size + typ.align = typ.base.align + + of tyForward: + # is this really illegal recursion, or maybe just unknown? + typ.size = szIllegalRecursion + typ.align = szIllegalRecursion + + of tyStatic: + if typ.n != nil: + computeSizeAlign(conf, typ.lastSon) + typ.size = typ.lastSon.size + typ.align = typ.lastSon.align + else: + typ.size = szUncomputedSize + typ.align = szUncomputedSize + else: + typ.size = szUncomputedSize + typ.align = szUncomputedSize diff --git a/compiler/suggest.nim b/compiler/suggest.nim index b264415d8..dfa6e5ddb 100644 --- a/compiler/suggest.nim +++ b/compiler/suggest.nim @@ -115,7 +115,7 @@ proc symToSuggest(conf: ConfigRef; s: PSym, isLocal: bool, section: IdeCmd, info result.forth = typeToString(s.typ) else: result.forth = "" - when defined(nimsuggest) and not defined(noDocgen): + when defined(nimsuggest) and not defined(noDocgen) and not defined(leanCompiler): result.doc = s.extractDocComment let infox = if section in {ideUse, ideHighlight, ideOutline}: info else: s.info result.filePath = toFullPath(conf, infox) @@ -153,7 +153,7 @@ proc `$`*(suggest: Suggest): string = result.add(sep) result.add($suggest.column) result.add(sep) - when defined(nimsuggest) and not defined(noDocgen): + when defined(nimsuggest) and not defined(noDocgen) and not defined(leanCompiler): result.add(suggest.doc.escape) if suggest.version == 0: result.add(sep) diff --git a/compiler/syntaxes.nim b/compiler/syntaxes.nim index b5fcee7b1..60246b9bf 100644 --- a/compiler/syntaxes.nim +++ b/compiler/syntaxes.nim @@ -17,10 +17,10 @@ type TFilterKind* = enum filtNone, filtTemplate, filtReplace, filtStrip TParserKind* = enum - skinStandard, skinStrongSpaces, skinEndX + skinStandard, skinEndX const - parserNames*: array[TParserKind, string] = ["standard", "strongspaces", + parserNames*: array[TParserKind, string] = ["standard", "endx"] filterNames*: array[TFilterKind, string] = ["none", "stdtmpl", "replace", "strip"] @@ -34,14 +34,14 @@ template config(p: TParsers): ConfigRef = p.parser.lex.config proc parseAll*(p: var TParsers): PNode = case p.skin - of skinStandard, skinStrongSpaces: + of skinStandard: result = parser.parseAll(p.parser) of skinEndX: internalError(p.config, "parser to implement") proc parseTopLevelStmt*(p: var TParsers): PNode = case p.skin - of skinStandard, skinStrongSpaces: + of skinStandard: result = parser.parseTopLevelStmt(p.parser) of skinEndX: internalError(p.config, "parser to implement") @@ -72,12 +72,16 @@ proc parsePipe(filename: AbsoluteFile, inputStream: PLLStream; cache: IdentCache i = 0 inc linenumber if i+1 < line.len and line[i] == '#' and line[i+1] == '?': - inc(i, 2) - while i < line.len and line[i] in Whitespace: inc(i) - var q: TParser - parser.openParser(q, filename, llStreamOpen(substr(line, i)), cache, config) - result = parser.parseAll(q) - parser.closeParser(q) + when defined(nimpretty2): + # XXX this is a bit hacky, but oh well... + quit "can't nimpretty a source code filter" + else: + inc(i, 2) + while i < line.len and line[i] in Whitespace: inc(i) + var q: TParser + parser.openParser(q, filename, llStreamOpen(substr(line, i)), cache, config) + result = parser.parseAll(q) + parser.closeParser(q) llStreamClose(s) proc getFilter(ident: PIdent): TFilterKind = @@ -149,21 +153,23 @@ proc openParsers*(p: var TParsers, fileIdx: FileIndex, inputstream: PLLStream; else: s = inputstream case p.skin of skinStandard, skinEndX: - parser.openParser(p.parser, fileIdx, s, cache, config, false) - of skinStrongSpaces: - parser.openParser(p.parser, fileIdx, s, cache, config, true) + parser.openParser(p.parser, fileIdx, s, cache, config) proc closeParsers*(p: var TParsers) = parser.closeParser(p.parser) -proc parseFile*(fileIdx: FileIndex; cache: IdentCache; config: ConfigRef): PNode {.procvar.} = - var - p: TParsers - f: File +proc setupParsers*(p: var TParsers; fileIdx: FileIndex; cache: IdentCache; + config: ConfigRef): bool = + var f: File let filename = toFullPathConsiderDirty(config, fileIdx) if not open(f, filename.string): rawMessage(config, errGenerated, "cannot open file: " & filename.string) - return + return false openParsers(p, fileIdx, llStreamOpen(f), cache, config) - result = parseAll(p) - closeParsers(p) + result = true + +proc parseFile*(fileIdx: FileIndex; cache: IdentCache; config: ConfigRef): PNode {.procvar.} = + var p: TParsers + if setupParsers(p, fileIdx, cache, config): + result = parseAll(p) + closeParsers(p) diff --git a/compiler/transf.nim b/compiler/transf.nim index 83e66a069..7b2979dea 100644 --- a/compiler/transf.nim +++ b/compiler/transf.nim @@ -21,9 +21,14 @@ import intsets, strutils, options, ast, astalgo, trees, treetab, msgs, lookups, idents, renderer, types, passes, semfold, magicsys, cgmeth, - lambdalifting, sempass2, lowerings, destroyer, liftlocals, closureiters, + sempass2, lowerings, destroyer, liftlocals, modulegraphs, lineinfos +proc transformBody*(g: ModuleGraph, prc: PSym, cache = true; + noDestructors = false): PNode + +import closureiters, lambdalifting + type PTransNode* = distinct PNode @@ -38,13 +43,13 @@ type # if we encounter the 2nd yield statement next: PTransCon # for stacking - TTransfContext = object of passes.TPassContext + TTransfContext = object of TPassContext module: PSym transCon: PTransCon # top of a TransCon stack inlining: int # > 0 if we are in inlining context (copy vars) nestedProcs: int # > 0 if we are in a nested proc contSyms, breakSyms: seq[PSym] # to transform 'continue' and 'break' - deferDetected, tooEarly, needsDestroyPass: bool + deferDetected, tooEarly, needsDestroyPass, noDestructors: bool graph: ModuleGraph PTransf = ref TTransfContext @@ -62,18 +67,25 @@ proc newTransNode(kind: TNodeKind, n: PNode, var x = newNodeIT(kind, n.info, n.typ) newSeq(x.sons, sons) x.typ = n.typ +# x.flags = n.flags result = x.PTransNode +proc add(a, b: PTransNode) {.inline.} = addSon(PNode(a), PNode(b)) +proc len(a: PTransNode): int {.inline.} = sonsLen(a.PNode) + proc `[]=`(a: PTransNode, i: int, x: PTransNode) {.inline.} = var n = PNode(a) n.sons[i] = PNode(x) +proc `[]=`(a: PTransNode, i: BackwardsIndex, x: PTransNode) {.inline.} = + `[]=`(a, a.len - i.int, x) + proc `[]`(a: PTransNode, i: int): PTransNode {.inline.} = var n = PNode(a) result = n.sons[i].PTransNode -proc add(a, b: PTransNode) {.inline.} = addSon(PNode(a), PNode(b)) -proc len(a: PTransNode): int {.inline.} = result = sonsLen(a.PNode) +proc `[]`(a: PTransNode, i: BackwardsIndex): PTransNode {.inline.} = + `[]`(a, a.len - i.int) proc newTransCon(owner: PSym): PTransCon = assert owner != nil @@ -110,14 +122,16 @@ proc transformSons(c: PTransf, n: PNode): PTransNode = for i in countup(0, sonsLen(n)-1): result[i] = transform(c, n.sons[i]) -proc newAsgnStmt(c: PTransf, le: PNode, ri: PTransNode): PTransNode = - result = newTransNode(nkFastAsgn, PNode(ri).info, 2) +proc newAsgnStmt(c: PTransf, kind: TNodeKind, le: PNode, ri: PTransNode): PTransNode = + result = newTransNode(kind, PNode(ri).info, 2) result[0] = PTransNode(le) result[1] = ri proc transformSymAux(c: PTransf, n: PNode): PNode = let s = n.sym if s.typ != nil and s.typ.callConv == ccClosure: + if s.kind in routineKinds: + discard transformBody(c.graph, s, true, c.noDestructors) if s.kind == skIterator: if c.tooEarly: return n else: return liftIterSym(c.graph, n, getCurrOwner(c)) @@ -190,26 +204,32 @@ proc transformVarSection(c: PTransf, v: PNode): PTransNode = var L = sonsLen(it) var defs = newTransNode(it.kind, it.info, L) for j in countup(0, L-3): - let x = freshVar(c, it.sons[j].sym) - idNodeTablePut(c.transCon.mapping, it.sons[j].sym, x) - defs[j] = x.PTransNode + if it[j].kind == nkSym: + let x = freshVar(c, it.sons[j].sym) + idNodeTablePut(c.transCon.mapping, it.sons[j].sym, x) + defs[j] = x.PTransNode + else: + defs[j] = transform(c, it[j]) assert(it.sons[L-2].kind == nkEmpty) defs[L-2] = newNodeI(nkEmpty, it.info).PTransNode defs[L-1] = transform(c, it.sons[L-1]) result[i] = defs proc transformConstSection(c: PTransf, v: PNode): PTransNode = - result = newTransNode(v) - for i in countup(0, sonsLen(v)-1): - var it = v.sons[i] - if it.kind == nkCommentStmt: - result[i] = PTransNode(it) - else: - if it.kind != nkConstDef: internalError(c.graph.config, it.info, "transformConstSection") - if it.sons[0].kind != nkSym: - internalError(c.graph.config, it.info, "transformConstSection") + result = PTransNode(v) + when false: + result = newTransNode(v) + for i in countup(0, sonsLen(v)-1): + var it = v.sons[i] + if it.kind == nkCommentStmt: + result[i] = PTransNode(it) + else: + if it.kind != nkConstDef: internalError(c.graph.config, it.info, "transformConstSection") + if it.sons[0].kind != nkSym: + debug it.sons[0] + internalError(c.graph.config, it.info, "transformConstSection") - result[i] = PTransNode(it) + result[i] = PTransNode(it) proc hasContinue(n: PNode): bool = case n.kind @@ -223,25 +243,17 @@ proc newLabel(c: PTransf, n: PNode): PSym = result = newSym(skLabel, nil, getCurrOwner(c), n.info) result.name = getIdent(c.graph.cache, genPrefix & $result.id) -proc freshLabels(c: PTransf, n: PNode; symMap: var TIdTable) = - if n.kind in {nkBlockStmt, nkBlockExpr}: - if n.sons[0].kind == nkSym: - let x = newLabel(c, n[0]) - idTablePut(symMap, n[0].sym, x) - n.sons[0].sym = x - if n.kind == nkSym and n.sym.kind == skLabel: - let x = PSym(idTableGet(symMap, n.sym)) - if x != nil: n.sym = x - else: - for i in 0 ..< safeLen(n): freshLabels(c, n.sons[i], symMap) - proc transformBlock(c: PTransf, n: PNode): PTransNode = var labl: PSym - if n.sons[0].kind != nkEmpty: - # already named block? -> Push symbol on the stack: - labl = n.sons[0].sym + if c.inlining > 0: + labl = newLabel(c, n[0]) + idNodeTablePut(c.transCon.mapping, n[0].sym, newSymNode(labl)) else: - labl = newLabel(c, n) + labl = + if n.sons[0].kind != nkEmpty: + n.sons[0].sym # already named block? -> Push symbol on the stack + else: + newLabel(c, n) c.breakSyms.add(labl) result = transformSons(c, n) discard c.breakSyms.pop @@ -282,28 +294,10 @@ proc transformWhile(c: PTransf; n: PNode): PTransNode = discard c.breakSyms.pop proc transformBreak(c: PTransf, n: PNode): PTransNode = - if n.sons[0].kind != nkEmpty or c.inlining > 0: - result = n.PTransNode - when false: - let lablCopy = idNodeTableGet(c.transCon.mapping, n.sons[0].sym) - if lablCopy.isNil: - result = n.PTransNode - else: - result = newTransNode(n.kind, n.info, 1) - result[0] = lablCopy.PTransNode - elif c.breakSyms.len > 0: - # this check can fail for 'nim check' + result = transformSons(c, n) + if n.sons[0].kind == nkEmpty and c.breakSyms.len > 0: let labl = c.breakSyms[c.breakSyms.high] - result = transformSons(c, n) result[0] = newSymNode(labl).PTransNode - else: - result = n.PTransNode - -proc unpackTuple(c: PTransf, n: PNode, father: PTransNode) = - # XXX: BUG: what if `n` is an expression with side-effects? - for i in countup(0, sonsLen(c.transCon.forStmt) - 3): - add(father, newAsgnStmt(c, c.transCon.forStmt.sons[i], - transform(c, newTupleAccess(c.graph, n, i)))) proc introduceNewLocalVars(c: PTransf, n: PNode): PTransNode = case n.kind @@ -328,6 +322,18 @@ proc introduceNewLocalVars(c: PTransf, n: PNode): PTransNode = result[i] = introduceNewLocalVars(c, n.sons[i]) proc transformYield(c: PTransf, n: PNode): PTransNode = + proc asgnTo(lhs: PNode, rhs: PTransNode): PTransNode = + # Choose the right assignment instruction according to the given ``lhs`` + # node since it may not be a nkSym (a stack-allocated skForVar) but a + # nkDotExpr (a heap-allocated slot into the envP block) + case lhs.kind: + of nkSym: + internalAssert c.graph.config, lhs.sym.kind == skForVar + result = newAsgnStmt(c, nkFastAsgn, lhs, rhs) + of nkDotExpr: + result = newAsgnStmt(c, nkAsgn, lhs, rhs) + else: + internalAssert c.graph.config, false result = newTransNode(nkStmtList, n.info, 0) var e = n.sons[0] # c.transCon.forStmt.len == 3 means that there is one for loop variable @@ -340,13 +346,20 @@ proc transformYield(c: PTransf, n: PNode): PTransNode = for i in countup(0, sonsLen(e) - 1): var v = e.sons[i] if v.kind == nkExprColonExpr: v = v.sons[1] - add(result, newAsgnStmt(c, c.transCon.forStmt.sons[i], - transform(c, v))) + let lhs = c.transCon.forStmt.sons[i] + let rhs = transform(c, v) + add(result, asgnTo(lhs, rhs)) else: - unpackTuple(c, e, result) + # Unpack the tuple into the loop variables + # XXX: BUG: what if `n` is an expression with side-effects? + for i in countup(0, sonsLen(c.transCon.forStmt) - 3): + let lhs = c.transCon.forStmt.sons[i] + let rhs = transform(c, newTupleAccess(c.graph, e, i)) + add(result, asgnTo(lhs, rhs)) else: - var x = transform(c, e) - add(result, newAsgnStmt(c, c.transCon.forStmt.sons[0], x)) + let lhs = c.transCon.forStmt.sons[0] + let rhs = transform(c, e) + add(result, asgnTo(lhs, rhs)) inc(c.transCon.yieldStmts) if c.transCon.yieldStmts <= 1: @@ -542,9 +555,10 @@ proc transformFor(c: PTransf, n: PNode): PTransNode = c.breakSyms.add(labl) if call.kind notin nkCallKinds or call.sons[0].kind != nkSym or call.sons[0].typ.callConv == ccClosure: - n.sons[length-1] = transformLoopBody(c, n.sons[length-1]).PNode - n.sons[length-2] = transform(c, n.sons[length-2]).PNode - result[1] = lambdalifting.liftForLoop(c.graph, n, getCurrOwner(c)).PTransNode + result[1] = n.PTransNode + result[1][^1] = transformLoopBody(c, n[^1]) + result[1][^2] = transform(c, n[^2]) + result[1] = lambdalifting.liftForLoop(c.graph, result[1].PNode, getCurrOwner(c)).PTransNode discard c.breakSyms.pop return result @@ -584,7 +598,7 @@ proc transformFor(c: PTransf, n: PNode): PTransNode = # generate a temporary and produce an assignment statement: var temp = newTemp(c, formal.typ, formal.info) addVar(v, temp) - add(stmtList, newAsgnStmt(c, temp, arg.PTransNode)) + add(stmtList, newAsgnStmt(c, nkFastAsgn, temp, arg.PTransNode)) idNodeTablePut(newC.mapping, formal, temp) of paVarAsgn: assert(skipTypes(formal.typ, abstractInst).kind == tyVar) @@ -595,16 +609,11 @@ proc transformFor(c: PTransf, n: PNode): PTransNode = addSonSkipIntLit(typ, formal.typ.sons[0]) var temp = newTemp(c, typ, formal.info) addVar(v, temp) - add(stmtList, newAsgnStmt(c, temp, arg.PTransNode)) + add(stmtList, newAsgnStmt(c, nkFastAsgn, temp, arg.PTransNode)) idNodeTablePut(newC.mapping, formal, temp) - var body = iter.getBody.copyTree + let body = transformBody(c.graph, iter, true, c.noDestructors) pushInfoContext(c.graph.config, n.info) - # XXX optimize this somehow. But the check "c.inlining" is not correct: - var symMap: TIdTable - initIdTable symMap - freshLabels(c, body, symMap) - inc(c.inlining) add(stmtList, transform(c, body)) #findWrongOwners(c, stmtList.pnode) @@ -726,7 +735,7 @@ proc transformExceptBranch(c: PTransf, n: PNode): PTransNode = let actions = newTransNode(nkStmtListExpr, n[1], 2) # Generating `let exc = (excType)(getCurrentException())` # -> getCurrentException() - let excCall = PTransNode(callCodegenProc(c.graph, "getCurrentException", newNodeI(nkEmpty, n.info))) + let excCall = PTransNode(callCodegenProc(c.graph, "getCurrentException")) # -> (excType) let convNode = newTransNode(nkHiddenSubConv, n[1].info, 2) convNode[0] = PTransNode(newNodeI(nkEmpty, n.info)) @@ -1021,27 +1030,37 @@ template liftDefer(c, root) = if c.deferDetected: liftDeferAux(root) -proc transformBody*(g: ModuleGraph; module: PSym, n: PNode, prc: PSym): PNode = - if nfTransf in n.flags or prc.kind in {skTemplate}: - result = n +proc transformBody*(g: ModuleGraph, prc: PSym, cache = true; + noDestructors = false): PNode = + assert prc.kind in routineKinds + + if prc.transformedBody != nil: + result = prc.transformedBody + elif nfTransf in prc.ast[bodyPos].flags or prc.kind in {skTemplate}: + result = prc.ast[bodyPos] else: - var c = openTransf(g, module, "") - result = liftLambdas(g, prc, n, c.tooEarly) - #result = n + prc.transformedBody = newNode(nkEmpty) # protects from recursion + var c = openTransf(g, prc.getModule, "") + c.noDestructors = noDestructors + result = liftLambdas(g, prc, prc.ast[bodyPos], c.tooEarly) result = processTransf(c, result, prc) liftDefer(c, result) - #result = liftLambdas(prc, result) - when useEffectSystem: trackProc(g, prc, result) result = liftLocalsIfRequested(prc, result, g.cache, g.config) - if c.needsDestroyPass: #and newDestructors: + if c.needsDestroyPass and not noDestructors: result = injectDestructorCalls(g, prc, result) if prc.isIterator: result = g.transformClosureIterator(prc, result) incl(result.flags, nfTransf) - #if prc.name.s == "testbody": - # echo renderTree(result) + + let cache = cache or prc.typ.callConv == ccInline + if cache: + # genProc for inline procs will be called multiple times from diffrent modules, + # it is important to transform exactly once to get sym ids and locations right + prc.transformedBody = result + else: + prc.transformedBody = nil proc transformStmt*(g: ModuleGraph; module: PSym, n: PNode): PNode = if nfTransf in n.flags: @@ -1051,14 +1070,12 @@ proc transformStmt*(g: ModuleGraph; module: PSym, n: PNode): PNode = result = processTransf(c, n, module) liftDefer(c, result) #result = liftLambdasForTopLevel(module, result) - when useEffectSystem: trackTopLevelStmt(g, module, result) - #if n.info ?? "temp.nim": - # echo renderTree(result, {renderIds}) if c.needsDestroyPass: result = injectDestructorCalls(g, module, result) incl(result.flags, nfTransf) -proc transformExpr*(g: ModuleGraph; module: PSym, n: PNode): PNode = +proc transformExpr*(g: ModuleGraph; module: PSym, n: PNode; + noDestructors = false): PNode = if nfTransf in n.flags: result = n else: @@ -1067,6 +1084,6 @@ proc transformExpr*(g: ModuleGraph; module: PSym, n: PNode): PNode = liftDefer(c, result) # expressions are not to be injected with destructor calls as that # the list of top level statements needs to be collected before. - if c.needsDestroyPass: + if c.needsDestroyPass and not noDestructors: result = injectDestructorCalls(g, module, result) incl(result.flags, nfTransf) diff --git a/compiler/types.nim b/compiler/types.nim index d0eec35cf..b163ca4e9 100644 --- a/compiler/types.nim +++ b/compiler/types.nim @@ -238,6 +238,7 @@ proc containsObject*(t: PType): bool = result = searchTypeFor(t, isObjectPredicate) proc isObjectWithTypeFieldPredicate(t: PType): bool = + result = t.kind == tyObject and t.sons[0] == nil and not (t.sym != nil and {sfPure, sfInfixCall} * t.sym.flags != {}) and tfFinal notin t.flags @@ -396,7 +397,7 @@ const "float", "float32", "float64", "float128", "uint", "uint8", "uint16", "uint32", "uint64", "opt", "sink", - "lent", "varargs[$1]", "unused", "Error Type", + "lent ", "varargs[$1]", "UncheckedArray[$1]", "Error Type", "BuiltInTypeClass", "UserTypeClass", "UserTypeClassInst", "CompositeTypeClass", "inferred", "and", "or", "not", "any", "static", "TypeFromExpr", "FieldAccessor", @@ -405,7 +406,7 @@ const const preferToResolveSymbols = {preferName, preferTypeName, preferModuleInfo, preferGenericArg} template bindConcreteTypeToUserTypeClass*(tc, concrete: PType) = - tc.sons.safeAdd concrete + tc.sons.add concrete tc.flags.incl tfResolved # TODO: It would be a good idea to kill the special state of a resolved @@ -526,6 +527,8 @@ proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string = else: result = "array[" & typeToString(t.sons[0]) & ", " & typeToString(t.sons[1]) & ']' + of tyUncheckedArray: + result = "UncheckedArray[" & typeToString(t.sons[0]) & ']' of tySequence: result = "seq[" & typeToString(t.sons[0]) & ']' of tyOpt: @@ -638,6 +641,8 @@ proc firstOrd*(conf: ConfigRef; t: PType): BiggestInt = of tyOrdinal: if t.len > 0: result = firstOrd(conf, lastSon(t)) else: internalError(conf, "invalid kind for firstOrd(" & $t.kind & ')') + of tyUncheckedArray: + result = 0 else: internalError(conf, "invalid kind for firstOrd(" & $t.kind & ')') result = 0 @@ -695,6 +700,8 @@ proc lastOrd*(conf: ConfigRef; t: PType; fixedUnsigned = false): BiggestInt = of tyOrdinal: if t.len > 0: result = lastOrd(conf, lastSon(t)) else: internalError(conf, "invalid kind for lastOrd(" & $t.kind & ')') + of tyUncheckedArray: + result = high(BiggestInt) else: internalError(conf, "invalid kind for lastOrd(" & $t.kind & ')') result = 0 @@ -1030,7 +1037,7 @@ proc sameTypeAux(x, y: PType, c: var TSameTypeClosure): bool = if result and {ExactGenericParams, ExactTypeDescValues} * c.flags != {}: result = a.sym.position == b.sym.position of tyGenericInvocation, tyGenericBody, tySequence, - tyOpenArray, tySet, tyRef, tyPtr, tyVar, tyLent, tySink, + tyOpenArray, tySet, tyRef, tyPtr, tyVar, tyLent, tySink, tyUncheckedArray, tyArray, tyProc, tyVarargs, tyOrdinal, tyTypeClasses, tyOpt: cycleCheck() if a.kind == tyUserTypeClass and a.n != nil: return a.n == b.n @@ -1054,7 +1061,7 @@ proc sameTypeAux(x, y: PType, c: var TSameTypeClosure): bool = cycleCheck() result = sameTypeAux(a.lastSon, b.lastSon, c) of tyNone: result = false - of tyUnused, tyOptAsRef: result = false + of tyOptAsRef: result = false proc sameBackendType*(x, y: PType): bool = var c = initSameTypeClosure() @@ -1079,8 +1086,8 @@ proc inheritanceDiff*(a, b: PType): int = # | returns: +x iff `a` is the x'th direct subclass of `b` # | returns: `maxint` iff `a` and `b` are not compatible at all if a == b or a.kind == tyError or b.kind == tyError: return 0 - assert a.kind == tyObject - assert b.kind == tyObject + assert a.kind in {tyObject} + skipPtrs + assert b.kind in {tyObject} + skipPtrs var x = a result = 0 while x != nil: @@ -1136,7 +1143,6 @@ proc typeAllowedNode(marker: var IntSet, n: PNode, kind: TSymKind, flags: TTypeAllowedFlags = {}): PType = if n != nil: result = typeAllowedAux(marker, n.typ, kind, flags) - #if not result: debug(n.typ) if result == nil: case n.kind of nkNone..nkNilLit: @@ -1170,6 +1176,7 @@ proc typeAllowedAux(marker: var IntSet, typ: PType, kind: TSymKind, case t.kind of tyVar, tyLent: if kind in {skProc, skFunc, skConst}: return t + elif t.kind == tyLent and kind != skResult: return t var t2 = skipTypes(t.sons[0], abstractInst-{tyTypeDesc}) case t2.kind of tyVar, tyLent: @@ -1210,7 +1217,7 @@ proc typeAllowedAux(marker: var IntSet, typ: PType, kind: TSymKind, result = nil of tyOrdinal: if kind != skParam: result = t - of tyGenericInst, tyDistinct, tyAlias, tyInferred: + of tyGenericInst, tyDistinct, tyAlias, tyInferred, tyUncheckedArray: result = typeAllowedAux(marker, lastSon(t), kind, flags) of tyRange: if skipTypes(t.sons[0], abstractInst-{tyTypeDesc}).kind notin @@ -1252,7 +1259,7 @@ proc typeAllowedAux(marker: var IntSet, typ: PType, kind: TSymKind, # for now same as error node; we say it's a valid type as it should # prevent cascading errors: result = nil - of tyUnused, tyOptAsRef: result = t + of tyOptAsRef: result = t proc typeAllowed*(t: PType, kind: TSymKind; flags: TTypeAllowedFlags = {}): PType = # returns 'nil' on success and otherwise the part of the type that is @@ -1260,194 +1267,24 @@ proc typeAllowed*(t: PType, kind: TSymKind; flags: TTypeAllowedFlags = {}): PTyp var marker = initIntSet() result = typeAllowedAux(marker, t, kind, flags) -proc align(address, alignment: BiggestInt): BiggestInt = - result = (address + (alignment - 1)) and not (alignment - 1) - -const - szNonConcreteType* = -3 - szIllegalRecursion* = -2 - szUnknownSize* = -1 - -proc computeSizeAux(conf: ConfigRef; typ: PType, a: var BiggestInt): BiggestInt -proc computeRecSizeAux(conf: ConfigRef; n: PNode, a, currOffset: var BiggestInt): BiggestInt = - var maxAlign, maxSize, b, res: BiggestInt - case n.kind - of nkRecCase: - assert(n.sons[0].kind == nkSym) - result = computeRecSizeAux(conf, n.sons[0], a, currOffset) - maxSize = 0 - maxAlign = 1 - for i in countup(1, sonsLen(n) - 1): - case n.sons[i].kind - of nkOfBranch, nkElse: - res = computeRecSizeAux(conf, lastSon(n.sons[i]), b, currOffset) - if res < 0: return res - maxSize = max(maxSize, res) - maxAlign = max(maxAlign, b) - else: - return szIllegalRecursion - currOffset = align(currOffset, maxAlign) + maxSize - result = align(result, maxAlign) + maxSize - a = maxAlign - of nkRecList: - result = 0 - maxAlign = 1 - for i in countup(0, sonsLen(n) - 1): - res = computeRecSizeAux(conf, n.sons[i], b, currOffset) - if res < 0: return res - currOffset = align(currOffset, b) + res - result = align(result, b) + res - if b > maxAlign: maxAlign = b - a = maxAlign - of nkSym: - result = computeSizeAux(conf, n.sym.typ, a) - n.sym.offset = int(currOffset) - else: - a = 1 - result = szNonConcreteType - -proc computeSizeAux(conf: ConfigRef; typ: PType, a: var BiggestInt): BiggestInt = - var res, maxAlign, length, currOffset: BiggestInt - if typ.size == szIllegalRecursion: - # we are already computing the size of the type - # --> illegal recursion in type - return szIllegalRecursion - if typ.size >= 0: - # size already computed - result = typ.size - a = typ.align - return - typ.size = szIllegalRecursion # mark as being computed - case typ.kind - of tyInt, tyUInt: - result = conf.target.intSize - a = result - of tyInt8, tyUInt8, tyBool, tyChar: - result = 1 - a = result - of tyInt16, tyUInt16: - result = 2 - a = result - of tyInt32, tyUInt32, tyFloat32: - result = 4 - a = result - of tyInt64, tyUInt64, tyFloat64: - result = 8 - a = result - of tyFloat128: - result = 16 - a = result - of tyFloat: - result = conf.target.floatSize - a = result - of tyProc: - if typ.callConv == ccClosure: result = 2 * conf.target.ptrSize - else: result = conf.target.ptrSize - a = conf.target.ptrSize - of tyString: - if tfHasAsgn in typ.flags: - result = conf.target.ptrSize * 2 - else: - result = conf.target.ptrSize - of tyNil: - result = conf.target.ptrSize - a = result - of tyCString, tySequence, tyPtr, tyRef, tyVar, tyLent, tyOpenArray: - let base = typ.lastSon - if base == typ or (base.kind == tyTuple and base.size==szIllegalRecursion): - result = szIllegalRecursion - else: - if typ.kind == tySequence and tfHasAsgn in typ.flags: - result = conf.target.ptrSize * 2 - else: - result = conf.target.ptrSize - a = result - of tyArray: - let elemSize = computeSizeAux(conf, typ.sons[1], a) - if elemSize < 0: return elemSize - result = lengthOrd(conf, typ.sons[0]) * elemSize - of tyEnum: - if firstOrd(conf, typ) < 0: - result = 4 # use signed int32 - else: - length = lastOrd(conf, typ) # BUGFIX: use lastOrd! - if length + 1 < `shl`(1, 8): result = 1 - elif length + 1 < `shl`(1, 16): result = 2 - elif length + 1 < `shl`(BiggestInt(1), 32): result = 4 - else: result = 8 - a = result - of tySet: - if typ.sons[0].kind == tyGenericParam: - result = szUnknownSize - else: - length = lengthOrd(conf, typ.sons[0]) - if length <= 8: result = 1 - elif length <= 16: result = 2 - elif length <= 32: result = 4 - elif length <= 64: result = 8 - elif align(length, 8) mod 8 == 0: result = align(length, 8) div 8 - else: result = align(length, 8) div 8 + 1 - a = result - of tyRange: - result = computeSizeAux(conf, typ.sons[0], a) - of tyTuple: - result = 0 - maxAlign = 1 - for i in countup(0, sonsLen(typ) - 1): - res = computeSizeAux(conf, typ.sons[i], a) - if res < 0: return res - maxAlign = max(maxAlign, a) - result = align(result, a) + res - result = align(result, maxAlign) - a = maxAlign - of tyObject: - if typ.sons[0] != nil: - result = computeSizeAux(conf, typ.sons[0].skipTypes(skipPtrs), a) - if result < 0: return - maxAlign = a - elif isObjectWithTypeFieldPredicate(typ): - result = conf.target.intSize - maxAlign = result - else: - result = 0 - maxAlign = 1 - currOffset = result - result = computeRecSizeAux(conf, typ.n, a, currOffset) - if result < 0: return - if a < maxAlign: a = maxAlign - result = align(result, a) - of tyInferred: - if typ.len > 1: - result = computeSizeAux(conf, typ.lastSon, a) - of tyGenericInst, tyDistinct, tyGenericBody, tyAlias, tySink: - result = computeSizeAux(conf, lastSon(typ), a) - of tyTypeClasses: - result = if typ.isResolvedUserTypeClass: computeSizeAux(conf, typ.lastSon, a) - else: szUnknownSize - of tyTypeDesc: - result = computeSizeAux(conf, typ.base, a) - of tyForward: return szIllegalRecursion - of tyStatic: - result = if typ.n != nil: computeSizeAux(conf, typ.lastSon, a) - else: szUnknownSize - else: - #internalError("computeSizeAux()") - result = szUnknownSize - typ.size = result - typ.align = int16(a) +include sizealignoffsetimpl proc computeSize*(conf: ConfigRef; typ: PType): BiggestInt = - var a: BiggestInt = 1 - result = computeSizeAux(conf, typ, a) + computeSizeAlign(conf, typ) + result = typ.size proc getReturnType*(s: PSym): PType = # Obtains the return type of a iterator/proc/macro/template assert s.kind in skProcKinds result = s.typ.sons[0] +proc getAlign*(conf: ConfigRef; typ: PType): BiggestInt = + computeSizeAlign(conf, typ) + result = typ.align + proc getSize*(conf: ConfigRef; typ: PType): BiggestInt = - result = computeSize(conf, typ) - if result < 0: internalError(conf, "getSize: " & $typ.kind) + computeSizeAlign(conf, typ) + result = typ.size proc containsGenericTypeIter(t: PType, closure: RootRef): bool = case t.kind diff --git a/compiler/vm.nim b/compiler/vm.nim index faff81697..7e7ec8903 100644 --- a/compiler/vm.nim +++ b/compiler/vm.nim @@ -24,7 +24,7 @@ import from semfold import leValueConv, ordinalValToString from evaltempl import evalTemplate -from modulegraphs import ModuleGraph +from modulegraphs import ModuleGraph, PPassContext when hasFFI: import evalffi @@ -488,6 +488,22 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = of opcAsgnFloat: decodeB(rkFloat) regs[ra].floatVal = regs[rb].floatVal + of opcAsgnIntFromFloat32: + let rb = instr.regB + ensureKind(rkInt) + regs[ra].intVal = cast[int32](float32(regs[rb].floatVal)) + of opcAsgnIntFromFloat64: + let rb = instr.regB + ensureKind(rkInt) + regs[ra].intVal = cast[int64](regs[rb].floatVal) + of opcAsgnFloat32FromInt: + let rb = instr.regB + ensureKind(rkFloat) + regs[ra].floatVal = cast[float32](int32(regs[rb].intVal)) + of opcAsgnFloat64FromInt: + let rb = instr.regB + ensureKind(rkFloat) + regs[ra].floatVal = cast[float64](int64(regs[rb].intVal)) of opcAsgnComplex: asgnComplex(regs[ra], regs[instr.regB]) of opcAsgnRef: @@ -924,6 +940,17 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = regs[ra].node.flags.incl nfIsRef else: stackTrace(c, tos, pc, "node is not a symbol") + of opcGetImplTransf: + decodeB(rkNode) + let a = regs[rb].node + if a.kind == nkSym: + regs[ra].node = if a.sym.ast.isNil: newNode(nkNilLit) + else: + let ast = a.sym.ast.shallowCopy + for i in 0..<a.sym.ast.len: + ast[i] = a.sym.ast[i] + ast[bodyPos] = transformBody(c.graph, a.sym) + ast.copyTree() of opcSymOwner: decodeB(rkNode) let a = regs[rb].node @@ -933,6 +960,17 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = regs[ra].node.flags.incl nfIsRef else: stackTrace(c, tos, pc, "node is not a symbol") + of opcSymIsInstantiationOf: + decodeBC(rkInt) + let a = regs[rb].node + let b = regs[rc].node + if a.kind == nkSym and a.sym.kind in skProcKinds and + b.kind == nkSym and b.sym.kind in skProcKinds: + regs[ra].intVal = + if sfFromGeneric in a.sym.flags and a.sym.owner == b.sym: 1 + else: 0 + else: + stackTrace(c, tos, pc, "node is not a proc symbol") of opcEcho: let rb = instr.regB if rb == 1: @@ -1201,7 +1239,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = of opcOf: decodeBC(rkInt) let typ = c.types[regs[rc].intVal.int] - regs[ra].intVal = ord(inheritanceDiff(regs[rb].node.typ, typ) >= 0) + regs[ra].intVal = ord(inheritanceDiff(regs[rb].node.typ, typ) <= 0) of opcIs: decodeBC(rkInt) let t1 = regs[rb].node.typ.skipTypes({tyTypeDesc}) @@ -1795,7 +1833,7 @@ proc execProc*(c: PCtx; sym: PSym; args: openArray[PNode]): PNode = "NimScript: attempt to call non-routine: " & sym.name.s) proc evalStmt*(c: PCtx, n: PNode) = - let n = transformExpr(c.graph, c.module, n) + let n = transformExpr(c.graph, c.module, n, noDestructors = true) let start = genStmt(c, n) # execute new instructions; this redundant opcEof check saves us lots # of allocations in 'execute': @@ -1803,7 +1841,7 @@ proc evalStmt*(c: PCtx, n: PNode) = discard execute(c, start) proc evalExpr*(c: PCtx, n: PNode): PNode = - let n = transformExpr(c.graph, c.module, n) + let n = transformExpr(c.graph, c.module, n, noDestructors = true) let start = genExpr(c, n) assert c.code[start].opcode != opcEof result = execute(c, start) @@ -1850,7 +1888,7 @@ const evalPass* = makePass(myOpen, myProcess, myClose) proc evalConstExprAux(module: PSym; g: ModuleGraph; prc: PSym, n: PNode, mode: TEvalMode): PNode = - let n = transformExpr(g, module, n) + let n = transformExpr(g, module, n, noDestructors = true) setupGlobalCtx(module, g) var c = PCtx g.vm let oldMode = c.mode diff --git a/compiler/vmdef.nim b/compiler/vmdef.nim index d642043dc..493078f74 100644 --- a/compiler/vmdef.nim +++ b/compiler/vmdef.nim @@ -35,6 +35,10 @@ type opcAsgnStr, opcAsgnFloat, opcAsgnRef, + opcAsgnIntFromFloat32, # int and float must be of the same byte size + opcAsgnIntFromFloat64, # int and float must be of the same byte size + opcAsgnFloat32FromInt, # int and float must be of the same byte size + opcAsgnFloat64FromInt, # int and float must be of the same byte size opcAsgnComplex, opcNodeToReg, @@ -107,6 +111,7 @@ type opcEqIdent, opcStrToIdent, opcGetImpl, + opcGetImplTransf opcEcho, opcIndCall, # dest = call regStart, n; where regStart = fn, arg1, ... @@ -142,7 +147,8 @@ type opcTypeTrait, opcMarshalLoad, opcMarshalStore, opcToNarrowInt, - opcSymOwner + opcSymOwner, + opcSymIsInstantiationOf TBlock* = object label*: PSym @@ -191,7 +197,7 @@ type VmCallback* = proc (args: VmArgs) {.closure.} PCtx* = ref TCtx - TCtx* = object of passes.TPassContext # code gen context + TCtx* = object of TPassContext # code gen context code*: seq[TInstr] debug*: seq[TLineInfo] # line info for every instruction; kept separate # to not slow down interpretation diff --git a/compiler/vmdeps.nim b/compiler/vmdeps.nim index eb6111165..f160a3096 100644 --- a/compiler/vmdeps.nim +++ b/compiler/vmdeps.nim @@ -82,12 +82,9 @@ proc mapTypeToAstX(cache: IdentCache; t: PType; info: TLineInfo; id template newIdentDefs(s): untyped = newIdentDefs(s, s.typ) - if inst: - if t.sym != nil: # if this node has a symbol - if not allowRecursion: # getTypeInst behavior: return symbol - return atomicType(t.sym) - #else: # getTypeImpl behavior: turn off recursion - # allowRecursion = false + if inst and not allowRecursion and t.sym != nil: + # getTypeInst behavior: return symbol + return atomicType(t.sym) case t.kind of tyNone: result = atomicType("none", mNone) @@ -98,6 +95,10 @@ proc mapTypeToAstX(cache: IdentCache; t: PType; info: TLineInfo; of tyStmt: result = atomicType("stmt", mStmt) of tyVoid: result = atomicType("void", mVoid) of tyEmpty: result = atomicType("empty", mNone) + of tyUncheckedArray: + result = newNodeIT(nkBracketExpr, if t.n.isNil: info else: t.n.info, t) + result.add atomicType("UncheckedArray", mUncheckedArray) + result.add mapTypeToAst(t.sons[0], info) of tyArray: result = newNodeIT(nkBracketExpr, if t.n.isNil: info else: t.n.info, t) result.add atomicType("array", mArray) @@ -156,9 +157,13 @@ proc mapTypeToAstX(cache: IdentCache; t: PType; info: TLineInfo; of tyObject: if inst: result = newNodeX(nkObjectTy) - result.add newNodeI(nkEmpty, info) # pragmas not reconstructed yet - if t.sons[0] == nil: result.add newNodeI(nkEmpty, info) # handle parent object + if t.sym.ast != nil: + result.add t.sym.ast[2][0].copyTree # copy object pragmas else: + result.add newNodeI(nkEmpty, info) + if t.sons[0] == nil: + result.add newNodeI(nkEmpty, info) + else: # handle parent object var nn = newNodeX(nkOfInherit) nn.add mapTypeToAst(t.sons[0], info) result.add nn @@ -237,8 +242,15 @@ proc mapTypeToAstX(cache: IdentCache; t: PType; info: TLineInfo; of tyRange: result = newNodeIT(nkBracketExpr, if t.n.isNil: info else: t.n.info, t) result.add atomicType("range", mRange) - result.add t.n.sons[0].copyTree - result.add t.n.sons[1].copyTree + if inst: + let rng = newNodeX(nkInfix) + rng.add newIdentNode(getIdent(cache, ".."), info) + rng.add t.n.sons[0].copyTree + rng.add t.n.sons[1].copyTree + result.add rng + else: + result.add t.n.sons[0].copyTree + result.add t.n.sons[1].copyTree of tyPointer: result = atomicType("pointer", mPointer) of tyString: result = atomicType("string", mString) of tyCString: result = atomicType("cstring", mCString) @@ -282,7 +294,7 @@ proc mapTypeToAstX(cache: IdentCache; t: PType; info: TLineInfo; result.add atomicType("static", mNone) if t.n != nil: result.add t.n.copyTree - of tyUnused, tyOptAsRef: assert(false, "mapTypeToAstX") + of tyOptAsRef: assert(false, "mapTypeToAstX") proc opMapTypeToAst*(cache: IdentCache; t: PType; info: TLineInfo): PNode = result = mapTypeToAstX(cache, t, info, false, true) diff --git a/compiler/vmgen.nim b/compiler/vmgen.nim index e612d7a2a..1f2a3e6d1 100644 --- a/compiler/vmgen.nim +++ b/compiler/vmgen.nim @@ -29,7 +29,7 @@ import strutils, ast, astalgo, types, msgs, renderer, vmdef, - trees, intsets, magicsys, options, lowerings, lineinfos + trees, intsets, magicsys, options, lowerings, lineinfos, transf import platform from os import splitFile @@ -182,7 +182,10 @@ const HighRegisterPressure = 40 proc bestEffort(c: PCtx): TLineInfo = - (if c.prc == nil: c.module.info else: c.prc.sym.info) + if c.prc != nil and c.prc.sym != nil: + c.prc.sym.info + else: + c.module.info proc getTemp(cc: PCtx; tt: PType): TRegister = let typ = tt.skipTypesOrNil({tyStatic}) @@ -562,6 +565,8 @@ proc genIndex(c: PCtx; n: PNode; arr: PType): TRegister = else: result = c.genx(n) +proc genCheckedObjAccessAux(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags) + proc genAsgnPatch(c: PCtx; le: PNode, value: TRegister) = case le.kind of nkBracketExpr: @@ -570,12 +575,16 @@ proc genAsgnPatch(c: PCtx; le: PNode, value: TRegister) = c.gABC(le, opcWrArr, dest, idx, value) c.freeTemp(dest) c.freeTemp(idx) - of nkDotExpr, nkCheckedFieldExpr: - # XXX field checks here - let left = if le.kind == nkDotExpr: le else: le.sons[0] - let dest = c.genx(left.sons[0], {gfNode}) - let idx = genField(c, left.sons[1]) - c.gABC(left, opcWrObj, dest, idx, value) + of nkCheckedFieldExpr: + var objR: TDest = -1 + genCheckedObjAccessAux(c, le, objR, {gfNode}) + let idx = genField(c, le[0].sons[1]) + c.gABC(le[0], opcWrObj, objR, idx, value) + c.freeTemp(objR) + of nkDotExpr: + let dest = c.genx(le.sons[0], {gfNode}) + let idx = genField(c, le.sons[1]) + c.gABC(le, opcWrObj, dest, idx, value) c.freeTemp(dest) of nkDerefExpr, nkHiddenDeref: let dest = c.genx(le.sons[0], {gfNode}) @@ -763,18 +772,18 @@ proc genCard(c: PCtx; n: PNode; dest: var TDest) = c.gABC(n, opcCard, dest, tmp) c.freeTemp(tmp) -proc genIntCast(c: PCtx; n: PNode; dest: var TDest) = +proc genCastIntFloat(c: PCtx; n: PNode; dest: var TDest) = const allowedIntegers = {tyInt..tyInt64, tyUInt..tyUInt64, tyChar} var signedIntegers = {tyInt8..tyInt32} var unsignedIntegers = {tyUInt8..tyUInt32, tyChar} let src = n.sons[1].typ.skipTypes(abstractRange)#.kind let dst = n.sons[0].typ.skipTypes(abstractRange)#.kind let src_size = getSize(c.config, src) - + let dst_size = getSize(c.config, dst) if c.config.target.intSize < 8: signedIntegers.incl(tyInt) unsignedIntegers.incl(tyUInt) - if src_size == getSize(c.config, dst) and src.kind in allowedIntegers and + if src_size == dst_size and src.kind in allowedIntegers and dst.kind in allowedIntegers: let tmp = c.genx(n.sons[1]) var tmp2 = c.getTemp(n.sons[1].typ) @@ -803,8 +812,28 @@ proc genIntCast(c: PCtx; n: PNode; dest: var TDest) = c.freeTemp(tmp) c.freeTemp(tmp2) c.freeTemp(tmp3) + elif src_size == dst_size and src.kind in allowedIntegers and + dst.kind in {tyFloat, tyFloat32, tyFloat64}: + let tmp = c.genx(n[1]) + if dest < 0: dest = c.getTemp(n[0].typ) + if dst.kind == tyFloat32: + c.gABC(n, opcAsgnFloat32FromInt, dest, tmp) + else: + c.gABC(n, opcAsgnFloat64FromInt, dest, tmp) + c.freeTemp(tmp) + + elif src_size == dst_size and src.kind in {tyFloat, tyFloat32, tyFloat64} and + dst.kind in allowedIntegers: + let tmp = c.genx(n[1]) + if dest < 0: dest = c.getTemp(n[0].typ) + if src.kind == tyFloat32: + c.gABC(n, opcAsgnIntFromFloat32, dest, tmp) + else: + c.gABC(n, opcAsgnIntFromFloat64, dest, tmp) + c.freeTemp(tmp) + else: - globalError(c.config, n.info, "VM is only allowed to 'cast' between integers of same size") + globalError(c.config, n.info, "VM is only allowed to 'cast' between integers and/or floats of same size") proc genVoidABC(c: PCtx, n: PNode, dest: TDest, opcode: TOpcode) = unused(c, n, dest) @@ -1069,13 +1098,11 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) = var tmp = c.genx(n.sons[1]) var idx = c.getTemp(getSysType(c.graph, n.info, tyInt)) var typ = n.sons[2].typ - if m == mOf: typ = typ.skipTypes(abstractPtrs-{tyTypeDesc}) + if m == mOf: typ = typ.skipTypes(abstractPtrs) c.gABx(n, opcLdImmInt, idx, c.genType(typ)) c.gABC(n, if m == mOf: opcOf else: opcIs, dest, tmp, idx) c.freeTemp(tmp) c.freeTemp(idx) - of mSizeOf: - globalError(c.config, n.info, "cannot run in the VM: " & renderTree(n)) of mHigh: if dest < 0: dest = c.getTemp(n.typ) let tmp = c.genx(n.sons[1]) @@ -1088,13 +1115,14 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) = of mEcho: unused(c, n, dest) let n = n[1].skipConv - let x = c.getTempRange(n.len, slotTempUnknown) - internalAssert c.config, n.kind == nkBracket - for i in 0..<n.len: - var r: TRegister = x+i - c.gen(n.sons[i], r) - c.gABC(n, opcEcho, x, n.len) - c.freeTempRange(x, n.len) + if n.kind == nkBracket: + # can happen for nim check, see bug #9609 + let x = c.getTempRange(n.len, slotTempUnknown) + for i in 0..<n.len: + var r: TRegister = x+i + c.gen(n.sons[i], r) + c.gABC(n, opcEcho, x, n.len) + c.freeTempRange(x, n.len) of mAppendStrCh: unused(c, n, dest) genBinaryStmtVar(c, n, opcAddStrCh) @@ -1118,7 +1146,9 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) = of mStaticExec: genBinaryABCD(c, n, dest, opcGorge) of mNLen: genUnaryABI(c, n, dest, opcLenSeq, nimNodeFlag) of mGetImpl: genUnaryABC(c, n, dest, opcGetImpl) + of mGetImplTransf: genUnaryABC(c, n, dest, opcGetImplTransf) of mSymOwner: genUnaryABC(c, n, dest, opcSymOwner) + of mSymIsInstantiationOf: genBinaryABC(c, n, dest, opcSymIsInstantiationOf) of mNChild: genBinaryABC(c, n, dest, opcNChild) of mNSetChild: genVoidABC(c, n, dest, opcNSetChild) of mNDel: genVoidABC(c, n, dest, opcNDel) @@ -1209,8 +1239,7 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) = if dest < 0: dest = c.getTemp(n.typ) c.gABC(n, opcCallSite, dest) of mNGenSym: genBinaryABC(c, n, dest, opcGenSym) - of mMinI, mMaxI, mAbsF64, mMinF64, mMaxF64, mAbsI, - mDotDot: + of mMinI, mMaxI, mAbsF64, mMinF64, mMaxF64, mAbsI, mDotDot: c.genCall(n, dest) of mExpandToAst: if n.len != 2: @@ -1225,8 +1254,11 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) = # produces a value else: globalError(c.config, n.info, "expandToAst requires a call expression") + of mSizeOf, mAlignOf: + globalError(c.config, n.info, "cannot evaluate 'sizeof/alignof' because its type is not defined completely") of mRunnableExamples: discard "just ignore any call to runnableExamples" + of mDestroy: discard "ignore calls to the default destructor" else: # mGCref, mGCunref, globalError(c.config, n.info, "cannot generate code for: " & $m) @@ -1419,13 +1451,19 @@ proc genAsgn(c: PCtx; le, ri: PNode; requiresCopy: bool) = else: c.preventFalseAlias(le, opcWrArr, dest, idx, tmp) c.freeTemp(tmp) - of nkDotExpr, nkCheckedFieldExpr: - # XXX field checks here - let left = if le.kind == nkDotExpr: le else: le.sons[0] - let dest = c.genx(left.sons[0], {gfNode}) - let idx = genField(c, left.sons[1]) + of nkCheckedFieldExpr: + var objR: TDest = -1 + genCheckedObjAccessAux(c, le, objR, {gfNode}) + let idx = genField(c, le[0].sons[1]) + let tmp = c.genx(ri) + c.preventFalseAlias(le[0], opcWrObj, objR, idx, tmp) + c.freeTemp(tmp) + c.freeTemp(objR) + of nkDotExpr: + let dest = c.genx(le.sons[0], {gfNode}) + let idx = genField(c, le.sons[1]) let tmp = c.genx(ri) - c.preventFalseAlias(left, opcWrObj, dest, idx, tmp) + c.preventFalseAlias(le, opcWrObj, dest, idx, tmp) c.freeTemp(tmp) of nkDerefExpr, nkHiddenDeref: let dest = c.genx(le.sons[0], {gfNode}) @@ -1561,9 +1599,59 @@ proc genObjAccess(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags) = c.gABC(n, opcLdObj, dest, a, b) c.freeTemp(a) +proc genCheckedObjAccessAux(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags) = + internalAssert c.config, n.kind == nkCheckedFieldExpr + # nkDotExpr to access the requested field + let accessExpr = n[0] + # nkCall to check if the discriminant is valid + var checkExpr = n[1] + + let negCheck = checkExpr[0].sym.magic == mNot + if negCheck: + checkExpr = checkExpr[^1] + + # Discriminant symbol + let disc = checkExpr[2] + internalAssert c.config, disc.sym.kind == skField + + # Load the object in `dest` + c.gen(accessExpr[0], dest, flags) + # Load the discriminant + var discVal = c.getTemp(disc.typ) + c.gABC(n, opcLdObj, discVal, dest, genField(c, disc)) + # Check if its value is contained in the supplied set + let setLit = c.genx(checkExpr[1]) + var rs = c.getTemp(getSysType(c.graph, n.info, tyBool)) + c.gABC(n, opcContainsSet, rs, setLit, discVal) + c.freeTemp(setLit) + # If the check fails let the user know + let L1 = c.xjmp(n, if negCheck: opcFJmp else: opcTJmp, rs) + c.freeTemp(rs) + # Not ideal but will do for the moment + c.gABC(n, opcQuit) + c.patch(L1) + proc genCheckedObjAccess(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags) = - # XXX implement field checks! - genObjAccess(c, n.sons[0], dest, flags) + var objR: TDest = -1 + genCheckedObjAccessAux(c, n, objR, flags) + + let accessExpr = n[0] + # Field symbol + var field = accessExpr[1] + internalAssert c.config, field.sym.kind == skField + + # Load the content now + if dest < 0: dest = c.getTemp(n.typ) + let fieldPos = genField(c, field) + if needsRegLoad(): + var cc = c.getTemp(accessExpr.typ) + c.gABC(n, opcLdObj, cc, objR, fieldPos) + c.gABC(n, opcNodeToReg, dest, cc) + c.freeTemp(cc) + else: + c.gABC(n, opcLdObj, dest, objR, fieldPos) + + c.freeTemp(objR) proc genArrAccess(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags) = let arrayType = n.sons[0].typ.skipTypes(abstractVarRange-{tyTypeDesc}).kind @@ -1945,7 +2033,7 @@ proc gen(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags = {}) = if allowCast in c.features: genConv(c, n, n.sons[1], dest, opcCast) else: - genIntCast(c, n, dest) + genCastIntFloat(c, n, dest) of nkTypeOfExpr: genTypeLit(c, n.typ, dest) of nkComesFrom: @@ -2067,7 +2155,8 @@ proc genProc(c: PCtx; s: PSym): int = s.ast.sons[miscPos] = x # thanks to the jmp we can add top level statements easily and also nest # procs easily: - let body = s.getBody + let body = transformBody(c.graph, s, cache = not isCompileTimeProc(s), + noDestructors = true) let procStart = c.xjmp(body, opcJmp, 0) var p = PProc(blocks: @[], sym: s) let oldPrc = c.prc diff --git a/compiler/vmops.nim b/compiler/vmops.nim index 83e65279a..75873bfe8 100644 --- a/compiler/vmops.nim +++ b/compiler/vmops.nim @@ -11,7 +11,7 @@ #import vmdeps, vm from math import sqrt, ln, log10, log2, exp, round, arccos, arcsin, arctan, arctan2, cos, cosh, hypot, sinh, sin, tan, tanh, pow, trunc, - floor, ceil, fmod + floor, ceil, `mod` from os import getEnv, existsEnv, dirExists, fileExists, putEnv, walkDir @@ -21,9 +21,6 @@ template mathop(op) {.dirty.} = template osop(op) {.dirty.} = registerCallback(c, "stdlib.os." & astToStr(op), `op Wrapper`) -template ospathsop(op) {.dirty.} = - registerCallback(c, "stdlib.ospaths." & astToStr(op), `op Wrapper`) - template systemop(op) {.dirty.} = registerCallback(c, "stdlib.system." & astToStr(op), `op Wrapper`) @@ -105,12 +102,15 @@ proc registerAdditionalOps*(c: PCtx) = wrap1f_math(trunc) wrap1f_math(floor) wrap1f_math(ceil) - wrap2f_math(fmod) + + proc `mod Wrapper`(a: VmArgs) {.nimcall.} = + setResult(a, `mod`(getFloat(a, 0), getFloat(a, 1))) + registerCallback(c, "stdlib.math.mod", `mod Wrapper`) when defined(nimcore): - wrap2s(getEnv, ospathsop) - wrap1s(existsEnv, ospathsop) - wrap2svoid(putEnv, ospathsop) + wrap2s(getEnv, osop) + wrap1s(existsEnv, osop) + wrap2svoid(putEnv, osop) wrap1s(dirExists, osop) wrap1s(fileExists, osop) wrap2svoid(writeFile, systemop) diff --git a/compiler/wordrecg.nim b/compiler/wordrecg.nim index 91b527e02..41bdc9fcb 100644 --- a/compiler/wordrecg.nim +++ b/compiler/wordrecg.nim @@ -69,13 +69,13 @@ type wImplicitStatic, wGlobal, wCodegenDecl, wUnchecked, wGuard, wLocks, wPartial, wExplain, wLiftLocals, - wAuto, wBool, wCatch, wChar, wClass, + wAuto, wBool, wCatch, wChar, wClass, wCompl wConst_cast, wDefault, wDelete, wDouble, wDynamic_cast, wExplicit, wExtern, wFalse, wFloat, wFriend, wGoto, wInt, wLong, wMutable, wNamespace, wNew, wOperator, - wPrivate, wProtected, wPublic, wRegister, wReinterpret_cast, + wPrivate, wProtected, wPublic, wRegister, wReinterpret_cast, wRestrict, wShort, wSigned, wSizeof, wStatic_cast, wStruct, wSwitch, - wThis, wThrow, wTrue, wTypedef, wTypeid, wTypename, + wThis, wThrow, wTrue, wTypedef, wTypeid, wTypeof, wTypename, wUnion, wPacked, wUnsigned, wVirtual, wVoid, wVolatile, wWchar_t, wAlignas, wAlignof, wConstexpr, wDecltype, wNullptr, wNoexcept, @@ -84,7 +84,7 @@ type wStdIn, wStdOut, wStdErr, wInOut, wByCopy, wByRef, wOneWay, - wBitsize, + wBitsize TSpecialWords* = set[TSpecialWord] @@ -156,14 +156,14 @@ const "asmnostackframe", "implicitstatic", "global", "codegendecl", "unchecked", "guard", "locks", "partial", "explain", "liftlocals", - "auto", "bool", "catch", "char", "class", + "auto", "bool", "catch", "char", "class", "compl", "const_cast", "default", "delete", "double", "dynamic_cast", "explicit", "extern", "false", "float", "friend", "goto", "int", "long", "mutable", "namespace", "new", "operator", - "private", "protected", "public", "register", "reinterpret_cast", + "private", "protected", "public", "register", "reinterpret_cast", "restrict", "short", "signed", "sizeof", "static_cast", "struct", "switch", - "this", "throw", "true", "typedef", "typeid", + "this", "throw", "true", "typedef", "typeid", "typeof", "typename", "union", "packed", "unsigned", "virtual", "void", "volatile", "wchar_t", @@ -173,7 +173,7 @@ const "stdin", "stdout", "stderr", "inout", "bycopy", "byref", "oneway", - "bitsize", + "bitsize" ] proc findStr*(a: openArray[string], s: string): int = |