From be49935753f0e37ae8e04fb53111e6f116c63f47 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Wed, 21 Feb 2024 09:38:47 +0100 Subject: Destructuring improvements (#3463) --- tests/src/tests.rs | 2 +- tests/typ/bugs/parenthesized.typ | 86 ++++++++++++++++++++++++++++++++++++ tests/typ/compiler/backtracking.typ | 33 ++++++++++++++ tests/typ/compiler/block.typ | 3 +- tests/typ/compiler/call.typ | 3 +- tests/typ/compiler/closure.typ | 28 ++++++------ tests/typ/compiler/dict.typ | 3 +- tests/typ/compiler/embedded-expr.typ | 4 +- tests/typ/compiler/for.typ | 23 ++++++---- tests/typ/compiler/let.typ | 54 ++++++++++++++-------- tests/typ/compiler/ops.typ | 32 ++++++++++++++ tests/typ/compiler/spread.typ | 8 ++-- tests/typ/compute/foundations.typ | 2 +- 13 files changed, 227 insertions(+), 54 deletions(-) create mode 100644 tests/typ/bugs/parenthesized.typ create mode 100644 tests/typ/compiler/backtracking.typ (limited to 'tests') diff --git a/tests/src/tests.rs b/tests/src/tests.rs index 7da2be86..09baa190 100644 --- a/tests/src/tests.rs +++ b/tests/src/tests.rs @@ -884,7 +884,7 @@ fn print_annotation( let start_col = 1 + source.byte_to_column(range.start).unwrap(); let end_line = 1 + line + source.byte_to_line(range.end).unwrap(); let end_col = 1 + source.byte_to_column(range.end).unwrap(); - write!(output, "{start_line}:{start_col}-{end_line}:{end_col}: ").unwrap(); + write!(output, "{start_line}:{start_col}-{end_line}:{end_col} ").unwrap(); } writeln!(output, "{text}").unwrap(); } diff --git a/tests/typ/bugs/parenthesized.typ b/tests/typ/bugs/parenthesized.typ new file mode 100644 index 00000000..99488a67 --- /dev/null +++ b/tests/typ/bugs/parenthesized.typ @@ -0,0 +1,86 @@ +// Ref: false +// Test bugs related to destructuring and parenthesized parsing. + +--- +// https://github.com/typst/typst/issues/1338 +#let foo = "foo" +#let bar = "bar" +// Error: 8-9 expected expression, found underscore +// Error: 16-17 expected expression, found underscore +#(foo: _, bar: _) + +--- +// https://github.com/typst/typst/issues/1342 +// Error: 5-8 expected named or keyed pair, found identifier +// Error: 10-13 expected named or keyed pair, found identifier +#(: foo, bar) + +--- +// https://github.com/typst/typst/issues/1351 +// Error: 17-22 expected pattern, found string +#let foo((test: "bar")) = {} + +--- +// https://github.com/typst/typst/issues/3014 +// Error: 8-17 expected expression, found named pair +#(box, fill: red) + +--- +// https://github.com/typst/typst/issues/3144 +#let f(a: 10) = a(1) + 1 +#test(f(a: _ => 5), 6) + +--- +// Error: 18-20 missing argument: pattern parameter +#let f(a: 10) = a() + 1 +#f(a: _ => 5) + +--- +// This wasn't allowed. +#let ((x)) = 1 +#test(x, 1) + +--- +// This also wasn't allowed. +#let ((a, b)) = (1, 2) +#test(a, 1) +#test(b, 2) + +--- +// This was unintentionally allowed ... +// Error: 9 expected equals sign +#let (a) + +--- +// ... where this wasn't. +// Error: 12 expected equals sign +#let (a, b) + +--- +// This wasn't allowed before the bug fix ... +#let f(..) = {} +#f(arg: 1) + +--- +// ... but this was. +#let f(..x) = {} +#f(arg: 1) + +--- +// Here, `best` was accessed as a variable, where it shouldn't have. +#{ + (best: _) = (best: "brr") +} + +--- +// Same here. +#{ + let array = (1, 2, 3, 4) + (test: array.at(1), best: _) = (test: "baz", best: "brr") + test(array, (1, "baz", 3, 4)) +} + +--- +// Here, `a` is not duplicate, where it was previously identified as one. +#let f((a: b), (c,), a) = (a, b, c) +#test(f((a: 1), (2,), 3), (3, 1, 2)) diff --git a/tests/typ/compiler/backtracking.typ b/tests/typ/compiler/backtracking.typ new file mode 100644 index 00000000..9c3ab8ec --- /dev/null +++ b/tests/typ/compiler/backtracking.typ @@ -0,0 +1,33 @@ +// Ensure that parser backtracking doesn't lead to exponential time consumption. +// If this regresses, the test suite will not terminate, which is a bit +// unfortunate compared to a good error, but at least we know something is up. +// +// Ref: false + +--- +#{ + let s = "(x: 1) => x" + let pat = "(x: {}) => 1 + x()" + for _ in range(50) { + s = pat.replace("{}", s) + } + test(eval(s)(), 51) +} + +--- +#{ + let s = "(x) = 1" + let pat = "(x: {_}) = 1" + for _ in range(100) { + s = pat.replace("_", s) + } + // Error: 8-9 cannot destructure integer + eval(s) +} + +--- +// Test whitespace after memoized part. +#( (x: () => 1 ) => 1 ) +// ------- +// This is memoized and we want to ensure that whitespace after this +// is handled correctly. diff --git a/tests/typ/compiler/block.typ b/tests/typ/compiler/block.typ index 0580acd2..48c9fefc 100644 --- a/tests/typ/compiler/block.typ +++ b/tests/typ/compiler/block.typ @@ -126,8 +126,7 @@ // Should output `3`. #{ - // Error: 6 expected identifier - // Error: 10 expected block + // Error: 7-10 expected pattern, found string for "v" // Error: 8 expected keyword `in` diff --git a/tests/typ/compiler/call.typ b/tests/typ/compiler/call.typ index e48eabfd..0c225a1c 100644 --- a/tests/typ/compiler/call.typ +++ b/tests/typ/compiler/call.typ @@ -75,8 +75,7 @@ #f[1](2) --- -// Error: 7 expected expression -// Error: 8 expected expression +// Error: 7-8 unexpected colon #func(:) // Error: 10-12 unexpected end of block comment diff --git a/tests/typ/compiler/closure.typ b/tests/typ/compiler/closure.typ index 85e9dbe2..ef4e7df0 100644 --- a/tests/typ/compiler/closure.typ +++ b/tests/typ/compiler/closure.typ @@ -171,25 +171,26 @@ #let f((k: a, b), c: 3, (d,)) = (a, b, c, d) #test(f((k: 1, b: 2), (4,)), (1, 2, 3, 4)) -// Error: 22-23 duplicate parameter: a -#let f((a: b), (c,), a) = none - -// Error: 8-14 expected identifier, found array +// Error: 8-14 expected identifier, found destructuring pattern #let f((a, b): 0) = none -// Error: 10-19 expected identifier, found destructuring pattern +// Error: 10-19 expected pattern, found array #let f(..(a, b: c)) = none -// Error: 10-16 expected identifier, found array +// Error: 10-16 expected pattern, found array #let f(..(a, b)) = none -// Error: 10-19 expected identifier, found destructuring pattern -#let f(..(a, b: c)) = none - --- // Error: 11-12 duplicate parameter: x #let f(x, x) = none +--- +// Error: 21 expected comma +// Error: 22-23 expected pattern, found integer +// Error: 24-25 unexpected plus +// Error: 26-27 expected pattern, found integer +#let f = (x: () => 1 2 + 3) => 4 + --- // Error: 14-15 duplicate parameter: a // Error: 23-24 duplicate parameter: b @@ -201,17 +202,18 @@ #let f(a, ..a) = none --- -// Error: 7-17 expected identifier, named pair or argument sink, found keyed pair +// Error: 7-14 expected pattern, found string #((a, "named": b) => none) --- -// Error: 10-15 expected identifier, found string +// Error: 10-15 expected pattern, found string #let foo("key": b) = key --- -// Error: 10-14 expected identifier, found `none` +// Error: 10-14 expected pattern, found `none` +// Hint: 10-14 keyword `none` is not allowed as an identifier; try `none_` instead #let foo(none: b) = key --- -// Error: 11 expected comma +// Error: 10-11 expected identifier, found underscore #let foo(_: 3) = none diff --git a/tests/typ/compiler/dict.typ b/tests/typ/compiler/dict.typ index 8a5be5cd..552b243c 100644 --- a/tests/typ/compiler/dict.typ +++ b/tests/typ/compiler/dict.typ @@ -110,7 +110,6 @@ // Identified as dictionary due to initial colon. // The boolean key is allowed for now since it will only cause an error at the evaluation stage. // Error: 4-5 expected named or keyed pair, found integer -// Error: 5 expected comma // Error: 17 expected expression #(:1 b:"", true:) @@ -152,7 +151,7 @@ --- // Error: 7-10 expected identifier, found group -// Error: 12-14 expected identifier, found integer +// Error: 12-14 expected pattern, found integer #let ((a): 10) = "world" --- diff --git a/tests/typ/compiler/embedded-expr.typ b/tests/typ/compiler/embedded-expr.typ index c95184e4..ee6e07f9 100644 --- a/tests/typ/compiler/embedded-expr.typ +++ b/tests/typ/compiler/embedded-expr.typ @@ -2,13 +2,13 @@ // Ref: false --- -// Error: 6-8 expected identifier, found keyword `as` +// Error: 6-8 expected pattern, found keyword `as` // Hint: 6-8 keyword `as` is not allowed as an identifier; try `as_` instead #let as = 1 + 2 --- #{ - // Error: 7-9 expected identifier, found keyword `as` + // Error: 7-9 expected pattern, found keyword `as` // Hint: 7-9 keyword `as` is not allowed as an identifier; try `as_` instead let as = 10 } diff --git a/tests/typ/compiler/for.typ b/tests/typ/compiler/for.typ index 64b5a1d4..392dd676 100644 --- a/tests/typ/compiler/for.typ +++ b/tests/typ/compiler/for.typ @@ -92,19 +92,24 @@ --- // Destructuring without parentheses. -// Error: 7 expected keyword `in` -// Hint: 7 did you mean to use a destructuring pattern? +// Error: 7-8 unexpected comma +// Hint: 7-8 destructuring patterns must be wrapped in parentheses #for k, v in (a: 4, b: 5) { dont-care } -// Error: 5 expected identifier +// Error: 7-8 unexpected comma +// Hint: 7-8 destructuring patterns must be wrapped in parentheses +#for k, in () {} + +--- +// Error: 5 expected pattern #for -// Error: 5 expected identifier +// Error: 5 expected pattern #for// -// Error: 6 expected identifier +// Error: 6 expected pattern #{for} // Error: 7 expected keyword `in` @@ -116,15 +121,15 @@ // Error: 15 expected block #for v in iter -// Error: 5 expected identifier +// Error: 5 expected pattern #for v in iter {} -// Error: 6 expected identifier -// Error: 10 expected block +// Error: 7-10 expected pattern, found string +// Error: 16 expected block A#for "v" thing -// Error: 5 expected identifier +// Error: 6-9 expected pattern, found string #for "v" in iter {} // Error: 7 expected keyword `in` diff --git a/tests/typ/compiler/let.typ b/tests/typ/compiler/let.typ index 06f07394..411509ff 100644 --- a/tests/typ/compiler/let.typ +++ b/tests/typ/compiler/let.typ @@ -125,22 +125,22 @@ Three #test(a, 1) #test(b, 4) -// Error: 10-11 at most one binding per identifier is allowed +// Error: 10-11 duplicate binding: a #let (a, a) = (1, 2) -// Error: 12-15 at most one destructuring sink is allowed +// Error: 12-15 only one destructuring sink is allowed #let (..a, ..a) = (1, 2) -// Error: 12-13 at most one binding per identifier is allowed +// Error: 12-13 duplicate binding: a #let (a, ..a) = (1, 2) -// Error: 13-14 at most one binding per identifier is allowed +// Error: 13-14 duplicate binding: a #let (a: a, a) = (a: 1, b: 2) -// Error: 13-20 expected identifier, found function call +// Error: 13-20 expected pattern, found function call #let (a, b: b.at(0)) = (a: 1, b: 2) -// Error: 7-14 expected identifier or destructuring sink, found function call +// Error: 7-14 expected pattern, found function call #let (a.at(0),) = (1,) --- @@ -148,7 +148,7 @@ Three #let (a, b, c) = (1, 2) --- -// Error: 6-20 not enough elements to destructure +// Error: 7-10 not enough elements to destructure #let (..a, b, c, d) = (1, 2) --- @@ -193,6 +193,24 @@ Three #let (a, ..) = (a: 1, b: 2) #test(a, 1) +--- +// Ref: false +// Nested destructuring. +#let ((a, b), (key: c)) = ((1, 2), (key: 3)) +#test((a, b, c), (1, 2, 3)) + +--- +// Keyed destructuring is not currently supported. +// Error: 7-18 expected pattern, found string +#let ("spacy key": val) = ("spacy key": 123) +#val + +--- +// Keyed destructuring is not currently supported. +#let x = "spacy key" +// Error: 7-10 expected identifier, found group +#let ((x): v) = ("spacy key": 123) + --- // Trailing placeholders. // Error: 10-11 not enough elements to destructure @@ -200,8 +218,8 @@ Three #test(a, 1) --- -// Error: 10-13 expected identifier, found string -// Error: 18-19 expected identifier, found integer +// Error: 10-13 expected pattern, found string +// Error: 18-19 expected pattern, found integer #let (a: "a", b: 2) = (a: 1, b: 2) --- @@ -213,18 +231,17 @@ Three #let (a, b: b) = (a: 1) --- -// Error: 7-11 cannot destructure named elements from an array +// Error: 7-11 cannot destructure named pattern from an array #let (a: a, b) = (1, 2, 3) --- -// Error: 5 expected identifier +// Error: 5 expected pattern #let -// Error: 6 expected identifier +// Error: 6 expected pattern #{let} -// Error: 5 expected identifier -// Error: 5 expected semicolon or line break +// Error: 6-9 expected pattern, found string #let "v" // Error: 7 expected semicolon or line break @@ -233,8 +250,7 @@ Three // Error: 9 expected expression #let v = -// Error: 5 expected identifier -// Error: 5 expected semicolon or line break +// Error: 6-9 expected pattern, found string #let "v" = 1 // Terminated because expression ends. @@ -246,7 +262,7 @@ Three // Error: 11-12 unclosed delimiter #let v5 = (1, 2 + ; Five -// Error: 9-13 expected identifier, found boolean +// Error: 9-13 expected pattern, found boolean #let (..true) = false --- @@ -257,7 +273,7 @@ Three // Error: 2-3 unexpected underscore #_ -// Error: 8-9 unexpected underscore +// Error: 8-9 expected expression, found underscore #lorem(_) // Error: 3-4 expected expression, found underscore @@ -275,9 +291,11 @@ Three // Error: 15 expected expression #let func(x) = + --- // Error: 12 expected equals sign #let (func)(x) + --- // Error: 12 expected equals sign // Error: 15-15 expected semicolon or line break diff --git a/tests/typ/compiler/ops.typ b/tests/typ/compiler/ops.typ index 4d3b071f..e148dd19 100644 --- a/tests/typ/compiler/ops.typ +++ b/tests/typ/compiler/ops.typ @@ -273,6 +273,38 @@ #test(a, ((2, 3, 4), 2)) #test(b, 1) +--- +// Test comma placement in destructuring assignment. +#let array = (1, 2, 3) +#((key: array.at(1)) = (key: "hi")) +#test(array, (1, "hi", 3)) + +#let array = (1, 2, 3) +#((array.at(1)) = ("hi")) +#test(array, (1, "hi", 3)) + +#let array = (1, 2, 3) +#((array.at(1),) = ("hi",)) +#test(array, (1, "hi", 3)) + +#let array = (1, 2, 3) +#((array.at(1)) = ("hi",)) +#test(array, (1, ("hi",), 3)) + +--- +// Test nested destructuring assignment. +#let a +#let b +#let c +#(((a, b), (key: c)) = ((1, 2), (key: 3))) +#test((a, b, c), (1, 2, 3)) + +--- +#let array = (1, 2, 3) +// Error: 3-17 cannot destructure string +#((array.at(1),) = ("hi")) +#test(array, (1, ("hi",), 3)) + --- // Error: 3-6 cannot mutate a constant: box #(box = 1) diff --git a/tests/typ/compiler/spread.typ b/tests/typ/compiler/spread.typ index f4864faf..23cd587b 100644 --- a/tests/typ/compiler/spread.typ +++ b/tests/typ/compiler/spread.typ @@ -61,11 +61,11 @@ #test(f(1, 2, 3), 3) --- -// Error: 13-19 cannot spread string +// Error: 11-19 cannot spread string #calc.min(.."nope") --- -// Error: 10-14 expected identifier, found boolean +// Error: 10-14 expected pattern, found boolean #let f(..true) = none --- @@ -90,11 +90,11 @@ } --- -// Error: 11-17 cannot spread dictionary into array +// Error: 9-17 cannot spread dictionary into array #(1, 2, ..(a: 1)) --- -// Error: 5-11 cannot spread array into dictionary +// Error: 3-11 cannot spread array into dictionary #(..(1, 2), a: 1) --- diff --git a/tests/typ/compute/foundations.typ b/tests/typ/compute/foundations.typ index cabed0bf..e4b7ce6a 100644 --- a/tests/typ/compute/foundations.typ +++ b/tests/typ/compute/foundations.typ @@ -86,7 +86,7 @@ #eval("RR_1^NN", mode: "math", scope: (RR: math.NN, NN: math.RR)) --- -// Error: 7-12 expected identifier +// Error: 7-12 expected pattern #eval("let") --- -- cgit v1.2.3