Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Produce a better error message when failing to reference type-erased references #3150

Merged
merged 3 commits into from
Mar 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions runtime/interpreter/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,30 @@
)
}

// InvalidMemberReferenceError
type InvalidMemberReferenceError struct {
ExpectedType sema.Type
ActualType sema.Type
LocationRange
}

var _ errors.UserError = InvalidMemberReferenceError{}

func (InvalidMemberReferenceError) IsUserError() {}

func (e InvalidMemberReferenceError) Error() string {
expected, actual := sema.ErrorMessageExpectedActualTypes(
e.ExpectedType,
e.ActualType,
)

return fmt.Sprintf(
"cannot create reference: expected `%s`, got `%s`",
expected,
actual,
)

Check warning on line 438 in runtime/interpreter/errors.go

View check run for this annotation

Codecov / codecov/patch

runtime/interpreter/errors.go#L428-L438

Added lines #L428 - L438 were not covered by tests
}

// InvalidPathDomainError
type InvalidPathDomainError struct {
LocationRange
Expand Down
14 changes: 14 additions & 0 deletions runtime/interpreter/interpreter_expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,18 @@ func (interpreter *Interpreter) getReferenceValue(value Value, resultType sema.T
case NilValue, ReferenceValue:
// Reference to a nil, should return a nil.
// If the value is already a reference then return the same reference.
// However, we need to make sure that this reference is actually a subtype of the resultType,
// since the checker may not be aware that we are "short-circuiting" in this case

staticType := value.StaticType(interpreter)
if !interpreter.IsSubTypeOfSemaType(staticType, resultType) {
panic(InvalidMemberReferenceError{
ExpectedType: resultType,
ActualType: interpreter.MustConvertStaticToSemaType(staticType),
LocationRange: locationRange,
})
}

return value
case *SomeValue:
innerValue := interpreter.getReferenceValue(value.value, resultType, locationRange)
Expand Down Expand Up @@ -1030,13 +1042,15 @@ func (interpreter *Interpreter) maybeGetReference(
memberValue Value,
) Value {
indexExpressionTypes := interpreter.Program.Elaboration.IndexExpressionTypes(expression)

if indexExpressionTypes.ReturnReference {
expectedType := indexExpressionTypes.ResultType

locationRange := LocationRange{
Location: interpreter.Location,
HasPosition: expression,
}

// Get a reference to the value
memberValue = interpreter.getReferenceValue(memberValue, expectedType, locationRange)
}
Expand Down
49 changes: 49 additions & 0 deletions runtime/tests/interpreter/member_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1149,3 +1149,52 @@ func TestInterpretMemberAccess(t *testing.T) {
}
})
}

func TestInterpretNestedReferenceMemberAccess(t *testing.T) {

t.Parallel()

t.Run("indexing", func(t *testing.T) {
t.Parallel()

inter := parseCheckAndInterpret(t, `
resource R {}

fun test() {
let r <- create R()
let arrayRef = &[&r as &R] as &[AnyStruct]
let ref: &AnyStruct = arrayRef[0] // <--- run-time error here
destroy r
}
`)

_, err := inter.Invoke("test")
require.ErrorAs(t, err, &interpreter.InvalidMemberReferenceError{})
})

t.Run("field", func(t *testing.T) {
t.Parallel()

inter := parseCheckAndInterpret(t, `
resource R {}

struct Container {
let value: AnyStruct

init(value: AnyStruct) {
self.value = value
}
}

fun test() {
let r <- create R()
let containerRef = &Container(value: &r as &R) as &Container
let ref: &AnyStruct = containerRef.value // <--- run-time error here
destroy r
}
`)

_, err := inter.Invoke("test")
require.ErrorAs(t, err, &interpreter.InvalidMemberReferenceError{})
})
}
Loading