From 159c06e0451f85842b2168886565fa57496a2c68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20D=C3=B6ring?= Date: Tue, 30 Mar 2021 02:06:51 +0200 Subject: [PATCH] unify tuple expressions (#13793) * unify tuple expressions * fix test * fix test * apply feedback * Handle empty tuples * Fix rendering named unary tuple * Protect static NimNode against stripping * Slightly less hacky * Revert "Slightly less hacky" This reverts commit 170c5aec0addc029f637afbc948700ca006b7942. * Slightly less hacky * Cleanup * Fix test * Fix another test * Add condsym * Rebase fallout * changelog: Move from compiler changes to language changes * Add stricter tests * Add empty tuple example to doc/astspec * Fix test Co-authored-by: Clyybber --- changelog.md | 3 +++ compiler/condsyms.nim | 1 + compiler/parser.nim | 15 ++++++----- compiler/renderer.nim | 2 +- compiler/vm.nim | 6 +++++ doc/astspec.txt | 45 ++++++++++++++++++++++++++++--- tests/ast_pattern_matching.nim | 4 +-- tests/astspec/tastspec.nim | 36 ++++++++++++++++++++----- tests/compiler/tprefixmatches.nim | 2 +- tests/lexer/tunary_minus.nim | 2 +- tests/macros/tdumpast.nim | 31 +++++++++++++++++++++ tests/parser/tpostexprblocks.nim | 6 ++--- tests/parser/ttypemodifiers.nim | 15 +++++------ 13 files changed, 136 insertions(+), 32 deletions(-) diff --git a/changelog.md b/changelog.md index 2fbff8b41b14b..7452e318228fd 100644 --- a/changelog.md +++ b/changelog.md @@ -274,6 +274,9 @@ - Custom numeric literals (e.g. `-128'bignum`) are now supported. +- Tuple expressions are now parsed consistently as + `nnkTupleConstr` node. Will affect macros expecting nodes to be of `nnkPar`. + ## Compiler changes diff --git a/compiler/condsyms.nim b/compiler/condsyms.nim index 8d11dae7c52de..9ba97a8edac53 100644 --- a/compiler/condsyms.nim +++ b/compiler/condsyms.nim @@ -131,3 +131,4 @@ proc initDefines*(symbols: StringTableRef) = defineSymbol("nimHasHintAsError") defineSymbol("nimHasSpellSuggest") defineSymbol("nimHasCustomLiterals") + defineSymbol("nimHasUnifiedTuple") diff --git a/compiler/parser.nim b/compiler/parser.nim index b9a6ffb8ccdb5..83ea4cbdcd92e 100644 --- a/compiler/parser.nim +++ b/compiler/parser.nim @@ -421,10 +421,9 @@ proc exprColonEqExprListAux(p: var Parser, endTok: TokType, result: PNode) = var a = exprColonEqExpr(p) result.add(a) if p.tok.tokType != tkComma: break - getTok(p) - # (1,) produces a tuple expression - if endTok == tkParRi and p.tok.tokType == tkParRi and result.kind == nkPar: + elif result.kind == nkPar: result.transitionSonsKind(nkTupleConstr) + getTok(p) skipComment(p, a) optPar(p) eat(p, endTok) @@ -584,7 +583,10 @@ proc parsePar(p: var Parser): PNode = semiStmtList(p, result) elif p.tok.tokType == tkCurlyDotLe: result.add(parseStmtPragma(p)) - elif p.tok.tokType != tkParRi: + elif p.tok.tokType == tkParRi: + # Empty tuple '()' + result.transitionSonsKind(nkTupleConstr) + else: var a = simpleExpr(p) if p.tok.tokType == tkDo: result = postExprBlocks(p, a) @@ -605,13 +607,14 @@ proc parsePar(p: var Parser): PNode = semiStmtList(p, result) else: a = colonOrEquals(p, a) + if a.kind == nkExprColonExpr: + result.transitionSonsKind(nkTupleConstr) result.add(a) if p.tok.tokType == tkComma: getTok(p) skipComment(p, a) # (1,) produces a tuple expression: - if p.tok.tokType == tkParRi: - result.transitionSonsKind(nkTupleConstr) + result.transitionSonsKind(nkTupleConstr) # progress guaranteed while p.tok.tokType != tkParRi and p.tok.tokType != tkEof: var a = exprColonEqExpr(p) diff --git a/compiler/renderer.nim b/compiler/renderer.nim index 9a599f6fc72b1..b9ec4818531ba 100644 --- a/compiler/renderer.nim +++ b/compiler/renderer.nim @@ -1182,7 +1182,7 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) = of nkTupleConstr: put(g, tkParLe, "(") gcomma(g, n, c) - if n.len == 1: put(g, tkComma, ",") + if n.len == 1 and n[0].kind != nkExprColonExpr: put(g, tkComma, ",") put(g, tkParRi, ")") of nkCurly: put(g, tkCurlyLe, "{") diff --git a/compiler/vm.nim b/compiler/vm.nim index d97d2d6e6c885..baaf0f14ef2bd 100644 --- a/compiler/vm.nim +++ b/compiler/vm.nim @@ -2228,6 +2228,12 @@ proc prepareVMValue(arg: PNode): PNode = if arg.kind in nkLiterals: return arg + if arg.kind == nkExprColonExpr and arg[0].typ != nil and + arg[0].typ.sym != nil and arg[0].typ.sym.magic == mPNimrodNode: + # Poor mans way of protecting static NimNodes + # XXX: Maybe we need a nkNimNode? + return arg + result = copyNode(arg) if arg.kind == nkTupleConstr: for child in arg: diff --git a/doc/astspec.txt b/doc/astspec.txt index b4b8b34b57a12..92212bf8cb5a4 100644 --- a/doc/astspec.txt +++ b/doc/astspec.txt @@ -329,19 +329,56 @@ AST: Parentheses ----------- -Parentheses for affecting operator precedence or tuple construction -are built with the ``nnkPar`` node. +Parentheses for affecting operator precedence use the ``nnkPar`` node. Concrete syntax: .. code-block:: nim - (1, 2, (3)) + (a + b) * c AST: .. code-block:: nim - nnkPar(nnkIntLit(1), nnkIntLit(2), nnkPar(nnkIntLit(3))) + nnkInfix(nnkIdent("*"), + nnkPar( + nnkInfix(nnkIdent("+"), nnkIdent("a"), nnkIdent("b"))), + nnkIdent("c")) +Tuple Constructors +------------------ + +Nodes for tuple construction are built with the ``nnkTupleConstr`` node. + +Concrete syntax: + +.. code-block:: nim + (1, 2, 3) + (a: 1, b: 2, c: 3) + () + +AST: + +.. code-block:: nim + nnkTupleConstr(nnkIntLit(1), nnkIntLit(2), nnkIntLit(3)) + nnkTupleConstr( + nnkExprColonExpr(nnkIdent("a"), nnkIntLit(1)), + nnkExprColonExpr(nnkIdent("b"), nnkIntLit(2)), + nnkExprColonExpr(nnkIdent("c"), nnkIntLit(3))) + +Since the one tuple would be syntactically identical to parentheses +with an expression in them, the parser expects a trailing comma for +them. For tuple constructors with field names, this is not necessary. + +.. code-block:: nim + (1,) + (a: 1) + +AST: + +.. code-block:: nim + nnkTupleConstr(nnkIntLit(1)) + nnkTupleConstr( + nnkExprColonExpr(nnkIdent("a"), nnkIntLit(1))) Curly braces ------------ diff --git a/tests/ast_pattern_matching.nim b/tests/ast_pattern_matching.nim index c08234b9e38b7..d209493b6c090 100644 --- a/tests/ast_pattern_matching.nim +++ b/tests/ast_pattern_matching.nim @@ -133,8 +133,8 @@ proc matchLengthKind*(arg: NimNode; kind: NimNodeKind; length: int): MatchingErr matchLengthKind(arg, {kind}, length) proc matchValue(arg: NimNode; kind: set[NimNodeKind]; value: SomeInteger): MatchingError {.compileTime.} = - let kindFail = not(kind.card == 0 or arg.kind in kind) - let valueFail = arg.intVal != int(value) + template kindFail: bool = not(kind.card == 0 or arg.kind in kind) + template valueFail: bool = arg.intVal != int(value) if kindFail or valueFail: result.node = arg result.kind = WrongKindValue diff --git a/tests/astspec/tastspec.nim b/tests/astspec/tastspec.nim index e2cfed2776164..33a245b1bb110 100644 --- a/tests/astspec/tastspec.nim +++ b/tests/astspec/tastspec.nim @@ -327,19 +327,43 @@ static: testArrayAccessOperator(x[y]) - - ## Parentheses scope: - let ast = myquote: - (1, 2, (3)) + (a + b) * c ast.matchAst: - of nnkPar(nnkIntLit(intVal = 1), nnkIntLit(intVal = 2), nnkPar(nnkIntLit(intVal = 3))): - echo "ok" + of nnkInfix(ident"*", nnkPar(nnkInfix(ident"+", ident"a", ident"b")), ident"c"): + echo "parentheses ok" + ## Tuple Constructors + + scope: + let ast = myquote: + (1, 2, 3) + (a: 1, b: 2, c: 3) + (1,) + (a: 1) + () + + for it in ast: + echo it.lispRepr + it.matchAst: + of nnkTupleConstr(nnkIntLit(intVal = 1), nnkIntLit(intVal = 2), nnkIntLit(intVal = 3)): + echo "simple tuple ok" + of nnkTupleConstr( + nnkExprColonExpr(ident"a", nnkIntLit(intVal = 1)), + nnkExprColonExpr(ident"b", nnkIntLit(intVal = 2)), + nnkExprColonExpr(ident"c", nnkIntLit(intVal = 3)) + ): + echo "named tuple ok" + of nnkTupleConstr(nnkIntLit(intVal = 1)): + echo "one tuple ok" + of nnkTupleConstr(nnkExprColonExpr(ident"a", nnkIntLit(intVal = 1))): + echo "named one tuple ok" + of nnkTupleConstr(): + echo "empty tuple ok" ## Curly braces diff --git a/tests/compiler/tprefixmatches.nim b/tests/compiler/tprefixmatches.nim index a89a6f613aa7f..6a3186729886c 100644 --- a/tests/compiler/tprefixmatches.nim +++ b/tests/compiler/tprefixmatches.nim @@ -5,7 +5,7 @@ macro check(val, body: untyped): untyped = result = newStmtList() expectKind body, nnkStmtList for b in body: - expectKind b, nnkPar + expectKind b, nnkTupleConstr expectLen b, 2 let p = b[0] let s = b[1] diff --git a/tests/lexer/tunary_minus.nim b/tests/lexer/tunary_minus.nim index 87b3cb52dcfd9..1641e918c39c9 100644 --- a/tests/lexer/tunary_minus.nim +++ b/tests/lexer/tunary_minus.nim @@ -45,7 +45,7 @@ template main = doAssert lispReprStr([-1]) == """(Bracket (IntLit -1))""" doAssert (-1, 2)[0] == minusOne: "unable to handle negatives after parenthesis" - doAssert lispReprStr((-1, 2)) == """(Par (IntLit -1) (IntLit 2))""" + doAssert lispReprStr((-1, 2)) == """(TupleConstr (IntLit -1) (IntLit 2))""" proc x(): int = var a = 1;-1 # the -1 should act as the return value doAssert x() == minusOne: diff --git a/tests/macros/tdumpast.nim b/tests/macros/tdumpast.nim index b9d224ab81ec0..b6bcbe8f04dfc 100644 --- a/tests/macros/tdumpast.nim +++ b/tests/macros/tdumpast.nim @@ -48,3 +48,34 @@ macro fun3(): untyped = int | float | array | seq | object | ptr | pointer | float32 doAssert n.repr == "int | float | array | seq | object | ptr | pointer | float32", n.repr fun3() + +macro fun4() = + let n = quote do: + (a: 1) + doAssert n.repr == "(a: 1)", n.repr +fun4() + +# nkTupleConstr vs nkPar tests: +block: # lispRepr + macro lispRepr2(a: untyped): string = newLit a.lispRepr + + doAssert lispRepr2(()) == """(TupleConstr)""" + doAssert lispRepr2((a: 1)) == """(TupleConstr (ExprColonExpr (Ident "a") (IntLit 1)))""" + doAssert lispRepr2((a: 1, b: 2)) == """(TupleConstr (ExprColonExpr (Ident "a") (IntLit 1)) (ExprColonExpr (Ident "b") (IntLit 2)))""" + doAssert lispRepr2((1,)) == """(TupleConstr (IntLit 1))""" + doAssert lispRepr2((1, 2)) == """(TupleConstr (IntLit 1) (IntLit 2))""" + doAssert lispRepr2((1, 2, 3.0)) == """(TupleConstr (IntLit 1) (IntLit 2) (FloatLit 3.0))""" + doAssert lispRepr2((1)) == """(Par (IntLit 1))""" + doAssert lispRepr2((1+2)) == """(Par (Infix (Ident "+") (IntLit 1) (IntLit 2)))""" + +block: # repr + macro repr2(a: untyped): string = newLit a.repr + + doAssert repr2(()) == "()" + doAssert repr2((a: 1)) == "(a: 1)" + doAssert repr2((a: 1, b: 2)) == "(a: 1, b: 2)" + doAssert repr2((1,)) == "(1,)" + doAssert repr2((1, 2)) == "(1, 2)" + doAssert repr2((1, 2, 3.0)) == "(1, 2, 3.0)" + doAssert repr2((1)) == "(1)" + doAssert repr2((1+2)) == "(1 + 2)" diff --git a/tests/parser/tpostexprblocks.nim b/tests/parser/tpostexprblocks.nim index bc10a72e2e21d..d272c712f5ff9 100644 --- a/tests/parser/tpostexprblocks.nim +++ b/tests/parser/tpostexprblocks.nim @@ -464,7 +464,7 @@ StmtList DiscardStmt Empty OfBranch - Par + TupleConstr Ident "a" Ident "b" StmtList @@ -476,7 +476,7 @@ StmtList DiscardStmt Empty ElifBranch - Par + TupleConstr Ident "a" Ident "b" StmtList @@ -488,7 +488,7 @@ StmtList DiscardStmt Empty ExceptBranch - Par + TupleConstr Ident "a" Ident "b" StmtList diff --git a/tests/parser/ttypemodifiers.nim b/tests/parser/ttypemodifiers.nim index 2c322b44ba0d5..9a1ccb1a5e156 100644 --- a/tests/parser/ttypemodifiers.nim +++ b/tests/parser/ttypemodifiers.nim @@ -21,7 +21,7 @@ StmtList Ident "PtrTuple" Empty PtrTy - Par + TupleConstr Ident "int" Ident "string" TypeDef @@ -43,14 +43,14 @@ StmtList Ident "RefTupleType" Empty RefTy - Par + TupleConstr Ident "int" Ident "string" TypeDef Ident "RefTupleVars" Empty RefTy - Par + TupleConstr Ident "a" Ident "b" TypeDef @@ -80,7 +80,7 @@ StmtList Empty Command Ident "static" - Par + TupleConstr Ident "int" Ident "string" TypeDef @@ -155,7 +155,7 @@ StmtList Empty Command Ident "type" - Par + TupleConstr Ident "a" Ident "b" TypeDef @@ -163,7 +163,7 @@ StmtList Empty Command Ident "type" - Par + TupleConstr Ident "int" Ident "string" TypeDef @@ -287,7 +287,7 @@ StmtList IdentDefs Ident "refTuple2" RefTy - Par + TupleConstr Ident "int" Ident "string" Empty @@ -524,4 +524,3 @@ dumpTree: static: staticStmtList1 staticStmtList2 -