Nim: Macro: bool is converted to int inside `quote do`

Created on 20 Mar 2018  路  9Comments  路  Source: nim-lang/Nim

When creating a boolean in a macro and passing it to other macro, it is transformed to a static[int]

import macros

macro fails(b: static[bool]): untyped =
  echo b
  result = newStmtList()

macro works(b: static[int]): untyped =
  echo b
  result = newStmtList()

macro foo(): untyped =

  var b = false

  ## Fails
  result = quote do:
    fails(`b`)

  ## Works
  # result = quote do:
  #   works(`b`)

foo()
Error: type mismatch: got <static[int literal(0)](0)>
but expected one of:
macro fails(b: static[bool]): untyped

expression: fails(0)

I would expect a conversion to static[bool] instead of static[int]

originally posted in #10719

It seems that passing true/false literals within macros gets them converted to integer literals internally, which further results in errors if you try to use them as booleans. This worked as expected in 0.18.0.

Example

import macros

macro someMacro*(): typed =
  template tmpl(boolean) =
    when boolean:
      echo "it's true!"
  result = getAst(tmpl(true))

someMacro()

Current Output

test.nim(9, 10) template/generic instantiation of `someMacro` from here
test.nim(7, 23) Error: type mismatch: got <int literal(1)> but expected 'bool'
High Priority Macros quote do

Most helpful comment

I did some investigations. So first of all, enum values are atcually represented as integer literals, but with a type that says that they are actually enum values. So there is nothing wrong with that. Except that the type is lost in translation.

For getAst a macro call is generated here:

https://github.com/nim-lang/Nim/blob/417d27c54406ac29ab00ac0b459aa7546af4b83a/compiler/vmgen.nim#L1289

The argument is passed as an immediate int here:

https://github.com/nim-lang/Nim/blob/417d27c54406ac29ab00ac0b459aa7546af4b83a/compiler/vmgen.nim#L1989

The type field is not serialized.

In the vm the register in translated back into a node here:

https://github.com/nim-lang/Nim/blob/417d27c54406ac29ab00ac0b459aa7546af4b83a/compiler/vm.nim#L1118

This node is an integer literal node without any type information (nil).
Then in semConstExpr鈫抯emExprWithType鈫抯emExpr the missing type is set to be actually of type int (tyInt).

https://github.com/nim-lang/Nim/blob/417d27c54406ac29ab00ac0b459aa7546af4b83a/compiler/semexprs.nim#L2148

This crashes then in forceBool, because an integer is not a boolean.

All 9 comments

Couple of updates.

  • Enums are also affected
  • This is not only when passing macro to macro but passing from compile-time to runtime:
    Test case:
import macros


macro foo(): untyped =
  result = quote do: `littleEndian`


doAssert littleEndian == foo() # Error: type mismatch: got <Endianness, int literal(0)>

Test case:

import macros

macro eqSym(x, y: untyped): untyped =
  let eq = $x == $y # Unfortunately eqIdent compares to string.
  result = quote do: `eq`

var r, a, b: int

template fma(result: var int, a, b: int, op: untyped) =
  # fused multiple-add
  when eqSym(op, `+=`):
    echo "+="
  else:
    echo "+"

fma(r, a, b, `+=`)

Impacted lib: https://github.com/status-im/nim-stint/blob/bd734b6845342feff426a849240f478020e6ee85/stint/private/uint_mul.nim#L83-L109

Workarounds:

Instead of

result = quote do: `eq`
  1. either use result = newLit eq

  2. or use

result = quote do: Endianness(`eq`)

Turns out the regression was unrelated to this bug.

I just hit this bug coming from another case;
unfortunately the workaround (result = newLit eq) doesn't work so well in my case where the expression inside quote do can be complex (and turning it into AST via newCall etc, is undesirable)

#[
KEY bool converted to int in quote do
https://github.com/nim-lang/Nim/issues/7375
]#
import macros
macro foo(): untyped=
  let isReq = true
  result = quote do:
    echo `isReq`
    # or, complexExpression(`isReq`)
foo()

The issue is not specific to quote, it looks like getAst is also affected:

import macros

template test(boolArg: bool) =
  static:
    echo "type is: ", type(boolArg)
  let x: bool = boolArg # compile error here, because boolArg became an int

macro testWrapped1(boolArg: bool): untyped =
  # forwarding boolArg directly works
  result = getAst(test(boolArg))

macro testWrapped2(boolArg: bool): untyped =
  # forwarding boolArg via a local variable also works
  let b = boolArg
  result = getAst(test(b))

macro testWrapped3(boolArg: bool): untyped =
  # but using a literal `true` as a local variable will be converted to int
  let b = true
  result = getAst(test(b))

test(true) # ok
testWrapped1(true) # ok
testWrapped2(true) # ok
testWrapped3(true) # error

I would say it is an regression, because the godot bindings were relying on getAst with local bools. The work-around with wrapping them in newLit also works with getAst.

The issue here is that -
https://github.com/nim-lang/Nim/blob/a1e268e3dccdde4df9b11a0ee87971e1143fbb43/lib/system.nim#L42-L44
compiler converts enum fields to integers in the semantic phase -
https://github.com/nim-lang/Nim/blob/a1e268e3dccdde4df9b11a0ee87971e1143fbb43/compiler/semfold.nim#L551-L552
So if at compile time - the boolean value gets converted to integer value, then at runtime there is no boolean value, only integer value. (Internally in the compiler there is no notion of a bool, .intVal is checked for being 0 or 1, similarly VM does not know anything about bool)

I would like to reference my new RFC here, because it would be affected:
https://github.com/nim-lang/Nim/issues/10412
Effectively the pattern to use newLit would be enforced.

I did some investigations. So first of all, enum values are atcually represented as integer literals, but with a type that says that they are actually enum values. So there is nothing wrong with that. Except that the type is lost in translation.

For getAst a macro call is generated here:

https://github.com/nim-lang/Nim/blob/417d27c54406ac29ab00ac0b459aa7546af4b83a/compiler/vmgen.nim#L1289

The argument is passed as an immediate int here:

https://github.com/nim-lang/Nim/blob/417d27c54406ac29ab00ac0b459aa7546af4b83a/compiler/vmgen.nim#L1989

The type field is not serialized.

In the vm the register in translated back into a node here:

https://github.com/nim-lang/Nim/blob/417d27c54406ac29ab00ac0b459aa7546af4b83a/compiler/vm.nim#L1118

This node is an integer literal node without any type information (nil).
Then in semConstExpr鈫抯emExprWithType鈫抯emExpr the missing type is set to be actually of type int (tyInt).

https://github.com/nim-lang/Nim/blob/417d27c54406ac29ab00ac0b459aa7546af4b83a/compiler/semexprs.nim#L2148

This crashes then in forceBool, because an integer is not a boolean.

For when this is later fixed; here is another bit of sample code to test against. Very likely the same bug involving a template called from macro:

https://forum.nim-lang.org/t/4995

@JohanAD just make sure all arguments to the template you want to call with getAst arg of type NimNode.

macro joe(body: untyped): untyped =
  result = newStmtList()
  let t = newLit(true)
  let temp = getAst test(t)
  result.add(temp)
Was this page helpful?
0 / 5 - 0 ratings

Related issues

hlaaftana picture hlaaftana  路  3Comments

zzz125 picture zzz125  路  4Comments

ghost picture ghost  路  4Comments

SolitudeSF picture SolitudeSF  路  3Comments

capocasa picture capocasa  路  3Comments