pass typedesc as NimNode to macros (#11400)
* change typedesc's design in macros * Manual and changelog entry. * add link to RFC
This commit is contained in:
parent
71388caf2e
commit
cb45527e37
6 changed files with 54 additions and 39 deletions
|
|
@ -52,6 +52,12 @@
|
||||||
- With the exception of `uint` and `uint64`, conversion to unsigned types
|
- With the exception of `uint` and `uint64`, conversion to unsigned types
|
||||||
are now range checked during runtime.
|
are now range checked during runtime.
|
||||||
|
|
||||||
|
- Macro arguments of type `typedesc` are now passed in to the macro as
|
||||||
|
`NimNode` like every other type. Use either `typed` or
|
||||||
|
`static[typedesc]` for a behavior that is identical in new and old
|
||||||
|
Nim.
|
||||||
|
RFC: `Pass typedesc as NimNode to macros
|
||||||
|
<https://github.com/nim-lang/RFCs/issues/148>`_.
|
||||||
|
|
||||||
#### Breaking changes in the standard library
|
#### Breaking changes in the standard library
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1554,11 +1554,7 @@ proc activate(c: PContext, n: PNode) =
|
||||||
|
|
||||||
proc maybeAddResult(c: PContext, s: PSym, n: PNode) =
|
proc maybeAddResult(c: PContext, s: PSym, n: PNode) =
|
||||||
if s.kind == skMacro:
|
if s.kind == skMacro:
|
||||||
let resultType =
|
let resultType = sysTypeFromName(c.graph, n.info, "NimNode")
|
||||||
if s.typ.sons[0] != nil and s.typ.sons[0].kind == tyTypeDesc:
|
|
||||||
s.typ.sons[0]
|
|
||||||
else:
|
|
||||||
sysTypeFromName(c.graph, n.info, "NimNode")
|
|
||||||
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):
|
elif s.typ.sons[0] != nil and not isInlineIterator(s):
|
||||||
|
|
|
||||||
|
|
@ -869,8 +869,8 @@ 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 != nil and 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!
|
||||||
let nn = getSysSym(c.graph, param.info, "NimNode")
|
let nn = getSysSym(c.graph, param.info, "NimNode")
|
||||||
|
|
|
||||||
|
|
@ -2028,8 +2028,8 @@ proc setupMacroParam(x: PNode, typ: PType): TFullReg =
|
||||||
case typ.kind
|
case typ.kind
|
||||||
of tyStatic:
|
of tyStatic:
|
||||||
putIntoReg(result, x)
|
putIntoReg(result, x)
|
||||||
of tyTypeDesc:
|
#of tyTypeDesc:
|
||||||
putIntoReg(result, x)
|
# putIntoReg(result, x)
|
||||||
else:
|
else:
|
||||||
result.kind = rkNode
|
result.kind = rkNode
|
||||||
var n = x
|
var n = x
|
||||||
|
|
|
||||||
|
|
@ -4828,16 +4828,8 @@ While macros enable advanced compile-time code transformations, they
|
||||||
cannot change Nim's syntax. However, this is no real restriction because
|
cannot change Nim's syntax. However, this is no real restriction because
|
||||||
Nim's syntax is flexible enough anyway.
|
Nim's syntax is flexible enough anyway.
|
||||||
|
|
||||||
To write macros, one needs to know how the Nim concrete syntax is converted
|
Debug Example
|
||||||
to an AST.
|
-------------
|
||||||
|
|
||||||
There are two ways to invoke a macro:
|
|
||||||
(1) invoking a macro like a procedure call (`expression macros`)
|
|
||||||
(2) invoking a macro with the special ``macrostmt`` syntax (`statement macros`)
|
|
||||||
|
|
||||||
|
|
||||||
Expression Macros
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
The following example implements a powerful ``debug`` command that accepts a
|
The following example implements a powerful ``debug`` command that accepts a
|
||||||
variable number of arguments:
|
variable number of arguments:
|
||||||
|
|
@ -4943,22 +4935,23 @@ However, the symbols ``write``, ``writeLine`` and ``stdout`` are already bound
|
||||||
and are not looked up again. As the example shows, ``bindSym`` does work with
|
and are not looked up again. As the example shows, ``bindSym`` does work with
|
||||||
overloaded symbols implicitly.
|
overloaded symbols implicitly.
|
||||||
|
|
||||||
|
Case-Of Macro
|
||||||
|
-------------
|
||||||
|
|
||||||
Statement Macros
|
In Nim it is possible to have a macro with the syntax of a *case-of*
|
||||||
----------------
|
expression just with the difference that all of branches are passed to
|
||||||
|
and processed by the macro implementation. It is then up the macro
|
||||||
Statement macros are defined just as expression macros. However, they are
|
implementation to transform the *of-branches* into a valid Nim
|
||||||
invoked by an expression following a colon.
|
statement. The following example should show how this feature could be
|
||||||
|
used for a lexical analyzer.
|
||||||
The following example outlines a macro that generates a lexical analyzer from
|
|
||||||
regular expressions:
|
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
import macros
|
import macros
|
||||||
|
|
||||||
macro case_token(n: untyped): untyped =
|
macro case_token(args: varargs[untyped]): untyped =
|
||||||
|
echo args.treeRepr
|
||||||
# creates a lexical analyzer from regular expressions
|
# creates a lexical analyzer from regular expressions
|
||||||
# ... (implementation is an exercise for the reader :-)
|
# ... (implementation is an exercise for the reader ;-)
|
||||||
discard
|
discard
|
||||||
|
|
||||||
case_token: # this colon tells the parser it is a macro statement
|
case_token: # this colon tells the parser it is a macro statement
|
||||||
|
|
@ -5001,8 +4994,8 @@ This is a simple syntactic transformation into:
|
||||||
proc p() = discard
|
proc p() = discard
|
||||||
|
|
||||||
|
|
||||||
For loop macros
|
For Loop Macro
|
||||||
---------------
|
--------------
|
||||||
|
|
||||||
A macro that takes as its only input parameter an expression of the special
|
A macro that takes as its only input parameter an expression of the special
|
||||||
type ``system.ForLoopStmt`` can rewrite the entirety of a ``for`` loop:
|
type ``system.ForLoopStmt`` can rewrite the entirety of a ``for`` loop:
|
||||||
|
|
@ -5197,8 +5190,10 @@ Once bound, type params can appear in the rest of the proc signature:
|
||||||
declareVariableWithType int, 42
|
declareVariableWithType int, 42
|
||||||
|
|
||||||
|
|
||||||
Overload resolution can be further influenced by constraining the set of
|
Overload resolution can be further influenced by constraining the set
|
||||||
types that will match the type param:
|
of types that will match the type param. This works in practice to
|
||||||
|
attaching attributes to types via templates. The constraint can be a
|
||||||
|
concrete type or a type class.
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
:test: "nim c $1"
|
:test: "nim c $1"
|
||||||
|
|
@ -5211,14 +5206,34 @@ types that will match the type param:
|
||||||
when false:
|
when false:
|
||||||
var s = string.maxval # error, maxval is not implemented for string
|
var s = string.maxval # error, maxval is not implemented for string
|
||||||
|
|
||||||
The constraint can be a concrete type or a type class.
|
template isNumber(t: typedesc[object]): string = "Don't think so."
|
||||||
|
template isNumber(t: typedesc[SomeInteger]): string = "Yes!"
|
||||||
|
template isNumber(t: typedesc[SomeFloat]): string = "Maybe, could be NaN."
|
||||||
|
|
||||||
|
echo "is int a number? ", isNumber(int)
|
||||||
|
echo "is float a number? ", isNumber(float)
|
||||||
|
echo "is RootObj a number? ", isNumber(RootObj)
|
||||||
|
|
||||||
|
Passing ``typedesc`` almost identical, just with the differences that
|
||||||
|
the macro is not instantiated generically. The type expression is
|
||||||
|
simply passed as a ``NimNode`` to the macro, like everything else.
|
||||||
|
|
||||||
|
.. code-block:: nim
|
||||||
|
|
||||||
|
import macros
|
||||||
|
|
||||||
|
macro forwardType(arg: typedesc): typedesc =
|
||||||
|
# ``arg`` is of type ``NimNode``
|
||||||
|
let tmp: NimNode = arg
|
||||||
|
result = tmp
|
||||||
|
|
||||||
|
var tmp: forwardType(int)
|
||||||
|
|
||||||
typeof operator
|
typeof operator
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
**Note**: ``typeof(x)`` can also be written as ``type(x)`` but ``type(x)``
|
**Note**: ``typeof(x)`` can for historical reasons also be written as
|
||||||
is discouraged.
|
``type(x)`` but ``type(x)`` is discouraged.
|
||||||
|
|
||||||
You can obtain the type of a given expression by constructing a ``typeof``
|
You can obtain the type of a given expression by constructing a ``typeof``
|
||||||
value from it (in many other languages this is known as the `typeof`:idx:
|
value from it (in many other languages this is known as the `typeof`:idx:
|
||||||
|
|
@ -6989,5 +7004,3 @@ Threads and exceptions
|
||||||
The interaction between threads and exceptions is simple: A *handled* exception
|
The interaction between threads and exceptions is simple: A *handled* exception
|
||||||
in one thread cannot affect any other thread. However, an *unhandled* exception
|
in one thread cannot affect any other thread. However, an *unhandled* exception
|
||||||
in one thread terminates the whole *process*!
|
in one thread terminates the whole *process*!
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ template selectType(x: int): type =
|
||||||
template simpleTypeTempl: type =
|
template simpleTypeTempl: type =
|
||||||
string
|
string
|
||||||
|
|
||||||
macro typeFromMacro: type = string
|
macro typeFromMacro: type = bindSym"string"
|
||||||
|
|
||||||
# The tests below check that the result variable of the
|
# The tests below check that the result variable of the
|
||||||
# selected type matches the literal types in the code:
|
# selected type matches the literal types in the code:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue