From 638969ad64ec5a67d4bb50198313cabcb20e58cf Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Fri, 8 Dec 2023 10:22:09 -0500 Subject: [PATCH 1/4] raise type error on creation of nested reference --- runtime/sema/check_reference_expression.go | 8 ++++++++ runtime/sema/errors.go | 17 +++++++++++++++++ runtime/tests/interpreter/reference_test.go | 12 +++++++++--- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/runtime/sema/check_reference_expression.go b/runtime/sema/check_reference_expression.go index 16092129b5..06a847c602 100644 --- a/runtime/sema/check_reference_expression.go +++ b/runtime/sema/check_reference_expression.go @@ -83,6 +83,14 @@ func (checker *Checker) VisitReferenceExpression(referenceExpression *ast.Refere referencedType, actualType := checker.visitExpression(referencedExpression, expectedLeftType) + // check that the type of the referenced value is not itself a reference + if nestedReference, isNestedReference := actualType.(*ReferenceType); isNestedReference { + checker.report(&NestedReferenceError{ + Type: nestedReference, + Range: checker.expressionRange(referenceExpression), + }) + } + hasErrors := len(checker.errors) > beforeErrors if !hasErrors { // If the reference type was an optional type, diff --git a/runtime/sema/errors.go b/runtime/sema/errors.go index 5f6a639c17..98cd6d6138 100644 --- a/runtime/sema/errors.go +++ b/runtime/sema/errors.go @@ -4694,3 +4694,20 @@ func (*InvalidTypeParameterizedNonNativeFunctionError) IsUserError() {} func (e *InvalidTypeParameterizedNonNativeFunctionError) Error() string { return "invalid type parameters in non-native function" } + +// NestedReferenceError +type NestedReferenceError struct { + Type *ReferenceType + ast.Range +} + +var _ SemanticError = &NestedReferenceError{} +var _ errors.UserError = &NestedReferenceError{} + +func (*NestedReferenceError) isSemanticError() {} + +func (*NestedReferenceError) IsUserError() {} + +func (e *NestedReferenceError) Error() string { + return fmt.Sprintf("cannot create a nested reference to value of type %s", e.Type.QualifiedString()) +} diff --git a/runtime/tests/interpreter/reference_test.go b/runtime/tests/interpreter/reference_test.go index ff600433b5..96ac2a111c 100644 --- a/runtime/tests/interpreter/reference_test.go +++ b/runtime/tests/interpreter/reference_test.go @@ -1739,14 +1739,20 @@ func TestInterpretReferenceToReference(t *testing.T) { t.Parallel() - inter := parseCheckAndInterpret(t, ` + inter, err := parseCheckAndInterpretWithOptions(t, ` fun main() { let x = &1 as &Int let y = &x as & &Int } - `) + `, ParseCheckAndInterpretOptions{ + HandleCheckerError: func(err error) { + errs := checker.RequireCheckerErrors(t, err, 1) + require.IsType(t, errs[0], &sema.NestedReferenceError{}) + }, + }) + require.NoError(t, err) - _, err := inter.Invoke("main") + _, err = inter.Invoke("main") RequireError(t, err) require.ErrorAs(t, err, &interpreter.NestedReferenceError{}) From 548be578a7f225b98d1bf841be6f4ec58b5a3134 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Fri, 8 Dec 2023 10:26:40 -0500 Subject: [PATCH 2/4] handle optional values as well --- runtime/sema/check_reference_expression.go | 17 +++++--- runtime/tests/checker/reference_test.go | 46 +++++++++++++++++++++ runtime/tests/interpreter/reference_test.go | 2 +- 3 files changed, 59 insertions(+), 6 deletions(-) diff --git a/runtime/sema/check_reference_expression.go b/runtime/sema/check_reference_expression.go index 06a847c602..da2c071cb6 100644 --- a/runtime/sema/check_reference_expression.go +++ b/runtime/sema/check_reference_expression.go @@ -84,12 +84,19 @@ func (checker *Checker) VisitReferenceExpression(referenceExpression *ast.Refere referencedType, actualType := checker.visitExpression(referencedExpression, expectedLeftType) // check that the type of the referenced value is not itself a reference - if nestedReference, isNestedReference := actualType.(*ReferenceType); isNestedReference { - checker.report(&NestedReferenceError{ - Type: nestedReference, - Range: checker.expressionRange(referenceExpression), - }) + var requireNoReferenceNesting func(actualType Type) + requireNoReferenceNesting = func(actualType Type) { + switch nestedReference := actualType.(type) { + case *ReferenceType: + checker.report(&NestedReferenceError{ + Type: nestedReference, + Range: checker.expressionRange(referenceExpression), + }) + case *OptionalType: + requireNoReferenceNesting(nestedReference.Type) + } } + requireNoReferenceNesting(actualType) hasErrors := len(checker.errors) > beforeErrors if !hasErrors { diff --git a/runtime/tests/checker/reference_test.go b/runtime/tests/checker/reference_test.go index f4af464361..63a6bafebe 100644 --- a/runtime/tests/checker/reference_test.go +++ b/runtime/tests/checker/reference_test.go @@ -3061,3 +3061,49 @@ func TestCheckResourceReferenceIndexNilAssignment(t *testing.T) { require.IsType(t, &sema.InvalidResourceAssignmentError{}, errors[2]) }) } + +func TestCheckNestedReference(t *testing.T) { + t.Parallel() + + t.Run("basic", func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheck(t, ` + fun main() { + let x = &1 as &Int + let y = &x as & &Int + } + `) + + errors := RequireCheckerErrors(t, err, 1) + require.IsType(t, &sema.NestedReferenceError{}, errors[0]) + }) + + t.Run("type of underlying value checked", func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheck(t, ` + fun main() { + let x = &1 as &Int + let y = &x as &AnyStruct + } + `) + + errors := RequireCheckerErrors(t, err, 1) + require.IsType(t, &sema.NestedReferenceError{}, errors[0]) + }) + + t.Run("optional", func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheck(t, ` + fun main() { + let x: &Int? = &1 as &Int + let y = &x as &AnyStruct? + } + `) + + errors := RequireCheckerErrors(t, err, 1) + require.IsType(t, &sema.NestedReferenceError{}, errors[0]) + }) +} diff --git a/runtime/tests/interpreter/reference_test.go b/runtime/tests/interpreter/reference_test.go index 96ac2a111c..f3f7d8fff5 100644 --- a/runtime/tests/interpreter/reference_test.go +++ b/runtime/tests/interpreter/reference_test.go @@ -1747,7 +1747,7 @@ func TestInterpretReferenceToReference(t *testing.T) { `, ParseCheckAndInterpretOptions{ HandleCheckerError: func(err error) { errs := checker.RequireCheckerErrors(t, err, 1) - require.IsType(t, errs[0], &sema.NestedReferenceError{}) + require.IsType(t, &sema.NestedReferenceError{}, errs[0]) }, }) require.NoError(t, err) From 20b5fccd1c93fa5618ad6533e667c9ae902b87a9 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Fri, 8 Dec 2023 10:27:03 -0500 Subject: [PATCH 3/4] add test --- runtime/tests/checker/reference_test.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/runtime/tests/checker/reference_test.go b/runtime/tests/checker/reference_test.go index 63a6bafebe..fe652807d0 100644 --- a/runtime/tests/checker/reference_test.go +++ b/runtime/tests/checker/reference_test.go @@ -3106,4 +3106,18 @@ func TestCheckNestedReference(t *testing.T) { errors := RequireCheckerErrors(t, err, 1) require.IsType(t, &sema.NestedReferenceError{}, errors[0]) }) + + t.Run("nested optional", func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheck(t, ` + fun main() { + let x: &Int?? = &1 as &Int + let y = &x as &AnyStruct? + } + `) + + errors := RequireCheckerErrors(t, err, 1) + require.IsType(t, &sema.NestedReferenceError{}, errors[0]) + }) } From 9034f6d270cb7b0284eb49abca6419b9428703ef Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Fri, 8 Dec 2023 10:28:26 -0500 Subject: [PATCH 4/4] fix test --- runtime/tests/interpreter/reference_test.go | 25 ++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/runtime/tests/interpreter/reference_test.go b/runtime/tests/interpreter/reference_test.go index f3f7d8fff5..7dd2640513 100644 --- a/runtime/tests/interpreter/reference_test.go +++ b/runtime/tests/interpreter/reference_test.go @@ -1779,11 +1779,34 @@ func TestInterpretReferenceToReference(t *testing.T) { t.Parallel() - inter := parseCheckAndInterpret(t, ` + inter, err := parseCheckAndInterpretWithOptions(t, ` fun main() { let x: (&Int)? = &1 as &Int let y: (&(&Int))? = &x } + `, ParseCheckAndInterpretOptions{ + HandleCheckerError: func(err error) { + errs := checker.RequireCheckerErrors(t, err, 1) + require.IsType(t, &sema.NestedReferenceError{}, errs[0]) + }, + }) + require.NoError(t, err) + + _, err = inter.Invoke("main") + RequireError(t, err) + + require.ErrorAs(t, err, &interpreter.NestedReferenceError{}) + }) + + t.Run("upcast to optional anystruct", func(t *testing.T) { + + t.Parallel() + + inter := parseCheckAndInterpret(t, ` + fun main() { + let x = &1 as &Int as AnyStruct? + let y = &x as &AnyStruct? + } `) _, err := inter.Invoke("main")