diff --git a/changelog.md b/changelog.md index 42f4695678..78c707bf41 100644 --- a/changelog.md +++ b/changelog.md @@ -165,6 +165,8 @@ echo f - `var a {.foo.}: MyType = expr` now lowers to `foo(a, MyType, expr)` for non builtin pragmas, enabling things like lvalue references, see `pragmas.byaddr` +- `macro pragmas` can now be used in type sections. + ## Language changes - Unsigned integer operators have been fixed to allow promotion of the first operand. diff --git a/compiler/ast.nim b/compiler/ast.nim index 33edcc9532..8b000ecb92 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -1597,48 +1597,43 @@ proc transitionToLet*(s: PSym) = s.bitsize = obj.bitsize s.alignment = obj.alignment -proc shallowCopy*(src: PNode): PNode = - # does not copy its sons, but provides space for them: - if src == nil: return nil - result = newNode(src.kind) - result.info = src.info - result.typ = src.typ - result.flags = src.flags * PersistentNodeFlags - result.comment = src.comment +template copyNodeImpl(dst, src, processSonsStmt) = + if src == nil: return + dst = newNode(src.kind) + dst.info = src.info + dst.typ = src.typ + dst.flags = src.flags * PersistentNodeFlags + dst.comment = src.comment when defined(useNodeIds): - if result.id == nodeIdToDebug: + if dst.id == nodeIdToDebug: echo "COMES FROM ", src.id case src.kind - of nkCharLit..nkUInt64Lit: result.intVal = src.intVal - of nkFloatLiterals: result.floatVal = src.floatVal - of nkSym: result.sym = src.sym - of nkIdent: result.ident = src.ident - of nkStrLit..nkTripleStrLit: result.strVal = src.strVal - else: newSeq(result.sons, src.len) + of nkCharLit..nkUInt64Lit: dst.intVal = src.intVal + of nkFloatLiterals: dst.floatVal = src.floatVal + of nkSym: dst.sym = src.sym + of nkIdent: dst.ident = src.ident + of nkStrLit..nkTripleStrLit: dst.strVal = src.strVal + else: processSonsStmt + +proc shallowCopy*(src: PNode): PNode = + # does not copy its sons, but provides space for them: + copyNodeImpl(result, src): + newSeq(result.sons, src.len) proc copyTree*(src: PNode): PNode = # copy a whole syntax tree; performs deep copying - if src == nil: - return nil - result = newNode(src.kind) - result.info = src.info - result.typ = src.typ - result.flags = src.flags * PersistentNodeFlags - result.comment = src.comment - when defined(useNodeIds): - if result.id == nodeIdToDebug: - echo "COMES FROM ", src.id - case src.kind - of nkCharLit..nkUInt64Lit: result.intVal = src.intVal - of nkFloatLiterals: result.floatVal = src.floatVal - of nkSym: result.sym = src.sym - of nkIdent: result.ident = src.ident - of nkStrLit..nkTripleStrLit: result.strVal = src.strVal - else: + copyNodeImpl(result, src): newSeq(result.sons, src.len) for i in 0..= 1: it[0] else: it + let it = n[i] + let key = if it.kind in nkPragmaCallKinds and it.len >= 1: it[0] else: it if whichPragma(it) != wInvalid: # Not a custom pragma diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index 6e423181c3..7cddc7520c 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -1567,9 +1567,44 @@ proc semTypeClass(c: PContext, n: PNode, prev: PType): PType = result.n[3] = semConceptBody(c, n[3]) closeScope(c) +proc applyTypeSectionPragmas(c: PContext; pragmas, operand: PNode): PNode = + for p in pragmas: + let key = if p.kind in nkPragmaCallKinds and p.len >= 1: p[0] else: p + + if p.kind == nkEmpty or whichPragma(p) != wInvalid: + discard "builtin pragma" + elif strTableGet(c.userPragmas, considerQuotedIdent(c, key)) != nil: + discard "User-defined pragma" + else: + # we transform ``(arg1, arg2: T) {.m, rest.}`` into ``m((arg1, arg2: T) {.rest.})`` and + # let the semantic checker deal with it: + var x = newNodeI(nkCall, key.info) + x.add(key) + if p.kind in nkPragmaCallKinds and p.len > 1: + # pass pragma arguments to the macro too: + for i in 1 ..< p.len: + x.add(p[i]) + # Also pass the node the pragma has been applied to + x.add(operand.copyTreeWithoutNode(p)) + # recursion assures that this works for multiple macro annotations too: + var r = semOverloadedCall(c, x, x, {skMacro, skTemplate}, {efNoUndeclared}) + if r != nil: + doAssert r[0].kind == nkSym + let m = r[0].sym + case m.kind + of skMacro: return semMacroExpr(c, r, r, m, {efNoSemCheck}) + of skTemplate: return semTemplateExpr(c, r, m, {efNoSemCheck}) + else: doAssert(false, "cannot happen") + proc semProcTypeWithScope(c: PContext, n: PNode, - prev: PType, kind: TSymKind): PType = + prev: PType, kind: TSymKind): PType = checkSonsLen(n, 2, c.config) + + if n[1].kind != nkEmpty and n[1].len > 0: + let macroEval = applyTypeSectionPragmas(c, n[1], n) + if macroEval != nil: + return semTypeNode(c, macroEval, prev) + openScope(c) result = semProcTypeNode(c, n[0], nil, prev, kind, isType=true) # start with 'ccClosure', but of course pragmas can overwrite this: @@ -1845,11 +1880,12 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = result.addSonSkipIntLit(child) else: result = semProcTypeWithScope(c, n, prev, skIterator) - result.flags.incl(tfIterator) - if n.lastSon.kind == nkPragma and hasPragma(n.lastSon, wInline): - result.callConv = ccInline - else: - result.callConv = ccClosure + if result.kind == tyProc: + result.flags.incl(tfIterator) + if n.lastSon.kind == nkPragma and hasPragma(n.lastSon, wInline): + result.callConv = ccInline + else: + result.callConv = ccClosure of nkProcTy: if n.len == 0: result = newConstraint(c, tyProc) diff --git a/doc/manual.rst b/doc/manual.rst index bffb9e60cc..4c1d5c6590 100644 --- a/doc/manual.rst +++ b/doc/manual.rst @@ -5353,26 +5353,6 @@ powerful programming construct that still suffices. So the "check list" is: (4) Else: Use a macro. -Macros as pragmas ------------------ - -Whole routines (procs, iterators etc.) can also be passed to a template or -a macro via the pragma notation: - -.. code-block:: nim - template m(s: untyped) = discard - - proc p() {.m.} = discard - -This is a simple syntactic transformation into: - -.. code-block:: nim - template m(s: untyped) = discard - - m: - proc p() = discard - - For Loop Macro -------------- @@ -6408,29 +6388,6 @@ the created global variables within a module is not defined, but all of them will be initialized after any top-level variables in their originating module and before any variable in a module that imports it. -pragma pragma -------------- - -The ``pragma`` pragma can be used to declare user defined pragmas. This is -useful because Nim's templates and macros do not affect pragmas. User -defined pragmas are in a different module-wide scope than all other symbols. -They cannot be imported from a module. - -Example: - -.. code-block:: nim - when appType == "lib": - {.pragma: rtl, exportc, dynlib, cdecl.} - else: - {.pragma: rtl, importc, dynlib: "client.dll", cdecl.} - - proc p*(a, b: int): int {.rtl.} = - result = a+b - -In the example a new pragma named ``rtl`` is introduced that either imports -a symbol from a dynamic library or exports the symbol for dynamic library -generation. - Disabling certain messages -------------------------- Nim generates some warnings and hints ("line too long") that may annoy the @@ -7127,6 +7084,34 @@ used. To see if a value was provided, `defined(FooBar)` can be used. The syntax `-d:flag` is actually just a shortcut for `-d:flag=true`. +User-defined pragmas +==================== + + +pragma pragma +------------- + +The ``pragma`` pragma can be used to declare user defined pragmas. This is +useful because Nim's templates and macros do not affect pragmas. User +defined pragmas are in a different module-wide scope than all other symbols. +They cannot be imported from a module. + +Example: + +.. code-block:: nim + when appType == "lib": + {.pragma: rtl, exportc, dynlib, cdecl.} + else: + {.pragma: rtl, importc, dynlib: "client.dll", cdecl.} + + proc p*(a, b: int): int {.rtl.} = + result = a+b + +In the example a new pragma named ``rtl`` is introduced that either imports +a symbol from a dynamic library or exports the symbol for dynamic library +generation. + + Custom annotations ------------------ It is possible to define custom typed pragmas. Custom pragmas do not effect @@ -7193,6 +7178,51 @@ More examples with custom pragmas: alpha {.editRange: [0.0..1.0], animatable.}: float32 +Macro pragmas +------------- + +All macros and templates can also be used as pragmas. They can be attached +to routines (procs, iterators, etc), type names or type expressions. The +compiler will perform the following simple syntactic transformations: + +.. code-block:: nim + template command(name: string, def: untyped) = discard + + proc p() {.command("print").} = discard + +This is translated to: + +.. code-block:: nim + command("print"): + proc p() = discard + +------ + +.. code-block:: nim + type + AsyncEventHandler = proc (x: Event) {.async.} + +This is translated to: + +.. code-block:: nim + type + AsyncEventHandler = async(proc (x: Event)) + +------ + +.. code-block:: nim + type + MyObject {.schema: "schema.protobuf".} = object + +This is translated to a call to the ``schema`` macro with a `nnkTypeDef` +AST node capturing both the left-hand side and right-hand side of the +definition. The macro can return a potentially modified `nnkTypeDef` tree +will replace the original row in the type section. + +When multiple macro pragmas are applied to the same definition, the +compiler will apply them consequently from left to right. Each macro +will receive as input the output of the previous one. + Foreign function interface @@ -7269,7 +7299,6 @@ In the example the external name of ``p`` is set to ``prefixp``. Only ``$1`` is available and a literal dollar sign must be written as ``$$``. - Bycopy pragma ------------- @@ -7313,6 +7342,7 @@ checked. **Future directions**: GC'ed memory should be allowed in unions and the GC should scan unions conservatively. + Packed pragma ------------- The ``packed`` pragma can be applied to any ``object`` type. It ensures diff --git a/lib/pure/asyncmacro.nim b/lib/pure/asyncmacro.nim index ce84491eb4..c6f4ae04ad 100644 --- a/lib/pure/asyncmacro.nim +++ b/lib/pure/asyncmacro.nim @@ -212,6 +212,12 @@ proc verifyReturnType(typeName: string) {.compileTime.} = proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} = ## This macro transforms a single procedure into a closure iterator. ## The ``async`` macro supports a stmtList holding multiple async procedures. + if prc.kind == nnkProcTy: + result = prc + if prc[0][0].kind == nnkEmpty: + result[0][0] = parseExpr("Future[void]") + return result + if prc.kind notin {nnkProcDef, nnkLambda, nnkMethodDef, nnkDo}: error("Cannot transform this node kind into an async proc." & " proc/method definition or lambda node expected.") diff --git a/tests/pragmas/tcustom_pragma.nim b/tests/pragmas/tcustom_pragma.nim index 719af4d504..2d970aba1b 100644 --- a/tests/pragmas/tcustom_pragma.nim +++ b/tests/pragmas/tcustom_pragma.nim @@ -1,6 +1,6 @@ {.experimental: "notnil".} -import macros +import macros, asyncmacro, asyncfutures block: template myAttr() {.pragma.} @@ -249,3 +249,89 @@ block: var e {.fooBar("foo", 123, 'u').}: int doAssert(hasCustomPragma(e, fooBar)) doAssert(getCustomPragmaVal(e, fooBar).c == 123) + +block: + macro expectedAst(expectedRepr: static[string], input: untyped): untyped = + assert input.treeRepr & "\n" == expectedRepr + return input + + const procTypeAst = """ +ProcTy + FormalParams + Empty + IdentDefs + Ident "x" + Ident "int" + Empty + Pragma + Ident "async" +""" + + type + Foo = proc (x: int) {.expectedAst(procTypeAst), async.} + + static: assert Foo is proc(x: int): Future[void] + + const asyncProcTypeAst = """ +ProcTy + FormalParams + BracketExpr + Ident "Future" + Ident "void" + IdentDefs + Ident "s" + Ident "string" + Empty + Pragma +""" + + type + Bar = proc (s: string) {.async, expectedAst(asyncProcTypeAst).} + + static: assert Bar is proc(x: string): Future[void] + + const typeAst = """ +TypeDef + PragmaExpr + Ident "Baz" + Pragma + Empty + ObjectTy + Empty + Empty + RecList + IdentDefs + Ident "x" + Ident "string" + Empty +""" + + type + Baz {.expectedAst(typeAst).} = object + x: string + + static: assert Baz.x is string + + const procAst = """ +ProcDef + Ident "bar" + Empty + Empty + FormalParams + Ident "string" + IdentDefs + Ident "s" + Ident "string" + Empty + Empty + Empty + StmtList + ReturnStmt + Ident "s" +""" + + proc bar(s: string): string {.expectedAst(procAst).} = + return s + + static: assert bar("x") == "x" +