summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--compiler/ast.nim11
-rw-r--r--compiler/semexprs.nim20
-rw-r--r--compiler/semgnrc.nim47
-rw-r--r--compiler/semstmts.nim15
-rw-r--r--compiler/semtempl.nim8
-rw-r--r--compiler/semtypes.nim129
-rw-r--r--doc/manual_experimental.md17
-rw-r--r--tests/errmsgs/t5167_5.nim20
-rw-r--r--tests/template/t13515.nim16
-rw-r--r--tests/template/taliassyntax.nim63
-rw-r--r--tests/template/taliassyntaxerrors.nim28
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]#