Allow void macro result (#11286)

* allow void macro result
* add test for void macro result type
This commit is contained in:
Arne Döring 2019-05-21 21:31:40 +02:00 • committed by Andreas Rumpf
commit f94ec363ab
25 changed files with 143 additions and 60 deletions

View file

@ -36,8 +36,12 @@
- Compile time checks for integer and float conversions are now stricter. For example, `const x = uint32(-1)` now gives a compile time error instead of being equivalent to `const x = 0xFFFFFFFF'u32`. - Compile time checks for integer and float conversions are now stricter. For example, `const x = uint32(-1)` now gives a compile time error instead of being equivalent to `const x = 0xFFFFFFFF'u32`.
- A bug allowed ``macro foo(): int = 123`` to compile even though a - Using `typed` as the result type in templates/macros now means
macros has to return a ``NimNode``. This has been fixed. "expression with a type". The old meaning of `typed` is preserved
as `void` or no result type at all.
- A bug allowed `macro foo(): int = 123` to compile even though a
macros has to return a `NimNode`. This has been fixed.
#### Breaking changes in the standard library #### Breaking changes in the standard library

View file

@ -759,7 +759,7 @@ proc getLinkCmd(conf: ConfigRef; output: AbsoluteFile,
template getLinkCmd(conf: ConfigRef; output: AbsoluteFile, objfiles: string): string = template getLinkCmd(conf: ConfigRef; output: AbsoluteFile, objfiles: string): string =
getLinkCmd(conf, output, objfiles, optGenDynLib in conf.globalOptions) getLinkCmd(conf, output, objfiles, optGenDynLib in conf.globalOptions)
template tryExceptOSErrorMessage(conf: ConfigRef; errorPrefix: string = "", body: untyped): typed = template tryExceptOSErrorMessage(conf: ConfigRef; errorPrefix: string = "", body: untyped) =
try: try:
body body
except OSError: except OSError:

View file

@ -407,12 +407,11 @@ proc semAfterMacroCall(c: PContext, call, macroResult: PNode,
else: else:
case s.typ.sons[0].kind case s.typ.sons[0].kind
of tyUntyped: of tyUntyped:
# BUGFIX: we cannot expect a type here, because module aliases would not # Not expecting a type here allows templates like in ``tmodulealias.in``.
# work then (see the ``tmodulealias`` test)
# semExprWithType(c, result)
result = semExpr(c, result, flags) result = semExpr(c, result, flags)
of tyTyped: of tyTyped:
result = semStmt(c, result, flags) # More restrictive version.
result = semExprWithType(c, result, flags)
of tyTypeDesc: of tyTypeDesc:
if result.kind == nkStmtList: result.kind = nkStmtListType if result.kind == nkStmtList: result.kind = nkStmtListType
var typ = semTypeNode(c, result, nil) var typ = semTypeNode(c, result, nil)

View file

@ -1985,7 +1985,7 @@ proc semQuoteAst(c: PContext, n: PNode): PNode =
if ids.len > 0: if ids.len > 0:
dummyTemplate.sons[paramsPos] = newNodeI(nkFormalParams, n.info) dummyTemplate.sons[paramsPos] = newNodeI(nkFormalParams, n.info)
dummyTemplate[paramsPos].add getSysSym(c.graph, n.info, "typed").newSymNode # return type dummyTemplate[paramsPos].add getSysSym(c.graph, n.info, "untyped").newSymNode # return type
ids.add getSysSym(c.graph, n.info, "untyped").newSymNode # params type ids.add getSysSym(c.graph, n.info, "untyped").newSymNode # params type
ids.add c.graph.emptyNode # no default value ids.add c.graph.emptyNode # no default value
dummyTemplate[paramsPos].add newNode(nkIdentDefs, n.info, ids) dummyTemplate[paramsPos].add newNode(nkIdentDefs, n.info, ids)

View file

@ -1356,7 +1356,7 @@ proc semBorrow(c: PContext, n: PNode, s: PSym) =
localError(c.config, n.info, errNoSymbolToBorrowFromFound) localError(c.config, n.info, errNoSymbolToBorrowFromFound)
proc addResult(c: PContext, t: PType, info: TLineInfo, owner: TSymKind) = proc addResult(c: PContext, t: PType, info: TLineInfo, owner: TSymKind) =
if t != nil: if owner == skMacro or t != nil:
var s = newSym(skResult, getIdent(c.cache, "result"), getCurrOwner(c), info) var s = newSym(skResult, getIdent(c.cache, "result"), getCurrOwner(c), info)
s.typ = t s.typ = t
incl(s.flags, sfUsed) incl(s.flags, sfUsed)
@ -1550,17 +1550,17 @@ proc activate(c: PContext, n: PNode) =
discard discard
proc maybeAddResult(c: PContext, s: PSym, n: PNode) = proc maybeAddResult(c: PContext, s: PSym, n: PNode) =
if s.typ.sons[0] != nil and not isInlineIterator(s):
let resultType =
if s.kind == skMacro: if s.kind == skMacro:
if s.typ.sons[0].kind == tyTypeDesc: let resultType =
if s.typ.sons[0] != nil and s.typ.sons[0].kind == tyTypeDesc:
s.typ.sons[0] s.typ.sons[0]
else: else:
sysTypeFromName(c.graph, n.info, "NimNode") sysTypeFromName(c.graph, n.info, "NimNode")
else:
s.typ.sons[0]
addResult(c, resultType, n.info, s.kind) addResult(c, resultType, n.info, s.kind)
addResultNode(c, n) addResultNode(c, n)
elif s.typ.sons[0] != nil and not isInlineIterator(s):
addResult(c, s.typ.sons[0], n.info, s.kind)
addResultNode(c, n)
proc canonType(c: PContext, t: PType): PType = proc canonType(c: PContext, t: PType): PType =
if t.kind == tySequence: if t.kind == tySequence:
@ -1888,7 +1888,7 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
# context as it may even be evaluated in 'system.compiles': # context as it may even be evaluated in 'system.compiles':
trackProc(c, s, s.ast[bodyPos]) trackProc(c, s, s.ast[bodyPos])
else: else:
if s.typ.sons[0] != nil and kind != skIterator: if (s.typ.sons[0] != nil and kind != skIterator) or kind == skMacro:
addDecl(c, newSym(skUnknown, getIdent(c.cache, "result"), nil, n.info)) addDecl(c, newSym(skUnknown, getIdent(c.cache, "result"), nil, n.info))
openScope(c) openScope(c)
@ -2015,7 +2015,6 @@ proc semMacroDef(c: PContext, n: PNode): PNode =
let param = t.n.sons[i].sym let param = t.n.sons[i].sym
if param.typ.kind != tyUntyped: allUntyped = false if param.typ.kind != tyUntyped: allUntyped = false
if allUntyped: incl(s.flags, sfAllUntyped) if allUntyped: incl(s.flags, sfAllUntyped)
if t.sons[0] == nil: localError(c.config, n.info, "macro needs a return type")
if n.sons[bodyPos].kind == nkEmpty: if n.sons[bodyPos].kind == nkEmpty:
localError(c.config, n.info, errImplOfXexpected % s.name.s) localError(c.config, n.info, errImplOfXexpected % s.name.s)

View file

@ -606,11 +606,6 @@ proc semTemplateDef(c: PContext, n: PNode): PNode =
if n.sons[genericParamsPos].kind == nkEmpty: if n.sons[genericParamsPos].kind == nkEmpty:
# we have a list of implicit type parameters: # we have a list of implicit type parameters:
n.sons[genericParamsPos] = gp n.sons[genericParamsPos] = gp
# no explicit return type? -> use tyTyped
if n.sons[paramsPos].sons[0].kind == nkEmpty:
# use ``stmt`` as implicit result type
s.typ.sons[0] = newTypeS(tyTyped, c)
s.typ.n.sons[0] = newNodeIT(nkType, n.info, s.typ.sons[0])
else: else:
s.typ = newTypeS(tyProc, c) s.typ = newTypeS(tyProc, c)
# XXX why do we need tyTyped as a return type again? # XXX why do we need tyTyped as a return type again?

View file

@ -855,6 +855,7 @@ proc findEnforcedStaticType(t: PType): PType =
# This handles types such as `static[T] and Foo`, # This handles types such as `static[T] and Foo`,
# which are subset of `static[T]`, hence they could # which are subset of `static[T]`, hence they could
# be treated in the same way # be treated in the same way
if t == nil: return nil
if t.kind == tyStatic: return t if t.kind == tyStatic: return t
if t.kind == tyAnd: if t.kind == tyAnd:
for s in t.sons: for s in t.sons:
@ -868,7 +869,7 @@ proc addParamOrResult(c: PContext, param: PSym, kind: TSymKind) =
var a = copySym(param) var a = copySym(param)
a.typ = staticType.base a.typ = staticType.base
addDecl(c, a) addDecl(c, a)
elif param.typ.kind == tyTypeDesc: elif param.typ != nil and param.typ.kind == tyTypeDesc:
addDecl(c, param) addDecl(c, param)
else: else:
# within a macro, every param has the type NimNode! # within a macro, every param has the type NimNode!
@ -1194,6 +1195,18 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode,
if n.sons[0].kind != nkEmpty: if n.sons[0].kind != nkEmpty:
r = semTypeNode(c, n.sons[0], nil) r = semTypeNode(c, n.sons[0], nil)
if r != nil and kind in {skMacro, skTemplate} and r.kind == tyTyped:
# XXX: To implement the propesed change in the warning, just
# delete this entire if block. The rest is (at least at time of
# writing this comment) already implemented.
let info = n.sons[0].info
const msg = "`typed` will change its meaning in future versions of Nim. " &
"`void` or no return type declaration at all has the same " &
"meaning as the current meaning of `typed` as return type " &
"declaration."
message(c.config, info, warnDeprecated, msg)
r = nil
if r != nil: if r != nil:
# turn explicit 'void' return type into 'nil' because the rest of the # turn explicit 'void' return type into 'nil' because the rest of the
# compiler only checks for 'nil': # compiler only checks for 'nil':

View file

@ -1104,7 +1104,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
#echo "new pc ", newPc, " calling: ", prc.name.s #echo "new pc ", newPc, " calling: ", prc.name.s
var newFrame = PStackFrame(prc: prc, comesFrom: pc, next: tos) var newFrame = PStackFrame(prc: prc, comesFrom: pc, next: tos)
newSeq(newFrame.slots, prc.offset+ord(isClosure)) newSeq(newFrame.slots, prc.offset+ord(isClosure))
if not isEmptyType(prc.typ.sons[0]) or prc.kind == skMacro: if not isEmptyType(prc.typ.sons[0]):
putIntoReg(newFrame.slots[0], getNullValue(prc.typ.sons[0], prc.info, c.config)) putIntoReg(newFrame.slots[0], getNullValue(prc.typ.sons[0], prc.info, c.config))
for i in 1 .. rc-1: for i in 1 .. rc-1:
newFrame.slots[i] = regs[rb+i] newFrame.slots[i] = regs[rb+i]

View file

@ -567,7 +567,7 @@ proc quote*(bl: typed, op = "``"): NimNode {.magic: "QuoteAst", noSideEffect.}
## ##
## .. code-block:: nim ## .. code-block:: nim
## ##
## macro check(ex: untyped): typed = ## macro check(ex: untyped) =
## # this is a simplified version of the check macro from the ## # this is a simplified version of the check macro from the
## # unittest module. ## # unittest module.
## ##

View file

@ -572,7 +572,7 @@ when not useUnicode:
proc isTitle(a: char): bool {.inline.} = return false proc isTitle(a: char): bool {.inline.} = return false
proc isWhiteSpace(a: char): bool {.inline.} = return a in {' ', '\9'..'\13'} proc isWhiteSpace(a: char): bool {.inline.} = return a in {' ', '\9'..'\13'}
template matchOrParse(mopProc: untyped): typed = template matchOrParse(mopProc: untyped) =
# Used to make the main matcher proc *rawMatch* as well as event parser # Used to make the main matcher proc *rawMatch* as well as event parser
# procs. For the former, *enter* and *leave* event handler code generators # procs. For the former, *enter* and *leave* event handler code generators
# are provided which just return *discard*. # are provided which just return *discard*.

View file

@ -71,7 +71,7 @@ template onFailedAssert*(msg, code: untyped): untyped {.dirty.} =
let msg = msgIMPL let msg = msgIMPL
code code
template doAssertRaises*(exception: typedesc, code: untyped): typed = template doAssertRaises*(exception: typedesc, code: untyped) =
## Raises ``AssertionError`` if specified ``code`` does not raise the ## Raises ``AssertionError`` if specified ``code`` does not raise the
## specified exception. Example: ## specified exception. Example:
## ##

View file

@ -8,5 +8,5 @@ $nimsuggest --tester $file
>sug $1 >sug $1
sug;;skMacro;;tsug_template.tmpb;;macro (){.noSideEffect, gcsafe, locks: 0.};;$file;;2;;6;;"";;0;;Prefix sug;;skMacro;;tsug_template.tmpb;;macro (){.noSideEffect, gcsafe, locks: 0.};;$file;;2;;6;;"";;0;;Prefix
sug;;skConverter;;tsug_template.tmpc;;converter ();;$file;;3;;10;;"";;0;;Prefix sug;;skConverter;;tsug_template.tmpc;;converter ();;$file;;3;;10;;"";;0;;Prefix
sug;;skTemplate;;tsug_template.tmpa;;template (): typed;;$file;;1;;9;;"";;0;;Prefix sug;;skTemplate;;tsug_template.tmpa;;template ();;$file;;1;;9;;"";;0;;Prefix
""" """

View file

@ -1,7 +1,7 @@
# bug #1679 # bug #1679
import macros, tables, hashes import macros, tables, hashes
proc hash(v: NimNode): Hash = 4 # performance is for suckers proc hash(v: NimNode): Hash = 4 # performance is for suckers
macro test(body: untyped): typed = macro test(body: untyped) =
var a = initCountTable[NimNode]() var a = initCountTable[NimNode]()
a.inc(body) a.inc(body)

View file

@ -46,7 +46,7 @@ block ttemplate:
for name, value in (n: "v").fieldPairs: for name, value in (n: "v").fieldPairs:
echo name echo name
template wrapper: typed = template wrapper(): void =
for name, value in (n: "v").fieldPairs: for name, value in (n: "v").fieldPairs:
echo name echo name
wrapper() wrapper()

View file

@ -12,7 +12,7 @@ import unittest, macros
# bug #4555 # bug #4555
macro memo(n: untyped): typed = macro memo(n: untyped) =
result = n result = n
proc fastFib(n: int): int {.memo.} = 40 proc fastFib(n: int): int {.memo.} = 40

View file

@ -32,7 +32,7 @@ proc foo(): seq[NimNode] {.compiletime.} =
result.add test() result.add test()
result.add parseExpr("echo(5+56)") result.add parseExpr("echo(5+56)")
macro bar(): typed = macro bar() =
result = newNimNode(nnkStmtList) result = newNimNode(nnkStmtList)
let x = foo() let x = foo()
for xx in x: for xx in x:

View file

@ -22,7 +22,7 @@ proc symToIdent(x: NimNode): NimNode =
result.add symToIdent(c) result.add symToIdent(c)
# check getTypeInst and getTypeImpl for given symbol x # check getTypeInst and getTypeImpl for given symbol x
macro testX(x,inst0: typed; recurse: static[bool]; implX: typed): typed = macro testX(x,inst0: typed; recurse: static[bool]; implX: typed) =
# check that getTypeInst(x) equals inst0 # check that getTypeInst(x) equals inst0
let inst = x.getTypeInst let inst = x.getTypeInst
let instr = inst.symToIdent.treeRepr let instr = inst.symToIdent.treeRepr

View file

@ -38,7 +38,7 @@ macro importImpl_forward(name, returns: untyped): untyped =
decls.add res decls.add res
echo(repr(res)) echo(repr(res))
macro importImpl(name, returns, body: untyped): typed = macro importImpl(name, returns, body: untyped) =
#var res = getAST(importImpl_forward(name, returns)) #var res = getAST(importImpl_forward(name, returns))
discard getAST(importImpl_forward(name, returns)) discard getAST(importImpl_forward(name, returns))
var res = copyNimTree(decls[decls.high]) var res = copyNimTree(decls[decls.high])
@ -46,7 +46,7 @@ macro importImpl(name, returns, body: untyped): typed =
echo repr(res) echo repr(res)
impls.add res impls.add res
macro okayy: typed = macro okayy() =
result = newNimNode(nnkStmtList) result = newNimNode(nnkStmtList)
for node in decls: result.add node for node in decls: result.add node
for node in impls: result.add node for node in impls: result.add node

75
tests/macros/tmacro6.nim Normal file
View file

@ -0,0 +1,75 @@
discard """
errormsg: "expression '123' is of type 'int literal(123)' and has to be discarded"
line: 71
"""
import macros
proc foo(a,b,c: int): int =
result += a
result += b
result += c
macro bar(a,b,c: int): int =
result = newCall(ident"echo")
result.add a
result.add b
result.add c
macro baz(a,b,c: int): int =
let stmt = nnkStmtListExpr.newTree()
stmt.add newCall(ident"echo", a)
stmt.add newCall(ident"echo", b)
stmt.add newCall(ident"echo", c)
stmt.add newLit(123)
return c
# test no result type with explicit return
macro baz2(a,b,c: int) =
let stmt = nnkStmtListExpr.newTree()
stmt.add newCall(ident"echo", a)
stmt.add newCall(ident"echo", b)
stmt.add newCall(ident"echo", c)
return stmt
# test explicit void type with explicit return
macro baz3(a,b,c: int): void =
let stmt = nnkStmtListExpr.newTree()
stmt.add newCall(ident"echo", a)
stmt.add newCall(ident"echo", b)
stmt.add newCall(ident"echo", c)
return stmt
# test no result type with result variable
macro baz4(a,b,c: int) =
result = nnkStmtListExpr.newTree()
result.add newCall(ident"echo", a)
result.add newCall(ident"echo", b)
result.add newCall(ident"echo", c)
# test explicit void type with result variable
macro baz5(a,b,c: int): void =
let result = nnkStmtListExpr.newTree()
result.add newCall(ident"echo", a)
result.add newCall(ident"echo", b)
result.add newCall(ident"echo", c)
macro foobar1(): int =
result = quote do:
echo "Hello World"
1337
echo foobar1()
# this should create an error message, because 123 has to be discarded.
macro foobar2() =
result = quote do:
echo "Hello World"
123
echo foobar2()

View file

@ -18,9 +18,10 @@ case_token: inc i
#bug #488 #bug #488
macro foo: typed = macro foo() =
var exp = newCall("whatwhat", newIntLitNode(1)) var exp = newCall("whatwhat", newIntLitNode(1))
if compiles(getAst(exp)): return exp if compiles(getAst(exp)):
return exp
else: echo "Does not compute! (test OK)" else: echo "Does not compute! (test OK)"
foo() foo()
@ -37,7 +38,7 @@ proc `!!` (c: cstring): int =
proc f(name: MyString): int = proc f(name: MyString): int =
!! $ name !! $ name
macro repr_and_parse(fn: typed): typed = macro repr_and_parse(fn: typed) =
let fn_impl = fn.getImpl let fn_impl = fn.getImpl
fn_impl.name = genSym(nskProc, $fn_impl.name) fn_impl.name = genSym(nskProc, $fn_impl.name)
echo fn_impl.repr echo fn_impl.repr
@ -134,4 +135,3 @@ repr_and_parse(test_block)
repr_and_parse(test_cond_stmtlist) repr_and_parse(test_cond_stmtlist)
repr_and_parse(t2) repr_and_parse(t2)
repr_and_parse(test_pure_enums) repr_and_parse(test_pure_enums)

View file

@ -34,7 +34,7 @@ block t898:
block t7528: block t7528:
macro bar(n: untyped): typed = macro bar(n: untyped) =
result = newNimNode(nnkStmtList, n) result = newNimNode(nnkStmtList, n)
result.add(newCall("write", newIdentNode("stdout"), n)) result.add(newCall("write", newIdentNode("stdout"), n))

View file

@ -77,7 +77,7 @@ block generic_templates:
block tgetast_typeliar: block tgetast_typeliar:
proc error(s: string) = quit s proc error(s: string) = quit s
macro assertOrReturn(condition: bool; message: string): typed = macro assertOrReturn2(condition: bool; message: string) =
var line = condition.lineInfo() var line = condition.lineInfo()
result = quote do: result = quote do:
block: block:
@ -86,8 +86,10 @@ block tgetast_typeliar:
return return
macro assertOrReturn(condition: bool): typed = macro assertOrReturn(condition: bool): typed =
var message = condition.toStrLit() var message : NimNode = newLit(condition.repr)
result = getAst assertOrReturn(condition, message) # echo message
result = getAst assertOrReturn2(condition, message)
echo result.repr
proc point(size: int16): tuple[x, y: int16] = proc point(size: int16): tuple[x, y: int16] =
# returns random point in square area with given `size` # returns random point in square area with given `size`
@ -245,7 +247,3 @@ block ttempl5:
block templreturntype: block templreturntype:
template `=~` (a: int, b: int): bool = false template `=~` (a: int, b: int): bool = false
var foo = 2 =~ 3 var foo = 2 =~ 3

View file

@ -18,7 +18,7 @@ proc initOpts(): set[nlOptions] =
const cOpts = initOpts() const cOpts = initOpts()
macro nlo(): typed = macro nlo() =
nlOpts.incl(nloNone) nlOpts.incl(nloNone)
nlOpts.excl(nloDebug) nlOpts.excl(nloDebug)
result = newEmptyNode() result = newEmptyNode()

View file

@ -24,7 +24,7 @@ bb
const s = @[1,2,3] const s = @[1,2,3]
macro foo: typed = macro foo() =
for e in s: for e in s:
echo e echo e
@ -55,7 +55,7 @@ static:
var m2: TData = data var m2: TData = data
for x in m2.numbers: echo x for x in m2.numbers: echo x
macro ff(d: static[TData]): typed = macro ff(d: static[TData]) =
for x in d.letters: for x in d.letters:
echo x echo x

View file

@ -20,7 +20,7 @@ nimout: '''
#bug #2514 #bug #2514
macro foo(): typed = macro foo() =
var x = 8'u8 var x = 8'u8
var y = 9'u16 var y = 9'u16
var z = 17'u32 var z = 17'u32
@ -58,7 +58,7 @@ macro foo(): typed =
var zz = 0x7FFFFFFF'u32 var zz = 0x7FFFFFFF'u32
echo zz echo zz
macro foo2(): typed = macro foo2() =
var xx = 0x7FFFFFFFFFFFFFFF var xx = 0x7FFFFFFFFFFFFFFF
echo xx echo xx