diff options
author | metagn <metagngn@gmail.com> | 2023-04-22 10:11:56 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-04-22 09:11:56 +0200 |
commit | 63d29ddd6980ee9f89673c454c15da52e2984283 (patch) | |
tree | 86af58c42f415e6e7dc4d1c10de9d6f633127477 | |
parent | c136ebf1ed0812f019895acc5aeeda8fde75ed00 (diff) | |
download | Nim-63d29ddd6980ee9f89673c454c15da52e2984283.tar.gz |
alias syntax fixes, improvements and tests (#21671)
* alias syntax fixes, improvements and tests * even better, cannot use alias syntax with generics * more type tests, improve comment * fix again * consistent error message + make t5167_5 work * more comments, remove {.noalias.}
-rw-r--r-- | compiler/ast.nim | 11 | ||||
-rw-r--r-- | compiler/semexprs.nim | 20 | ||||
-rw-r--r-- | compiler/semgnrc.nim | 47 | ||||
-rw-r--r-- | compiler/semstmts.nim | 15 | ||||
-rw-r--r-- | compiler/semtempl.nim | 8 | ||||
-rw-r--r-- | compiler/semtypes.nim | 129 | ||||
-rw-r--r-- | doc/manual_experimental.md | 17 | ||||
-rw-r--r-- | tests/errmsgs/t5167_5.nim | 20 | ||||
-rw-r--r-- | tests/template/t13515.nim | 16 | ||||
-rw-r--r-- | tests/template/taliassyntax.nim | 63 | ||||
-rw-r--r-- | tests/template/taliassyntaxerrors.nim | 28 |
11 files changed, 226 insertions, 148 deletions
diff --git a/compiler/ast.nim b/compiler/ast.nim index b5306c423..8aa4ca6b1 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -303,6 +303,8 @@ type sfUsedInFinallyOrExcept # symbol is used inside an 'except' or 'finally' sfSingleUsedTemp # For temporaries that we know will only be used once sfNoalias # 'noalias' annotation, means C's 'restrict' + # for templates and macros, means cannot be called + # as a lone symbol (cannot use alias syntax) sfEffectsDelayed # an 'effectsDelayed' parameter sfGeneratedType # A anonymous generic type that is generated by the compiler for # objects that do not have generic parameters in case one of the @@ -1920,15 +1922,6 @@ proc isRunnableExamples*(n: PNode): bool = 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 - # symbol instead? - for i in 1..<s.typ.len: - if s.typ.n[i].sym.ast != nil: - return i - 1 - return s.typ.len - 1 - proc hasPattern*(s: PSym): bool {.inline.} = result = isRoutine(s) and s.ast[patternPos].kind != nkEmpty diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index e5d5498a8..46d83c0e7 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -57,7 +57,14 @@ proc semOperand(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = # XXX tyGenericInst here? if result.typ.kind == tyProc and hasUnresolvedParams(result, {efOperand}): #and tfUnresolved in result.typ.flags: - localError(c.config, n.info, errProcHasNoConcreteType % n.renderTree) + let owner = result.typ.owner + let err = + # consistent error message with evaltempl/semMacroExpr + if owner != nil and owner.kind in {skTemplate, skMacro}: + errMissingGenericParamsForTemplate % n.renderTree + else: + errProcHasNoConcreteType % n.renderTree + localError(c.config, n.info, err) if result.typ.kind in {tyVar, tyLent}: result = newDeref(result) elif {efWantStmt, efAllowStmt} * flags != {}: result.typ = newTypeS(tyVoid, c) @@ -1259,6 +1266,7 @@ proc readTypeParameter(c: PContext, typ: PType, return nil proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode = + assert n.kind in nkIdentKinds + {nkDotExpr} let s = getGenSym(c, sym) case s.kind of skConst: @@ -1293,9 +1301,8 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode = else: result = newSymNode(s, n.info) of skMacro, skTemplate: - if efNoEvaluateGeneric in flags and s.ast[genericParamsPos].len > 0 or - (n.kind notin nkCallKinds and s.requiredParams > 0) or - sfCustomPragma in sym.flags: + # check if we cannot use alias syntax (no required args or generic params) + if sfNoalias in s.flags: let info = getCallLineInfo(n) markUsed(c, info, s) onUse(info, s) @@ -1588,9 +1595,8 @@ proc semSubscript(c: PContext, n: PNode, flags: TExprFlags): PNode = result.add(x[0]) return checkMinSonsLen(n, 2, c.config) - # make sure we don't evaluate generic macros/templates - n[0] = semExprWithType(c, n[0], - {efNoEvaluateGeneric}) + # signal that generic parameters may be applied after + n[0] = semExprWithType(c, n[0], {efNoEvaluateGeneric}) var arr = skipTypes(n[0].typ, {tyGenericInst, tyUserTypeClassInst, tyOwned, tyVar, tyLent, tyPtr, tyRef, tyAlias, tySink}) if arr.kind == tyStatic: diff --git a/compiler/semgnrc.nim b/compiler/semgnrc.nim index fa37af850..695f8a01d 100644 --- a/compiler/semgnrc.nim +++ b/compiler/semgnrc.nim @@ -50,13 +50,6 @@ proc semGenericStmtScope(c: PContext, n: PNode, result = semGenericStmt(c, n, flags, ctx) closeScope(c) -template macroToExpand(s): untyped = - s.kind in {skMacro, skTemplate} and (s.typ.len == 1 or sfAllUntyped in s.flags) - -template macroToExpandSym(s): untyped = - sfCustomPragma notin s.flags and s.kind in {skMacro, skTemplate} and - (s.typ.len == 1) and not fromDotExpr - template isMixedIn(sym): bool = let s = sym s.name.id in ctx.toMixin or (withinConcept in flags and @@ -74,19 +67,14 @@ proc semGenericStmtSymbol(c: PContext, n: PNode, s: PSym, result = n of skProc, skFunc, skMethod, skIterator, skConverter, skModule: result = symChoice(c, n, s, scOpen) - of skTemplate: - if macroToExpandSym(s): + of skTemplate, skMacro: + # alias syntax, see semSym for skTemplate, skMacro + if sfNoalias notin s.flags and not fromDotExpr: onUse(n.info, s) - result = semTemplateExpr(c, n, s, {efNoSemCheck}) - c.friendModules.add(s.owner.getModule) - result = semGenericStmt(c, result, {}, ctx) - discard c.friendModules.pop() - else: - result = symChoice(c, n, s, scOpen) - of skMacro: - if macroToExpandSym(s): - onUse(n.info, s) - result = semMacroExpr(c, n, n, s, {efNoSemCheck}) + case s.kind + of skTemplate: result = semTemplateExpr(c, n, s, {efNoSemCheck}) + of skMacro: result = semMacroExpr(c, n, n, s, {efNoSemCheck}) + else: discard # unreachable c.friendModules.add(s.owner.getModule) result = semGenericStmt(c, result, {}, ctx) discard c.friendModules.pop() @@ -245,21 +233,14 @@ proc semGenericStmt(c: PContext, n: PNode, else: scOpen let sc = symChoice(c, fn, s, whichChoice) case s.kind - of skMacro: - if macroToExpand(s) and sc.safeLen <= 1: - onUse(fn.info, s) - result = semMacroExpr(c, n, n, s, {efNoSemCheck}) - c.friendModules.add(s.owner.getModule) - result = semGenericStmt(c, result, flags, ctx) - discard c.friendModules.pop() - else: - n[0] = sc - result = n - mixinContext = true - of skTemplate: - if macroToExpand(s) and sc.safeLen <= 1: + of skMacro, skTemplate: + # unambiguous macros/templates are expanded if all params are untyped + if sfAllUntyped in s.flags and sc.safeLen <= 1: onUse(fn.info, s) - result = semTemplateExpr(c, n, s, {efNoSemCheck}) + case s.kind + of skMacro: result = semMacroExpr(c, n, n, s, {efNoSemCheck}) + of skTemplate: result = semTemplateExpr(c, n, s, {efNoSemCheck}) + else: discard # unreachable c.friendModules.add(s.owner.getModule) result = semGenericStmt(c, result, flags, ctx) discard c.friendModules.pop() diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 3701e09f5..20fb12d71 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -682,7 +682,14 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode = localError(c.config, def.info, errCannotInferTypeOfTheLiteral % typ.kind.toHumanStr) elif typ.kind == tyProc and def.kind == nkSym and isGenericRoutine(def.sym.ast): # tfUnresolved in typ.flags: - localError(c.config, def.info, errProcHasNoConcreteType % def.renderTree) + let owner = typ.owner + let err = + # consistent error message with evaltempl/semMacroExpr + if owner != nil and owner.kind in {skTemplate, skMacro}: + errMissingGenericParamsForTemplate % def.renderTree + else: + errProcHasNoConcreteType % def.renderTree + localError(c.config, def.info, err) when false: # XXX This typing rule is neither documented nor complete enough to # justify it. Instead use the newer 'unowned x' until we figured out @@ -2328,10 +2335,16 @@ proc semMacroDef(c: PContext, n: PNode): PNode = var s = result[namePos].sym var t = s.typ var allUntyped = true + var requiresParams = false for i in 1..<t.n.len: let param = t.n[i].sym if param.typ.kind != tyUntyped: allUntyped = false + # no default value, parameters required in call + if param.ast == nil: requiresParams = true if allUntyped: incl(s.flags, sfAllUntyped) + if requiresParams or n[genericParamsPos].kind != nkEmpty: + # macro cannot be called with alias syntax + incl(s.flags, sfNoalias) if n[bodyPos].kind == nkEmpty: localError(c.config, n.info, errImplOfXexpected % s.name.s) diff --git a/compiler/semtempl.nim b/compiler/semtempl.nim index 3b2f2dd9e..6c01be7ea 100644 --- a/compiler/semtempl.nim +++ b/compiler/semtempl.nim @@ -635,6 +635,7 @@ proc semTemplateDef(c: PContext, n: PNode): PNode = setGenericParamsMisc(c, n) # process parameters: var allUntyped = true + var requiresParams = false if n[paramsPos].kind != nkEmpty: semParamList(c, n[paramsPos], n[genericParamsPos], s) # a template's parameters are not gensym'ed even if that was originally the @@ -646,6 +647,8 @@ proc semTemplateDef(c: PContext, n: PNode): PNode = param.flags.incl sfTemplateParam param.flags.excl sfGenSym if param.typ.kind != tyUntyped: allUntyped = false + # no default value, parameters required in call + if param.ast == nil: requiresParams = true else: s.typ = newTypeS(tyProc, c) # XXX why do we need tyTyped as a return type again? @@ -657,6 +660,11 @@ proc semTemplateDef(c: PContext, n: PNode): PNode = n[genericParamsPos] = n[miscPos][1] n[miscPos] = c.graph.emptyNode if allUntyped: incl(s.flags, sfAllUntyped) + if requiresParams or + n[bodyPos].kind == nkEmpty or + n[genericParamsPos].kind != nkEmpty: + # template cannot be called with alias syntax + incl(s.flags, sfNoalias) if n[patternPos].kind != nkEmpty: n[patternPos] = semPattern(c, n[patternPos], s) diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index cd742c90a..8f34da72d 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -416,68 +416,6 @@ proc semOrdinal(c: PContext, n: PNode, prev: PType): PType = localError(c.config, n.info, errXExpectsOneTypeParam % "ordinal") result = newOrPrevType(tyError, prev, c) -proc semTypeIdent(c: PContext, n: PNode): PSym = - if n.kind == nkSym: - result = getGenSym(c, n.sym) - else: - result = pickSym(c, n, {skType, skGenericParam, skParam}) - if result.isNil: - result = qualifiedLookUp(c, n, {checkAmbiguity, checkUndeclared}) - if result != nil: - markUsed(c, 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? - # it's not bound when it's used multiple times in the - # proc signature for example - if c.inGenericInst > 0: - let bound = result.typ[0].sym - if bound != nil: return bound - return result - if result.typ.sym == nil: - localError(c.config, n.info, errTypeExpected) - return errorSym(c, n) - result = result.typ.sym.copySym(nextSymId c.idgen) - result.typ = exactReplica(result.typ) - result.typ.flags.incl tfUnresolved - - if result.kind == skGenericParam: - if result.typ.kind == tyGenericParam and result.typ.len == 0 and - tfWildcard in result.typ.flags: - # collapse the wild-card param to a type - result.transitionGenericParamToType() - result.typ.flags.excl tfWildcard - return - else: - localError(c.config, n.info, errTypeExpected) - return errorSym(c, n) - if result.kind != skType and result.magic notin {mStatic, mType, mTypeOf}: - # this implements the wanted ``var v: V, x: V`` feature ... - var ov: TOverloadIter - var amb = initOverloadIter(ov, c, n) - while amb != nil and amb.kind != skType: - amb = nextOverloadIter(ov, c, n) - if amb != nil: result = amb - else: - if result.kind != skError: localError(c.config, n.info, errTypeExpected) - return errorSym(c, n) - if result.typ.kind != tyGenericParam: - # XXX get rid of this hack! - var oldInfo = n.info - when defined(useNodeIds): - let oldId = n.id - reset(n[]) - when defined(useNodeIds): - n.id = oldId - n.transitionNoneToSym() - n.sym = result - n.info = oldInfo - n.typ = result.typ - else: - localError(c.config, n.info, "identifier expected") - result = errorSym(c, n) - proc semAnonTuple(c: PContext, n: PNode, prev: PType): PType = if n.len == 0: localError(c.config, n.info, errTypeExpected) @@ -1801,6 +1739,73 @@ proc semTypeOf2(c: PContext; n: PNode; prev: PType): PType = fixupTypeOf(c, prev, t) result = t.typ +proc semTypeIdent(c: PContext, n: PNode): PSym = + if n.kind == nkSym: + result = getGenSym(c, n.sym) + else: + result = pickSym(c, n, {skType, skGenericParam, skParam}) + if result.isNil: + result = qualifiedLookUp(c, n, {checkAmbiguity, checkUndeclared}) + if result != nil: + markUsed(c, n.info, result) + onUse(n.info, result) + + # alias syntax, see semSym for skTemplate, skMacro + if result.kind in {skTemplate, skMacro} and sfNoalias notin result.flags: + let t = semTypeExpr(c, n, nil) + result = symFromType(c, t, n.info) + + if result.kind == skParam and result.typ.kind == tyTypeDesc: + # This is a typedesc param. is it already bound? + # it's not bound when it's used multiple times in the + # proc signature for example + if c.inGenericInst > 0: + let bound = result.typ[0].sym + if bound != nil: return bound + return result + if result.typ.sym == nil: + localError(c.config, n.info, errTypeExpected) + return errorSym(c, n) + result = result.typ.sym.copySym(nextSymId c.idgen) + result.typ = exactReplica(result.typ) + result.typ.flags.incl tfUnresolved + + if result.kind == skGenericParam: + if result.typ.kind == tyGenericParam and result.typ.len == 0 and + tfWildcard in result.typ.flags: + # collapse the wild-card param to a type + result.transitionGenericParamToType() + result.typ.flags.excl tfWildcard + return + else: + localError(c.config, n.info, errTypeExpected) + return errorSym(c, n) + if result.kind != skType and result.magic notin {mStatic, mType, mTypeOf}: + # this implements the wanted ``var v: V, x: V`` feature ... + var ov: TOverloadIter + var amb = initOverloadIter(ov, c, n) + while amb != nil and amb.kind != skType: + amb = nextOverloadIter(ov, c, n) + if amb != nil: result = amb + else: + if result.kind != skError: localError(c.config, n.info, errTypeExpected) + return errorSym(c, n) + if result.typ.kind != tyGenericParam: + # XXX get rid of this hack! + var oldInfo = n.info + when defined(useNodeIds): + let oldId = n.id + reset(n[]) + when defined(useNodeIds): + n.id = oldId + n.transitionNoneToSym() + n.sym = result + n.info = oldInfo + n.typ = result.typ + else: + localError(c.config, n.info, "identifier expected") + result = errorSym(c, n) + proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = result = nil inc c.inTypeContext diff --git a/doc/manual_experimental.md b/doc/manual_experimental.md index 2a56c1354..37453bc89 100644 --- a/doc/manual_experimental.md +++ b/doc/manual_experimental.md @@ -453,28 +453,25 @@ Assuming `foo` is a macro or a template, this is roughly equivalent to: ``` -Symbols as template/macro calls -=============================== +Symbols as template/macro calls (alias syntax) +============================================== -Templates and macros that take no arguments can be called as lone symbols, -i.e. without parentheses. This is useful for repeated uses of complex -expressions that cannot conveniently be represented as runtime values. +Templates and macros that have no generic parameters and no required arguments +can be called as lone symbols, i.e. without parentheses. This is useful for +repeated uses of complex expressions that cannot conveniently be represented +as runtime values. ```nim type Foo = object bar: int var foo = Foo(bar: 10) - template bar: untyped = foo.bar + template bar: int = foo.bar assert bar == 10 bar = 15 assert bar == 15 ``` -In the future, this may require more specific information on template or macro -signatures to be used. Specializations for some applications of this may also -be introduced to guarantee consistency and circumvent bugs. - Not nil annotation ================== diff --git a/tests/errmsgs/t5167_5.nim b/tests/errmsgs/t5167_5.nim index ab6f094f3..6c1269bce 100644 --- a/tests/errmsgs/t5167_5.nim +++ b/tests/errmsgs/t5167_5.nim @@ -1,13 +1,9 @@ discard """ cmd: "nim check --mm:refc $file" -errormsg: "'t' has unspecified generic parameters" -nimout: ''' -t5167_5.nim(10, 16) Error: expression 'system' has no type (or is ambiguous) -t5167_5.nim(21, 9) Error: 't' has unspecified generic parameters -''' """ # issue #11942 -discard newSeq[system]() +discard newSeq[system]() #[tt.Error + ^ expression 'system' has no type (or is ambiguous)]# # issue #5167 template t[B]() = @@ -18,8 +14,12 @@ macro m[T]: untyped = nil proc bar(x: proc (x: int)) = echo "bar" -let x = t -bar t +let x = t #[tt.Error + ^ 't' has unspecified generic parameters]# +bar t #[tt.Error + ^ 't' has unspecified generic parameters]# -let y = m -bar m +let y = m #[tt.Error + ^ 'm' has unspecified generic parameters]# +bar m #[tt.Error + ^ 'm' has unspecified generic parameters]# diff --git a/tests/template/t13515.nim b/tests/template/t13515.nim deleted file mode 100644 index ffebedcbe..000000000 --- a/tests/template/t13515.nim +++ /dev/null @@ -1,16 +0,0 @@ -discard """ - action: compile -""" - -template test: bool = true - -# compiles: -if not test: - echo "wtf" - -# does not compile: -template x = - if not test: - echo "wtf" - -x diff --git a/tests/template/taliassyntax.nim b/tests/template/taliassyntax.nim new file mode 100644 index 000000000..969a9d144 --- /dev/null +++ b/tests/template/taliassyntax.nim @@ -0,0 +1,63 @@ +type Foo = object + bar: int + +var foo = Foo(bar: 10) +template bar: int = foo.bar +doAssert bar == 10 +bar = 15 +doAssert bar == 15 +var foo2 = Foo(bar: -10) +doAssert bar == 15 +# works in generics +proc genericProc[T](x: T): string = + $(x, bar) +doAssert genericProc(true) == "(true, 15)" +# redefine +template bar: int {.redefine.} = foo2.bar +doAssert bar == -10 + +block: # subscript + var bazVal = @[1, 2, 3] + template baz: seq[int] = bazVal + doAssert baz[1] == 2 + proc genericProc2[T](x: T): string = + result = $(x, baz[1]) + baz[1] = 7 + doAssert genericProc2(true) == "(true, 2)" + doAssert baz[1] == 7 + baz[1] = 14 + doAssert baz[1] == 14 + +block: # type alias + template Int2: untyped = int + let x: Int2 = 123 + proc generic[T](): string = + template U: untyped = T + var x: U + result = $typeof(x) + doAssert result == $U + doAssert result == $T + doAssert generic[int]() == "int" + doAssert generic[Int2]() == "int" + doAssert generic[string]() == "string" + doAssert generic[seq[int]]() == "seq[int]" + doAssert generic[seq[Int2]]() == "seq[int]" + discard generic[123]() + proc genericStatic[X; T: static[X]](): string = + template U: untyped = T + result = $U + doAssert result == $T + doAssert genericStatic[int, 123]() == "123" + doAssert genericStatic[Int2, 123]() == "123" + doAssert genericStatic[(string, bool), ("a", true)]() == "(\"a\", true)" + +block: # issue #13515 + template test: bool = true + # compiles: + if not test: + doAssert false + # does not compile: + template x = + if not test: + doAssert false + x diff --git a/tests/template/taliassyntaxerrors.nim b/tests/template/taliassyntaxerrors.nim new file mode 100644 index 000000000..f16acaf91 --- /dev/null +++ b/tests/template/taliassyntaxerrors.nim @@ -0,0 +1,28 @@ +discard """ + cmd: "nim check --hints:off $file" +""" + +block: # with params + type Foo = object + bar: int + + var foo = Foo(bar: 10) + template bar(x: int): int = x + foo.bar + let a = bar #[tt.Error + ^ invalid type: 'template (x: int): int' for let. Did you mean to call the template with '()'?]# + bar = 15 #[tt.Error + ^ 'bar' cannot be assigned to]# + +block: # generic template + type Foo = object + bar: int + + var foo = Foo(bar: 10) + template bar[T]: T = T(foo.bar) + let a = bar #[tt.Error + ^ invalid type: 'template (): T' for let. Did you mean to call the template with '()'?; tt.Error + ^ 'bar' has unspecified generic parameters]# + let b = bar[float]() + doAssert b == 10.0 + bar = 15 #[tt.Error + ^ 'bar' cannot be assigned to]# |