diff --git a/interpreter/misc_test.go b/interpreter/misc_test.go index 8ec9d0853..ee548f85b 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 8499f95e6..83b11d616 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 172884de1..caaf70a65 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" ) @@ -6185,7 +6186,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 +6201,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 +6270,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,24 +6285,42 @@ func TestParseStringTemplate(t *testing.T) { } } + require.NoError(t, err) + }) + + t.Run("invalid, missing paren", func(t *testing.T) { + + t.Parallel() + + _, errs := testParseExpression(` + "\(add" + `) + + var err error + if len(errs) > 0 { + err = Error{ + Errors: errs, + } + } + require.Error(t, err) AssertEqualWithDiff(t, []error{ &SyntaxError{ - Message: "expected identifier got: add()", - Pos: ast.Position{Offset: 12, Line: 2, Column: 11}, + Message: "expected token ')'", + Pos: ast.Position{Offset: 10, Line: 2, Column: 9}, }, }, errs, ) }) - t.Run("invalid, unbalanced paren", func(t *testing.T) { + t.Run("invalid, nested expression paren", func(t *testing.T) { t.Parallel() _, errs := testParseExpression(` - "\(add" + "\((2+2)/2()" `) var err error @@ -6321,11 +6331,11 @@ func TestParseStringTemplate(t *testing.T) { } require.Error(t, err) - AssertEqualWithDiff(t, + utils.AssertEqualWithDiff(t, []error{ &SyntaxError{ Message: "expected token ')'", - Pos: ast.Position{Offset: 10, Line: 2, Column: 9}, + Pos: ast.Position{Offset: 16, Line: 2, Column: 15}, }, }, errs, @@ -6496,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 cc46bc786..578f81d19 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]) + }) }