From 72b89eff82c54e771b6842674a4de3cbf714eae3 Mon Sep 17 00:00:00 2001 From: Saem Ghani Date: Wed, 17 Mar 2021 11:51:50 -0700 Subject: semLambda removed, semProcAux reworked (#17379) * simplified proc-like name ident to symbol code * wip - reworking generic param sem * wip - closer to removing nkEmpty generic params * it's hacky but tests pass * slowly tweaking semProcAux to take on semLambda * fix pragma superset check proto vs current * Set the symbol owner earlier * partial progress reworking proto found bug where default values between forward and impl lead to overload resolution issues. * simplified pragma handling and callConv checks Co-authored-by: Clyybber * partially working * cgexprs issue * It works! * comment clean-up * clean-up asserts, comments, and other bits * add isGenericParams, inline isGeneric queries * seeing if this is sufficiently consistent * can use this approach or continue it in a further PR * commentary about nullary generics and clean-ups * fixed a mistake in PNode isGenericRoutine * Some small cleanups * Small cleanup * for func lambdas ensure we use lambda pragmas * add some basic compileTime func tests * [ci skip] remove comments Co-authored-by: Clyybber Co-authored-by: Clyybber --- compiler/ast.nim | 33 ++- compiler/injectdestructors.nim | 4 +- compiler/liftdestructors.nim | 10 +- compiler/lookups.nim | 1 + compiler/pragmas.nim | 19 +- compiler/sem.nim | 1 - compiler/semcall.nim | 4 +- compiler/semexprs.nim | 2 +- compiler/seminst.nim | 2 +- compiler/semstmts.nim | 340 ++++++++++++++---------------- compiler/semtempl.nim | 7 +- compiler/semtypes.nim | 6 +- compiler/vmgen.nim | 2 +- tests/constr/tnocompiletimefunc.nim | 14 ++ tests/constr/tnocompiletimefunclambda.nim | 6 + tests/converter/tconverter.nim | 11 + tests/generics/tnullary_generics.nim | 26 +++ tests/statictypes/tstatictypes.nim | 6 +- 18 files changed, 284 insertions(+), 210 deletions(-) create mode 100644 tests/constr/tnocompiletimefunc.nim create mode 100644 tests/constr/tnocompiletimefunclambda.nim create mode 100644 tests/converter/tconverter.nim create mode 100644 tests/generics/tnullary_generics.nim diff --git a/compiler/ast.nim b/compiler/ast.nim index 2b1f76e2d..d82420519 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -1040,6 +1040,7 @@ const declarativeDefs* = {nkProcDef, nkFuncDef, nkMethodDef, nkIteratorDef, nkConverterDef} routineDefs* = declarativeDefs + {nkMacroDef, nkTemplateDef} procDefs* = nkLambdaKinds + declarativeDefs + callableDefs* = nkLambdaKinds + routineDefs nkSymChoices* = {nkClosedSymChoice, nkOpenSymChoice} nkStrKinds* = {nkStrLit..nkTripleStrLit} @@ -1760,12 +1761,32 @@ proc getStrOrChar*(a: PNode): string = #internalError(a.info, "getStrOrChar") #result = "" -proc isGenericRoutine*(s: PSym): bool = - case s.kind - of skProcKinds: - result = sfFromGeneric in s.flags or - (s.ast != nil and s.ast[genericParamsPos].kind != nkEmpty) - else: discard +proc isGenericParams*(n: PNode): bool {.inline.} = + ## used to judge whether a node is generic params. + n != nil and n.kind == nkGenericParams + +proc isGenericRoutine*(n: PNode): bool {.inline.} = + n != nil and n.kind in callableDefs and n[genericParamsPos].isGenericParams + +proc isGenericRoutineStrict*(s: PSym): bool {.inline.} = + ## determines if this symbol represents a generic routine + ## the unusual name is so it doesn't collide and eventually replaces + ## `isGenericRoutine` + s.kind in skProcKinds and s.ast.isGenericRoutine + +proc isGenericRoutine*(s: PSym): bool {.inline.} = + ## determines if this symbol represents a generic routine or an instance of + ## one. This should be renamed accordingly and `isGenericRoutineStrict` + ## should take this name instead. + ## + ## Warning/XXX: Unfortunately, it considers a proc kind symbol flagged with + ## sfFromGeneric as a generic routine. Instead this should likely not be the + ## case and the concepts should be teased apart: + ## - generic definition + ## - generic instance + ## - either generic definition or instance + s.kind in skProcKinds and (sfFromGeneric in s.flags or + s.ast.isGenericRoutine) proc skipGenericOwner*(s: PSym): PSym = ## Generic instantiations are owned by their originating generic diff --git a/compiler/injectdestructors.nim b/compiler/injectdestructors.nim index 6010ba43d..d20ed8e26 100644 --- a/compiler/injectdestructors.nim +++ b/compiler/injectdestructors.nim @@ -268,7 +268,7 @@ proc genOp(c: var Con; op: PSym; dest: PNode): PNode = proc genOp(c: var Con; t: PType; kind: TTypeAttachedOp; dest, ri: PNode): PNode = var op = getAttachedOp(c.graph, t, kind) - if op == nil or op.ast[genericParamsPos].kind != nkEmpty: + if op == nil or op.ast.isGenericRoutine: # give up and find the canonical type instead: let h = sighashes.hashType(t, {CoType, CoConsiderOwned, CoDistinct}) let canon = c.graph.canonTypes.getOrDefault(h) @@ -278,7 +278,7 @@ proc genOp(c: var Con; t: PType; kind: TTypeAttachedOp; dest, ri: PNode): PNode #echo dest.typ.id globalError(c.graph.config, dest.info, "internal error: '" & AttachedOpToStr[kind] & "' operator not found for type " & typeToString(t)) - elif op.ast[genericParamsPos].kind != nkEmpty: + elif op.ast.isGenericRoutine: globalError(c.graph.config, dest.info, "internal error: '" & AttachedOpToStr[kind] & "' operator is generic") dbg: diff --git a/compiler/liftdestructors.nim b/compiler/liftdestructors.nim index db31b632a..889c65cc0 100644 --- a/compiler/liftdestructors.nim +++ b/compiler/liftdestructors.nim @@ -354,7 +354,7 @@ proc considerAsgnOrSink(c: var TLiftCtx; t: PType; body, x, y: PNode; # markUsed(c.g.config, c.info, op, c.g.usageSym) onUse(c.info, op) # We also now do generic instantiations in the destructor lifting pass: - if op.ast[genericParamsPos].kind != nkEmpty: + if op.ast.isGenericRoutine: op = instantiateGeneric(c, op, t, t.typeInst) field = op #echo "trying to use ", op.ast @@ -370,7 +370,7 @@ proc addDestructorCall(c: var TLiftCtx; orig: PType; body, x: PNode) = var op = t.destructor if op != nil and sfOverriden in op.flags: - if op.ast[genericParamsPos].kind != nkEmpty: + if op.ast.isGenericRoutine: # patch generic destructor: op = instantiateGeneric(c, op, t, t.typeInst) setAttachedOp(c.g, c.idgen.module, t, attachedDestructor, op) @@ -394,7 +394,7 @@ proc considerUserDefinedOp(c: var TLiftCtx; t: PType; body, x, y: PNode): bool = var op = t.destructor if op != nil and sfOverriden in op.flags: - if op.ast[genericParamsPos].kind != nkEmpty: + if op.ast.isGenericRoutine: # patch generic destructor: op = instantiateGeneric(c, op, t, t.typeInst) setAttachedOp(c.g, c.idgen.module, t, attachedDestructor, op) @@ -1021,7 +1021,7 @@ proc patchBody(g: ModuleGraph; c: PContext; n: PNode; info: TLineInfo; idgen: Id let op = getAttachedOp(g, t, attachedDestructor) if op != nil: - if op.ast[genericParamsPos].kind != nkEmpty: + if op.ast.isGenericRoutine: internalError(g.config, info, "resolved destructor is generic") if op.magic == mDestroy: internalError(g.config, info, "patching mDestroy with mDestroy?") @@ -1031,7 +1031,7 @@ proc patchBody(g: ModuleGraph; c: PContext; n: PNode; info: TLineInfo; idgen: Id proc inst(g: ModuleGraph; c: PContext; t: PType; kind: TTypeAttachedOp; idgen: IdGenerator; info: TLineInfo) = let op = getAttachedOp(g, t, kind) - if op != nil and op.ast != nil and op.ast[genericParamsPos].kind != nkEmpty: + if op != nil and op.ast != nil and op.ast.isGenericRoutine: if t.typeInst != nil: var a: TLiftCtx a.info = info diff --git a/compiler/lookups.nim b/compiler/lookups.nim index 0f6ec151a..e928f707e 100644 --- a/compiler/lookups.nim +++ b/compiler/lookups.nim @@ -287,6 +287,7 @@ proc ensureNoMissingOrUnusedSymbols(c: PContext; scope: PScope) = proc wrongRedefinition*(c: PContext; info: TLineInfo, s: string; conflictsWith: TLineInfo) = + ## Emit a redefinition error if in non-interactive mode if c.config.cmd != cmdInteractive: localError(c.config, info, "redefinition of '$1'; previous declaration here: $2" % diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim index 8fbdd3579..e0a468419 100644 --- a/compiler/pragmas.nim +++ b/compiler/pragmas.nim @@ -60,7 +60,7 @@ const wNoSideEffect, wSideEffect, wNoreturn, wNosinks, wDynlib, wHeader, wThread, wAsmNoStackFrame, wRaises, wLocks, wTags, wRequires, wEnsures, - wGcSafe, wCodegenDecl, wNoInit} + wGcSafe, wCodegenDecl, wNoInit, wCompileTime} typePragmas* = declPragmas + {wMagic, wAcyclic, wPure, wHeader, wCompilerProc, wCore, wFinal, wSize, wShallow, wIncompleteStruct, wCompleteStruct, wByCopy, wByRef, @@ -1245,23 +1245,23 @@ proc mergePragmas(n, pragmas: PNode) = else: for p in pragmas: n[pragmasPos].add p -proc implicitPragmas*(c: PContext, sym: PSym, n: PNode, +proc implicitPragmas*(c: PContext, sym: PSym, info: TLineInfo, validPragmas: TSpecialWords) = if sym != nil and sym.kind != skModule: for it in c.optionStack: let o = it.otherPragmas if not o.isNil and sfFromGeneric notin sym.flags: # see issue #12985 - pushInfoContext(c.config, n.info) + pushInfoContext(c.config, info) var i = 0 while i < o.len: if singlePragma(c, sym, o, i, validPragmas, true, false): - internalError(c.config, n.info, "implicitPragmas") + internalError(c.config, info, "implicitPragmas") inc i popInfoContext(c.config) if sym.kind in routineKinds and sym.ast != nil: mergePragmas(sym.ast, o) if lfExportLib in sym.loc.flags and sfExportc notin sym.flags: - localError(c.config, n.info, ".dynlib requires .exportc") + localError(c.config, info, ".dynlib requires .exportc") var lib = c.optionStack[^1].dynlib if {lfDynamicLib, lfHeader} * sym.loc.flags == {} and sfImportc in sym.flags and lib != nil: @@ -1291,4 +1291,11 @@ proc pragma(c: PContext, sym: PSym, n: PNode, validPragmas: TSpecialWords; isStatement: bool) = if n == nil: return pragmaRec(c, sym, n, validPragmas, isStatement) - implicitPragmas(c, sym, n, validPragmas) + # XXX: in the case of a callable def, this should use its info + implicitPragmas(c, sym, n.info, validPragmas) + +proc pragmaCallable*(c: PContext, sym: PSym, n: PNode, validPragmas: TSpecialWords, + isStatement: bool = false) = + if n == nil: return + if n[pragmasPos].kind != nkEmpty: + pragmaRec(c, sym, n[pragmasPos], validPragmas, isStatement) diff --git a/compiler/sem.nim b/compiler/sem.nim index 70fd7b894..5593f8b02 100644 --- a/compiler/sem.nim +++ b/compiler/sem.nim @@ -36,7 +36,6 @@ proc semProcBody(c: PContext, n: PNode): PNode proc fitNode(c: PContext, formal: PType, arg: PNode; info: TLineInfo): PNode proc changeType(c: PContext; n: PNode, newType: PType, check: bool) -proc semLambda(c: PContext, n: PNode, flags: TExprFlags): PNode proc semTypeNode(c: PContext, n: PNode, prev: PType): PType proc semStmt(c: PContext, n: PNode; flags: TExprFlags): PNode proc semOpAux(c: PContext, n: PNode) diff --git a/compiler/semcall.nim b/compiler/semcall.nim index 0e79dec26..99979c382 100644 --- a/compiler/semcall.nim +++ b/compiler/semcall.nim @@ -445,7 +445,7 @@ proc instGenericConvertersArg*(c: PContext, a: PNode, x: TCandidate) = let a = if a.kind == nkHiddenDeref: a[0] else: a if a.kind == nkHiddenCallConv and a[0].kind == nkSym: let s = a[0].sym - if s.ast != nil and s.ast[genericParamsPos].kind != nkEmpty: + if s.isGenericRoutineStrict: let finalCallee = generateInstance(c, s, x.bindings, a.info) a[0].sym = finalCallee a[0].typ = finalCallee.typ @@ -524,7 +524,7 @@ proc semResolvedCall(c: PContext, x: TCandidate, if result.typ.kind == tyError: incl result.typ.flags, tfCheckedForDestructor return let gp = finalCallee.ast[genericParamsPos] - if gp.kind != nkEmpty: + if gp.isGenericParams: if x.calleeSym.kind notin {skMacro, skTemplate}: if x.calleeSym.magic in {mArrGet, mArrPut}: finalCallee = x.calleeSym diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index a7cc235c0..7bd903657 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -2856,7 +2856,7 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = of nkCurly: result = semSetConstr(c, n) of nkBracket: result = semArrayConstr(c, n, flags) of nkObjConstr: result = semObjConstr(c, n, flags) - of nkLambdaKinds: result = semLambda(c, n, flags) + of nkLambdaKinds: result = semProcAux(c, n, skProc, lambdaPragmas, flags) of nkDerefExpr: result = semDeref(c, n) of nkAddr: result = n diff --git a/compiler/seminst.nim b/compiler/seminst.nim index 62cbdbcc1..68ab2a310 100644 --- a/compiler/seminst.nim +++ b/compiler/seminst.nim @@ -358,7 +358,7 @@ proc generateInstance(c: PContext, fn: PSym, pt: TIdTable, openScope(c) let gp = n[genericParamsPos] - internalAssert c.config, gp.kind != nkEmpty + internalAssert c.config, gp.kind == nkGenericParams n[namePos] = newSymNode(result) pushInfoContext(c.config, info, fn.detailedInfo) var entry = TInstantiation.new diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 1fe540a65..a98103478 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -1523,75 +1523,9 @@ proc semProcAnnotation(c: PContext, prc: PNode; return -proc setGenericParamsMisc(c: PContext; n: PNode): PNode = - let orig = n[genericParamsPos] - # we keep the original params around for better error messages, see - # issue https://github.com/nim-lang/Nim/issues/1713 - result = semGenericParamList(c, orig) - if n[miscPos].kind == nkEmpty: - n[miscPos] = newTree(nkBracket, c.graph.emptyNode, orig) - else: - n[miscPos][1] = orig - n[genericParamsPos] = result - -proc semLambda(c: PContext, n: PNode, flags: TExprFlags): PNode = - # XXX semProcAux should be good enough for this now, we will eventually - # remove semLambda - result = semProcAnnotation(c, n, lambdaPragmas) - if result != nil: return result - result = n - checkSonsLen(n, bodyPos + 1, c.config) - var s: PSym - if n[namePos].kind != nkSym: - s = newSym(skProc, c.cache.idAnon, nextSymId c.idgen, getCurrOwner(c), n.info) - s.ast = n - n[namePos] = newSymNode(s) - else: - s = n[namePos].sym - pushOwner(c, s) - openScope(c) - var gp: PNode - if n[genericParamsPos].kind != nkEmpty: - gp = setGenericParamsMisc(c, n) - else: - gp = newNodeI(nkGenericParams, n.info) - - if n[paramsPos].kind != nkEmpty: - semParamList(c, n[paramsPos], gp, s) - # paramsTypeCheck(c, s.typ) - if gp.len > 0 and n[genericParamsPos].kind == nkEmpty: - # we have a list of implicit type parameters: - n[genericParamsPos] = gp - else: - s.typ = newProcType(c, n.info) - if n[pragmasPos].kind != nkEmpty: - pragma(c, s, n[pragmasPos], lambdaPragmas) - s.options = c.config.options - if n[bodyPos].kind != nkEmpty: - if sfImportc in s.flags: - localError(c.config, n[bodyPos].info, errImplOfXNotAllowed % s.name.s) - #if efDetermineType notin flags: - # XXX not good enough; see tnamedparamanonproc.nim - if gp.len == 0 or (gp.len == 1 and tfRetType in gp[0].typ.flags): - pushProcCon(c, s) - addResult(c, n, s.typ[0], skProc) - s.ast[bodyPos] = hloBody(c, semProcBody(c, n[bodyPos])) - trackProc(c, s, s.ast[bodyPos]) - popProcCon(c) - elif efOperand notin flags: - localError(c.config, n.info, errGenericLambdaNotAllowed) - sideEffectsCheck(c, s) - else: - localError(c.config, n.info, errImplOfXexpected % s.name.s) - closeScope(c) # close scope for parameters - popOwner(c) - result.typ = s.typ - if optOwnedRefs in c.config.globalOptions: - result.typ = makeVarType(c, result.typ, tyOwned) - proc semInferredLambda(c: PContext, pt: TIdTable, n: PNode): PNode {.nosinks.} = + ## used for resolving 'auto' in lambdas based on their callsite var n = n - let original = n[namePos].sym let s = original #copySym(original, false) #incl(s.flags, sfFromGeneric) @@ -1798,11 +1732,6 @@ proc cursorInProc(conf: ConfigRef; n: PNode): bool = if n.info.fileIndex == conf.m.trackPos.fileIndex: result = cursorInProcAux(conf, n) -type - TProcCompilationSteps = enum - stepRegisterSymbol, - stepDetermineType, - proc hasObjParam(s: PSym): bool = var t = s.typ for col in 1.. 0: - if n[genericParamsPos].kind == nkEmpty: - # we have a list of implicit type parameters: - n[genericParamsPos] = gp - # check for semantics again: - # semParamList(c, n[ParamsPos], nil, s) + semParamList(c, n[paramsPos], n[genericParamsPos], s) + # we maybe have implicit type parameters: else: s.typ = newProcType(c, n.info) + + if n[genericParamsPos].safeLen == 0: + # if there exist no explicit or implicit generic parameters, then this is + # at most a nullary generic (generic with no type params). Regardless of + # whether it's a nullary generic or non-generic, we restore the original. + # In the case of `nkEmpty` it's non-generic and an empty `nkGeneircParams` + # is a nullary generic. + # + # Remarks about nullary generics vs non-generics: + # The difference between a non-generic and nullary generic is minor in + # most cases but there are subtle and significant differences as well. + # Due to instantiation that generic procs go through, a static echo in the + # body of a nullary generic will not be executed immediately, as it's + # instantiated and not immediately evaluated. + n[genericParamsPos] = n[miscPos][1] + n[miscPos] = c.graph.emptyNode + if tfTriggersCompileTime in s.typ.flags: incl(s.flags, sfCompileTime) if n[patternPos].kind != nkEmpty: n[patternPos] = semPattern(c, n[patternPos]) @@ -1908,47 +1867,66 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind, elif s.kind == skFunc: incl(s.flags, sfNoSideEffect) incl(s.typ.flags, tfNoSideEffect) - var (proto, comesFromShadowScope) = if isAnon: (nil, false) - else: searchForProc(c, oldScope, s) + + var (proto, comesFromShadowScope) = + if isAnon: (nil, false) + else: searchForProc(c, delcarationScope, s) if proto == nil and sfForward in s.flags: - #This is a definition that shares its sym with its forward declaration (generated by a macro), - #if the symbol is also gensymmed we won't find it with searchForProc, so we check here + ## In cases such as a macro generating a proc with a gensymmed name we + ## know `searchForProc` will not find it and sfForward will be set. In + ## such scenarios the sym is shared between forward declaration and we + ## can treat the `s` as the proto. proto = s - if proto == nil: - if s.kind == skIterator: - if s.typ.callConv != ccClosure: - s.typ.callConv = if isAnon: ccClosure else: ccInline - else: + let hasProto = proto != nil + + # set the default calling conventions + case s.kind + of skIterator: + if s.typ.callConv != ccClosure: + s.typ.callConv = if isAnon: ccClosure else: ccInline + of skMacro, skTemplate: + # we don't bother setting calling conventions for macros and templates + discard + else: + # NB: procs with a forward decl have theirs determined by the forward decl + if not hasProto: + # in this case we're either a forward declaration or we're an impl without + # a forward decl. We set the calling convention or will be set during + # pragma analysis further down. s.typ.callConv = lastOptionEntry(c).defaultCC - # add it here, so that recursive procs are possible: - if sfGenSym in s.flags: - if s.owner == nil: s.owner = getCurrOwner(c) - elif kind in OverloadableSyms: - if not typeIsDetermined: - addInterfaceOverloadableSymAt(c, oldScope, s) - else: - if not typeIsDetermined: - addInterfaceDeclAt(c, oldScope, s) - if n[pragmasPos].kind != nkEmpty: - pragma(c, s, n[pragmasPos], validPragmas) + + if not hasProto and sfGenSym notin s.flags: #and not isAnon: + if s.kind in OverloadableSyms: + addInterfaceOverloadableSymAt(c, delcarationScope, s) else: - implicitPragmas(c, s, n, validPragmas) - styleCheckDef(c.config, s) - onDef(n[namePos].info, s) - else: - if n[pragmasPos].kind != nkEmpty: - pragma(c, s, n[pragmasPos], validPragmas) - # To ease macro generation that produce forwarded .async procs we now - # allow a bit redundancy in the pragma declarations. The rule is - # a prototype's pragma list must be a superset of the current pragma - # list. - # XXX This needs more checks eventually, for example that external - # linking names do agree: - if proto.typ.callConv != s.typ.callConv or proto.typ.flags < s.typ.flags: - localError(c.config, n[pragmasPos].info, errPragmaOnlyInHeaderOfProcX % - ("'" & proto.name.s & "' from " & c.config$proto.info)) - styleCheckDef(c.config, s) + addInterfaceDeclAt(c, delcarationScope, s) + + pragmaCallable(c, s, n, validPragmas) + if not hasProto: + implicitPragmas(c, s, n.info, validPragmas) + + # To ease macro generation that produce forwarded .async procs we now + # allow a bit redundancy in the pragma declarations. The rule is + # a prototype's pragma list must be a superset of the current pragma + # list. + # XXX This needs more checks eventually, for example that external + # linking names do agree: + if hasProto and ( + # calling convention mismatch + tfExplicitCallConv in s.typ.flags and proto.typ.callConv != s.typ.callConv or + # implementation has additional pragmas + proto.typ.flags < s.typ.flags): + localError(c.config, n[pragmasPos].info, errPragmaOnlyInHeaderOfProcX % + ("'" & proto.name.s & "' from " & c.config$proto.info & + " '" & s.name.s & "' from " & c.config$s.info)) + + styleCheckDef(c.config, s) + if hasProto: onDefResolveForward(n[namePos].info, proto) + else: + onDef(n[namePos].info, s) + + if hasProto: if sfForward notin proto.flags and proto.magic == mNone: wrongRedefinition(c, n.info, proto.name.s, proto.info) if not comesFromShadowScope: @@ -1957,7 +1935,7 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind, suggestSym(c.graph, s.info, proto, c.graph.usageSym) closeScope(c) # close scope with wrong parameter symbols openScope(c) # open scope for old (correct) parameter symbols - if proto.ast[genericParamsPos].kind != nkEmpty: + if proto.ast[genericParamsPos].isGenericParams: addGenericParamListToScope(c, proto.ast[genericParamsPos]) addParams(c, proto.typ.n, proto.kind) proto.info = s.info # more accurate line information @@ -1974,31 +1952,42 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind, popOwner(c) pushOwner(c, s) - if sfOverriden in s.flags or s.name.s[0] == '=': semOverride(c, s, n) - if s.name.s[0] in {'.', '('}: - if s.name.s in [".", ".()", ".="] and {Feature.destructor, dotOperators} * c.features == {}: - localError(c.config, n.info, "the overloaded " & s.name.s & - " operator has to be enabled with {.experimental: \"dotOperators\".}") - elif s.name.s == "()" and callOperator notin c.features: - localError(c.config, n.info, "the overloaded " & s.name.s & - " operator has to be enabled with {.experimental: \"callOperator\".}") + if not isAnon: + if sfOverriden in s.flags or s.name.s[0] == '=': semOverride(c, s, n) + elif s.name.s[0] in {'.', '('}: + if s.name.s in [".", ".()", ".="] and {Feature.destructor, dotOperators} * c.features == {}: + localError(c.config, n.info, "the overloaded " & s.name.s & + " operator has to be enabled with {.experimental: \"dotOperators\".}") + elif s.name.s == "()" and callOperator notin c.features: + localError(c.config, n.info, "the overloaded " & s.name.s & + " operator has to be enabled with {.experimental: \"callOperator\".}") if n[bodyPos].kind != nkEmpty and sfError notin s.flags: # for DLL generation we allow sfImportc to have a body, for use in VM if sfBorrow in s.flags: localError(c.config, n[bodyPos].info, errImplOfXNotAllowed % s.name.s) - let usePseudoGenerics = kind in {skMacro, skTemplate} - # Macros and Templates can have generic parameters, but they are - # only used for overload resolution (there is no instantiation of - # the symbol, so we must process the body now) - if not usePseudoGenerics and c.config.ideCmd in {ideSug, ideCon} and not + if c.config.ideCmd in {ideSug, ideCon} and s.kind notin {skMacro, skTemplate} and not cursorInProc(c.config, n[bodyPos]): - discard "speed up nimsuggest" + # speed up nimsuggest if s.kind == skMethod: semMethodPrototype(c, s, n) + elif isAnon: + let gp = n[genericParamsPos] + if gp.kind == nkEmpty or (gp.len == 1 and tfRetType in gp[0].typ.flags): + # absolutely no generics (empty) or a single generic return type are + # allowed, everything else, including a nullary generic is an error. + pushProcCon(c, s) + addResult(c, n, s.typ[0], skProc) + s.ast[bodyPos] = hloBody(c, semProcBody(c, n[bodyPos])) + trackProc(c, s, s.ast[bodyPos]) + popProcCon(c) + elif efOperand notin flags: + localError(c.config, n.info, errGenericLambdaNotAllowed) else: pushProcCon(c, s) - if n[genericParamsPos].kind == nkEmpty or usePseudoGenerics: - if not usePseudoGenerics and s.magic == mNone: paramsTypeCheck(c, s.typ) + if n[genericParamsPos].kind == nkEmpty or s.kind in {skMacro, skTemplate}: + # Macros and Templates can have generic parameters, but they are only + # used for overload resolution (there is no instantiation of the symbol) + if s.kind notin {skMacro, skTemplate} and s.magic == mNone: paramsTypeCheck(c, s.typ) maybeAddResult(c, s, n) # semantic checking also needed with importc in case used in VM @@ -2006,9 +1995,8 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind, # unfortunately we cannot skip this step when in 'system.compiles' # context as it may even be evaluated in 'system.compiles': trackProc(c, s, s.ast[bodyPos]) - if s.kind == skMethod: semMethodPrototype(c, s, n) else: - if (s.typ[0] != nil and kind != skIterator) or kind == skMacro: + if (s.typ[0] != nil and s.kind != skIterator): addDecl(c, newSym(skUnknown, getIdent(c.cache, "result"), nextSymId c.idgen, nil, n.info)) openScope(c) @@ -2016,20 +2004,17 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind, closeScope(c) if s.magic == mNone: fixupInstantiatedSymbols(c, s) - if s.kind == skMethod: semMethodPrototype(c, s, n) - if sfImportc in s.flags: - # don't ignore the body in case used in VM - # n[bodyPos] = c.graph.emptyNode - discard + if s.kind == skMethod: semMethodPrototype(c, s, n) popProcCon(c) else: - if s.kind in {skProc, skFunc} and s.typ[0] != nil and s.typ[0].kind == tyUntyped: - # `auto` is represented as `tyUntyped` at this point in compilation. - localError(c.config, n[paramsPos][0].info, "return type 'auto' cannot be used in forward declarations") - if s.kind == skMethod: semMethodPrototype(c, s, n) - if proto != nil: localError(c.config, n.info, errImplOfXexpected % proto.name.s) + if hasProto: localError(c.config, n.info, errImplOfXexpected % proto.name.s) if {sfImportc, sfBorrow, sfError} * s.flags == {} and s.magic == mNone: + # this is a forward declaration and we're building the prototype + if s.kind in {skProc, skFunc} and s.typ[0] != nil and s.typ[0].kind == tyUntyped: + # `auto` is represented as `tyUntyped` at this point in compilation. + localError(c.config, n[paramsPos][0].info, "return type 'auto' cannot be used in forward declarations") + incl(s.flags, sfForward) incl(s.flags, sfWasForwarded) elif sfBorrow in s.flags: semBorrow(c, n, s) @@ -2044,15 +2029,14 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind, result.typ = s.typ if optOwnedRefs in c.config.globalOptions: result.typ = makeVarType(c, result.typ, tyOwned) - if isTopLevel(c) and s.kind != skIterator and - s.typ.callConv == ccClosure: + elif isTopLevel(c) and s.kind != skIterator and s.typ.callConv == ccClosure: localError(c.config, s.info, "'.closure' calling convention for top level routines is invalid") proc determineType(c: PContext, s: PSym) = if s.typ != nil: return #if s.magic != mNone: return #if s.ast.isNil: return - discard semProcAux(c, s.ast, s.kind, {}, stepDetermineType) + discard semProcAux(c, s.ast, s.kind, {}) proc semIterator(c: PContext, n: PNode): PNode = # gensym'ed iterator? @@ -2086,7 +2070,9 @@ proc semProc(c: PContext, n: PNode): PNode = result = semProcAux(c, n, skProc, procPragmas) proc semFunc(c: PContext, n: PNode): PNode = - result = semProcAux(c, n, skFunc, procPragmas) + let validPragmas = if n[namePos].kind != nkEmpty: procPragmas + else: lambdaPragmas + result = semProcAux(c, n, skFunc, validPragmas) proc semMethod(c: PContext, n: PNode): PNode = if not isTopLevel(c): localError(c.config, n.info, errXOnlyAtModuleScope % "method") diff --git a/compiler/semtempl.nim b/compiler/semtempl.nim index 2f87fb8f2..568873269 100644 --- a/compiler/semtempl.nim +++ b/compiler/semtempl.nim @@ -636,10 +636,9 @@ proc semTemplateDef(c: PContext, n: PNode): PNode = param.flags.incl sfTemplateParam param.flags.excl sfGenSym if param.typ.kind != tyUntyped: allUntyped = false - if gp.len > 0: - if n[genericParamsPos].kind == nkEmpty: - # we have a list of implicit type parameters: - n[genericParamsPos] = gp + if gp.len > 0 and n[genericParamsPos].kind == nkEmpty: + # we have a list of implicit type parameters: + n[genericParamsPos] = gp else: s.typ = newTypeS(tyProc, c) # XXX why do we need tyTyped as a return type again? diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index b8dd19c51..dcab9a884 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -1233,7 +1233,7 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode, if hasDefault: def = a[^1] block determineType: - if genericParams != nil and genericParams.len > 0: + if genericParams.isGenericParams: def = semGenericStmt(c, def) if hasUnresolvedArgs(c, def): def.typ = makeTypeFromExpr(c, def.copyTree) @@ -1361,7 +1361,7 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode, result.flags.excl tfHasMeta result.n.typ = r - if genericParams != nil and genericParams.len > 0: + if genericParams.isGenericParams: for n in genericParams: if {sfUsed, sfAnon} * n.sym.flags == {}: result.flags.incl tfUnresolved @@ -1666,7 +1666,7 @@ proc semProcTypeWithScope(c: PContext, n: PNode, # we construct a fake 'nkProcDef' for the 'mergePragmas' inside 'implicitPragmas'... s.ast = newTree(nkProcDef, newNodeI(nkEmpty, n.info), newNodeI(nkEmpty, n.info), newNodeI(nkEmpty, n.info), newNodeI(nkEmpty, n.info), newNodeI(nkEmpty, n.info)) - implicitPragmas(c, s, n, {wTags, wRaises}) + implicitPragmas(c, s, n.info, {wTags, wRaises}) when useEffectSystem: setEffectsForProcType(c.graph, result, s.ast[pragmasPos]) closeScope(c) diff --git a/compiler/vmgen.nim b/compiler/vmgen.nim index 392fe9737..7d7382d18 100644 --- a/compiler/vmgen.nim +++ b/compiler/vmgen.nim @@ -2265,7 +2265,7 @@ proc genProc(c: PCtx; s: PSym): int = genParams(c, s.typ.n) # allocate additional space for any generically bound parameters - if s.kind == skMacro and s.ast[genericParamsPos].kind != nkEmpty: + if s.kind == skMacro and s.isGenericRoutineStrict: genGenericParams(c, s.ast[genericParamsPos]) if tfCapturesEnv in s.typ.flags: diff --git a/tests/constr/tnocompiletimefunc.nim b/tests/constr/tnocompiletimefunc.nim new file mode 100644 index 000000000..a95648c0f --- /dev/null +++ b/tests/constr/tnocompiletimefunc.nim @@ -0,0 +1,14 @@ +discard """ + errormsg: "request to generate code for .compileTime proc: foo" +""" + +# ensure compileTime funcs can't be called from runtime + +func foo(a: int): int {.compileTime.} = + a * a + +proc doAThing(): int = + for i in 0..2: + result += foo(i) + +echo doAThing() diff --git a/tests/constr/tnocompiletimefunclambda.nim b/tests/constr/tnocompiletimefunclambda.nim new file mode 100644 index 000000000..d134eea40 --- /dev/null +++ b/tests/constr/tnocompiletimefunclambda.nim @@ -0,0 +1,6 @@ +discard """ + errormsg: "request to generate code for .compileTime proc: :anonymous" +""" + +let a = func(a: varargs[int]) {.compileTime, closure.} = + discard a[0] \ No newline at end of file diff --git a/tests/converter/tconverter.nim b/tests/converter/tconverter.nim new file mode 100644 index 000000000..0bf067c55 --- /dev/null +++ b/tests/converter/tconverter.nim @@ -0,0 +1,11 @@ +discard """ + output: '''fooo fooo''' +""" + +converter intToString[T](i: T): string = "fooo" + +let + foo: string = 1 + bar: string = intToString(2) + +echo foo, " ", bar \ No newline at end of file diff --git a/tests/generics/tnullary_generics.nim b/tests/generics/tnullary_generics.nim new file mode 100644 index 000000000..c79558ee3 --- /dev/null +++ b/tests/generics/tnullary_generics.nim @@ -0,0 +1,26 @@ +discard """ + nimout: ''' +hah +hey +hey +hah +''' +""" + +# non-generic +proc foo(s: string) = + static: echo "hah" + echo s + +static: echo "hey" + +foo("hoo") + +# nullary generic +proc bar[](s: string) = + static: echo "hah" + echo s + +static: echo "hey" + +bar("hoo") diff --git a/tests/statictypes/tstatictypes.nim b/tests/statictypes/tstatictypes.nim index 41c060138..a76276d2c 100644 --- a/tests/statictypes/tstatictypes.nim +++ b/tests/statictypes/tstatictypes.nim @@ -2,6 +2,8 @@ discard """ nimout: ''' staticAlialProc instantiated with 358 staticAlialProc instantiated with 368 +0: Foo +1: Bar ''' output: ''' 16 @@ -289,8 +291,10 @@ macro fooParam(x: static array[2, string]): untyped = echo i, ": ", val macro barParam(x: static Table[int, string]): untyped = - for i, val in x: + let barParamInsides = proc(i: int, val: string): NimNode = echo i, ": ", val + for i, val in x: + discard barParamInsides(i, val) fooM() barM() -- cgit 1.4.1-2-gfad0