From ba580e2efe61fae362b86e65a0b17c2202aabb0c Mon Sep 17 00:00:00 2001 From: Raymond Zhang Date: Thu, 10 Oct 2024 11:24:10 -0700 Subject: [PATCH 1/2] Remove identifier constraint, add tests. --- interpreter/misc_test.go | 49 +++++++++++++++++++++++++++++++++++++++ parser/expression.go | 4 ---- parser/expression_test.go | 26 ++++----------------- 3 files changed, 53 insertions(+), 26 deletions(-) diff --git a/interpreter/misc_test.go b/interpreter/misc_test.go index 8ec9d08532..ee548f85b8 100644 --- a/interpreter/misc_test.go +++ b/interpreter/misc_test.go @@ -12509,4 +12509,53 @@ func TestInterpretStringTemplates(t *testing.T) { inter.Globals.Get("x").GetValue(inter), ) }) + + t.Run("func", func(t *testing.T) { + t.Parallel() + + inter := parseCheckAndInterpret(t, ` + let add = fun(): Int { + return 2+2 + } + let x: String = "\(add())" + `) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredStringValue("4"), + inter.Globals.Get("x").GetValue(inter), + ) + }) + + t.Run("ternary", func(t *testing.T) { + t.Parallel() + + inter := parseCheckAndInterpret(t, ` + let z = false + let x: String = "\(z ? "foo" : "bar" )" + `) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredStringValue("bar"), + inter.Globals.Get("x").GetValue(inter), + ) + }) + + t.Run("nested", func(t *testing.T) { + t.Parallel() + + inter := parseCheckAndInterpret(t, ` + let x: String = "\(2*(4-2) + 1 == 5)" + `) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredStringValue("true"), + inter.Globals.Get("x").GetValue(inter), + ) + }) } diff --git a/parser/expression.go b/parser/expression.go index 8499f95e60..83b11d616b 100644 --- a/parser/expression.go +++ b/parser/expression.go @@ -1191,10 +1191,6 @@ func defineStringExpression() { if err != nil { return nil, err } - // limit string templates to identifiers only - if _, ok := value.(*ast.IdentifierExpression); !ok { - return nil, p.syntaxError("expected identifier got: %s", value.String()) - } _, err = p.mustOne(lexer.TokenParenClose) if err != nil { return nil, err diff --git a/parser/expression_test.go b/parser/expression_test.go index 172884de18..6de08c4f78 100644 --- a/parser/expression_test.go +++ b/parser/expression_test.go @@ -6185,7 +6185,7 @@ func TestParseStringTemplate(t *testing.T) { ) }) - t.Run("invalid, num", func(t *testing.T) { + t.Run("valid, num", func(t *testing.T) { t.Parallel() @@ -6200,16 +6200,7 @@ func TestParseStringTemplate(t *testing.T) { } } - require.Error(t, err) - AssertEqualWithDiff(t, - []error{ - &SyntaxError{ - Message: "expected identifier got: 2 + 2", - Pos: ast.Position{Offset: 13, Line: 2, Column: 12}, - }, - }, - errs, - ) + require.NoError(t, err) }) t.Run("valid, nested identifier", func(t *testing.T) { @@ -6278,7 +6269,7 @@ func TestParseStringTemplate(t *testing.T) { ) }) - t.Run("invalid, function identifier", func(t *testing.T) { + t.Run("valid, function identifier", func(t *testing.T) { t.Parallel() @@ -6293,16 +6284,7 @@ func TestParseStringTemplate(t *testing.T) { } } - require.Error(t, err) - AssertEqualWithDiff(t, - []error{ - &SyntaxError{ - Message: "expected identifier got: add()", - Pos: ast.Position{Offset: 12, Line: 2, Column: 11}, - }, - }, - errs, - ) + require.NoError(t, err) }) t.Run("invalid, unbalanced paren", func(t *testing.T) { From d93bb65bebf466512f9d2a2b6a0b801e78bfdaf6 Mon Sep 17 00:00:00 2001 From: Raymond Zhang Date: Thu, 10 Oct 2024 11:49:09 -0700 Subject: [PATCH 2/2] Add more tests for expressions in string templates. --- parser/expression_test.go | 69 ++++++++++++++++++++++++++++++++++++++- sema/string_test.go | 35 ++++++++++++++++++++ 2 files changed, 103 insertions(+), 1 deletion(-) diff --git a/parser/expression_test.go b/parser/expression_test.go index 6de08c4f78..caaf70a653 100644 --- a/parser/expression_test.go +++ b/parser/expression_test.go @@ -34,6 +34,7 @@ import ( "github.com/onflow/cadence/common" "github.com/onflow/cadence/errors" "github.com/onflow/cadence/parser/lexer" + "github.com/onflow/cadence/runtime/tests/utils" . "github.com/onflow/cadence/test_utils/common_utils" ) @@ -6287,7 +6288,7 @@ func TestParseStringTemplate(t *testing.T) { require.NoError(t, err) }) - t.Run("invalid, unbalanced paren", func(t *testing.T) { + t.Run("invalid, missing paren", func(t *testing.T) { t.Parallel() @@ -6314,6 +6315,33 @@ func TestParseStringTemplate(t *testing.T) { ) }) + t.Run("invalid, nested expression paren", func(t *testing.T) { + + t.Parallel() + + _, errs := testParseExpression(` + "\((2+2)/2()" + `) + + var err error + if len(errs) > 0 { + err = Error{ + Errors: errs, + } + } + + require.Error(t, err) + utils.AssertEqualWithDiff(t, + []error{ + &SyntaxError{ + Message: "expected token ')'", + Pos: ast.Position{Offset: 16, Line: 2, Column: 15}, + }, + }, + errs, + ) + }) + t.Run("invalid, nested templates", func(t *testing.T) { t.Parallel() @@ -6478,6 +6506,45 @@ func TestParseStringTemplate(t *testing.T) { AssertEqualWithDiff(t, expected, actual) }) + + t.Run("valid, extra closing paren", func(t *testing.T) { + + t.Parallel() + + actual, errs := testParseExpression(` + "\(a))" + `) + + var err error + if len(errs) > 0 { + err = Error{ + Errors: errs, + } + } + + require.NoError(t, err) + + expected := &ast.StringTemplateExpression{ + Values: []string{ + "", + ")", + }, + Expressions: []ast.Expression{ + &ast.IdentifierExpression{ + Identifier: ast.Identifier{ + Identifier: "a", + Pos: ast.Position{Offset: 7, Line: 2, Column: 6}, + }, + }, + }, + Range: ast.Range{ + StartPos: ast.Position{Offset: 4, Line: 2, Column: 3}, + EndPos: ast.Position{Offset: 10, Line: 2, Column: 9}, + }, + } + + utils.AssertEqualWithDiff(t, expected, actual) + }) } func TestParseNilCoalescing(t *testing.T) { diff --git a/sema/string_test.go b/sema/string_test.go index cc46bc786d..578f81d194 100644 --- a/sema/string_test.go +++ b/sema/string_test.go @@ -743,6 +743,27 @@ func TestCheckStringTemplate(t *testing.T) { assert.IsType(t, &sema.TypeMismatchWithDescriptionError{}, errs[0]) }) + t.Run("invalid, struct with tostring", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + access(all) + struct SomeStruct { + access(all) + view fun toString(): String { + return "SomeStruct" + } + } + let a = SomeStruct() + let x: String = "\(a)" + `) + + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.TypeMismatchWithDescriptionError{}, errs[0]) + }) + t.Run("invalid, array", func(t *testing.T) { t.Parallel() @@ -788,4 +809,18 @@ func TestCheckStringTemplate(t *testing.T) { assert.IsType(t, &sema.TypeMismatchWithDescriptionError{}, errs[0]) }) + + t.Run("invalid, expression type", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + let y: Int = 0 + let x: String = "\(y > 0 ? "String" : true)" + `) + + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.TypeMismatchWithDescriptionError{}, errs[0]) + }) }