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.}
This commit is contained in:
metagn
2023-04-22 10:11:56 +03:00
committed by GitHub
parent c136ebf1ed
commit 63d29ddd69
11 changed files with 226 additions and 148 deletions

View File

@@ -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

View File

@@ -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:

View File

@@ -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:
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 = 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:
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()

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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
==================

View File

@@ -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]#

View File

@@ -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

View File

@@ -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

View File

@@ -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]#