From f8e663e8c9beff31e272d9aa88dfbbabbaa78193 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Wed, 4 May 2022 18:35:48 -0700 Subject: [PATCH] fix parsing of reference expressions: only parse casting expression --- runtime/parser2/expression.go | 7 +- runtime/parser2/expression_test.go | 105 ++++++++++++++++++ runtime/tests/checker/reference_test.go | 20 ++++ runtime/tests/interpreter/interpreter_test.go | 41 +++++++ 4 files changed, 170 insertions(+), 3 deletions(-) diff --git a/runtime/parser2/expression.go b/runtime/parser2/expression.go index c05c1816d9..274a282961 100644 --- a/runtime/parser2/expression.go +++ b/runtime/parser2/expression.go @@ -29,8 +29,10 @@ import ( "github.com/onflow/cadence/runtime/parser2/lexer" ) +const exprBindingPowerGap = 10 + const ( - exprLeftBindingPowerTernary = 10 * (iota + 2) + exprLeftBindingPowerTernary = exprBindingPowerGap * (iota + 2) exprLeftBindingPowerLogicalOr exprLeftBindingPowerLogicalAnd exprLeftBindingPowerComparison @@ -1117,8 +1119,7 @@ func defineReferenceExpression() { lexer.TokenAmpersand, func(p *parser, token lexer.Token) ast.Expression { p.skipSpaceAndComments(true) - // TODO: maybe require above unary - expression := parseExpression(p, lowestBindingPower) + expression := parseExpression(p, exprLeftBindingPowerCasting-exprBindingPowerGap) p.skipSpaceAndComments(true) diff --git a/runtime/parser2/expression_test.go b/runtime/parser2/expression_test.go index 0e90e5f554..e34ceb1db2 100644 --- a/runtime/parser2/expression_test.go +++ b/runtime/parser2/expression_test.go @@ -1878,6 +1878,111 @@ func TestParseReference(t *testing.T) { ) } +func TestParseNilCoelesceReference(t *testing.T) { + + t.Parallel() + + result, errs := ParseExpression(` + &xs["a"] as &Int? ?? 1 + `) + require.Empty(t, errs) + + utils.AssertEqualWithDiff(t, + &ast.BinaryExpression{ + Operation: ast.OperationNilCoalesce, + Left: &ast.ReferenceExpression{ + Expression: &ast.IndexExpression{ + TargetExpression: &ast.IdentifierExpression{ + Identifier: ast.Identifier{ + Identifier: "xs", + Pos: ast.Position{ + Offset: 12, + Line: 2, + Column: 11, + }, + }, + }, + IndexingExpression: &ast.StringExpression{ + Value: "a", + Range: ast.Range{ + StartPos: ast.Position{ + Offset: 15, + Line: 2, + Column: 14, + }, + EndPos: ast.Position{ + Offset: 17, + Line: 2, + Column: 16, + }, + }, + }, + Range: ast.Range{ + StartPos: ast.Position{ + Offset: 14, + Line: 2, + Column: 13, + }, + EndPos: ast.Position{ + Offset: 18, + Line: 2, + Column: 17, + }, + }, + }, + Type: &ast.OptionalType{ + Type: &ast.ReferenceType{ + Authorized: false, + Type: &ast.NominalType{ + Identifier: ast.Identifier{ + Identifier: "Int", + Pos: ast.Position{ + Offset: 24, + Line: 2, + Column: 23, + }, + }, + }, + StartPos: ast.Position{ + Offset: 23, + Line: 2, + Column: 22, + }, + }, + EndPos: ast.Position{ + Offset: 27, + Line: 2, + Column: 26, + }, + }, + StartPos: ast.Position{ + Offset: 11, + Line: 2, + Column: 10, + }, + }, + Right: &ast.IntegerExpression{ + PositiveLiteral: "1", + Value: big.NewInt(1), + Base: 10, + Range: ast.Range{ + StartPos: ast.Position{ + Offset: 32, + Line: 2, + Column: 31, + }, + EndPos: ast.Position{ + Offset: 32, + Line: 2, + Column: 31, + }, + }, + }, + }, + result, + ) +} + func TestParseCasts(t *testing.T) { t.Parallel() diff --git a/runtime/tests/checker/reference_test.go b/runtime/tests/checker/reference_test.go index 7c9454517a..e86f6a58c1 100644 --- a/runtime/tests/checker/reference_test.go +++ b/runtime/tests/checker/reference_test.go @@ -1071,6 +1071,26 @@ func TestCheckReferenceExpressionOfOptional(t *testing.T) { }) } +func TestCheckNilCoalesceReference(t *testing.T) { + + t.Parallel() + + checker, err := ParseAndCheckWithPanic(t, ` + let xs = {"a": 1} + let ref = &xs["a"] as &Int? ?? panic("no a") + `) + require.NoError(t, err) + + refValueType := RequireGlobalValue(t, checker.Elaboration, "ref") + + assert.Equal(t, + &sema.ReferenceType{ + Type: sema.IntType, + }, + refValueType, + ) +} + func TestCheckInvalidReferenceExpressionNonReferenceAmbiguous(t *testing.T) { t.Parallel() diff --git a/runtime/tests/interpreter/interpreter_test.go b/runtime/tests/interpreter/interpreter_test.go index eff344fca9..a63d819012 100644 --- a/runtime/tests/interpreter/interpreter_test.go +++ b/runtime/tests/interpreter/interpreter_test.go @@ -9785,3 +9785,44 @@ func TestInterpretCastingBoxing(t *testing.T) { ) }) } + +func TestInterpretNilCoalesceReference(t *testing.T) { + + t.Parallel() + + standardLibraryFunctions := + stdlib.StandardLibraryFunctions{ + stdlib.PanicFunction, + } + + valueDeclarations := standardLibraryFunctions.ToSemaValueDeclarations() + values := standardLibraryFunctions.ToInterpreterValueDeclarations() + + inter, err := parseCheckAndInterpretWithOptions(t, + ` + let xs = {"a": 2} + let ref = &xs["a"] as &Int? ?? panic("no a") + `, + ParseCheckAndInterpretOptions{ + CheckerOptions: []sema.Option{ + sema.WithPredeclaredValues(valueDeclarations), + }, + Options: []interpreter.Option{ + interpreter.WithPredeclaredValues(values), + }, + }, + ) + require.NoError(t, err) + + variable, ok := inter.Globals.Get("ref") + require.True(t, ok) + + require.Equal( + t, + &interpreter.EphemeralReferenceValue{ + Value: interpreter.NewIntValueFromInt64(2), + BorrowedType: sema.IntType, + }, + variable.GetValue(), + ) +}