diff --git a/interpreter/misc_test.go b/interpreter/misc_test.go index de93c0e6f..1784bbcde 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..566c9e39a 100644 --- a/parser/expression_test.go +++ b/parser/expression_test.go @@ -6054,14 +6054,7 @@ func TestParseStringTemplate(t *testing.T) { "\(test)" `) - var err error - if len(errs) > 0 { - err = Error{ - Errors: errs, - } - } - - require.NoError(t, err) + require.Empty(t, errs) expected := &ast.StringTemplateExpression{ Values: []string{ @@ -6093,14 +6086,7 @@ func TestParseStringTemplate(t *testing.T) { "this is a test \(abc)\(def) test" `) - var err error - if len(errs) > 0 { - err = Error{ - Errors: errs, - } - } - - require.NoError(t, err) + require.Empty(t, errs) expected := &ast.StringTemplateExpression{ Values: []string{ @@ -6139,14 +6125,7 @@ func TestParseStringTemplate(t *testing.T) { "this is a test \(FOO) `) - var err error - if len(errs) > 0 { - err = Error{ - Errors: errs, - } - } - - require.Error(t, err) + require.NotEmpty(t, errs) AssertEqualWithDiff(t, []error{ &SyntaxError{ @@ -6166,14 +6145,7 @@ func TestParseStringTemplate(t *testing.T) { "\(.)" `) - var err error - if len(errs) > 0 { - err = Error{ - Errors: errs, - } - } - - require.Error(t, err) + require.NotEmpty(t, errs) AssertEqualWithDiff(t, []error{ &SyntaxError{ @@ -6185,7 +6157,7 @@ func TestParseStringTemplate(t *testing.T) { ) }) - t.Run("invalid, num", func(t *testing.T) { + t.Run("valid, num", func(t *testing.T) { t.Parallel() @@ -6193,23 +6165,7 @@ func TestParseStringTemplate(t *testing.T) { "\(2 + 2) is a" `) - var err error - if len(errs) > 0 { - err = Error{ - Errors: errs, - } - } - - 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.Empty(t, errs) }) t.Run("valid, nested identifier", func(t *testing.T) { @@ -6220,14 +6176,7 @@ func TestParseStringTemplate(t *testing.T) { "\((a))" `) - var err error - if len(errs) > 0 { - err = Error{ - Errors: errs, - } - } - - require.NoError(t, err) + require.Empty(t, errs) expected := &ast.StringTemplateExpression{ Values: []string{ @@ -6259,14 +6208,7 @@ func TestParseStringTemplate(t *testing.T) { "\()" `) - var err error - if len(errs) > 0 { - err = Error{ - Errors: errs, - } - } - - require.Error(t, err) + require.NotEmpty(t, errs) AssertEqualWithDiff(t, []error{ &SyntaxError{ @@ -6278,7 +6220,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() @@ -6286,46 +6228,43 @@ func TestParseStringTemplate(t *testing.T) { "\(add())" `) - var err error - if len(errs) > 0 { - err = Error{ - Errors: errs, - } - } + require.Empty(t, errs) + }) - require.Error(t, err) + t.Run("invalid, missing paren", func(t *testing.T) { + + t.Parallel() + + _, errs := testParseExpression(` + "\(add" + `) + + require.NotEmpty(t, errs) 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 - if len(errs) > 0 { - err = Error{ - Errors: errs, - } - } - - require.Error(t, err) + require.NotEmpty(t, errs) 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, @@ -6340,14 +6279,7 @@ func TestParseStringTemplate(t *testing.T) { "outer string \( "\(inner template)" )" `) - var err error - if len(errs) > 0 { - err = Error{ - Errors: errs, - } - } - - require.Error(t, err) + require.NotEmpty(t, errs) AssertEqualWithDiff(t, []error{ &SyntaxError{ @@ -6367,14 +6299,7 @@ func TestParseStringTemplate(t *testing.T) { "a\(b)c" `) - var err error - if len(errs) > 0 { - err = Error{ - Errors: errs, - } - } - - require.NoError(t, err) + require.Empty(t, errs) expected := &ast.StringTemplateExpression{ Values: []string{ @@ -6406,14 +6331,7 @@ func TestParseStringTemplate(t *testing.T) { "\(a)b\(c)" `) - var err error - if len(errs) > 0 { - err = Error{ - Errors: errs, - } - } - - require.NoError(t, err) + require.Empty(t, errs) expected := &ast.StringTemplateExpression{ Values: []string{ @@ -6452,14 +6370,7 @@ func TestParseStringTemplate(t *testing.T) { "\(a)\(b)\(c)" `) - var err error - if len(errs) > 0 { - err = Error{ - Errors: errs, - } - } - - require.NoError(t, err) + require.Empty(t, errs) expected := &ast.StringTemplateExpression{ Values: []string{ @@ -6496,6 +6407,38 @@ 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))" + `) + + require.Empty(t, errs) + + 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}, + }, + } + + 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]) + }) }