Skip to content

Commit

Permalink
Merge pull request #3684 from onflow/bastian/port-internal-284
Browse files Browse the repository at this point in the history
[v1.0] Port internal #284
  • Loading branch information
turbolent authored Nov 21, 2024
2 parents 0f12024 + aecd31a commit 36b5b2b
Show file tree
Hide file tree
Showing 14 changed files with 359 additions and 58 deletions.
43 changes: 43 additions & 0 deletions runtime/runtime_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11548,3 +11548,46 @@ func TestRuntimeBuiltInFunctionConfusion(t *testing.T) {
var redeclarationError *sema.RedeclarationError
require.ErrorAs(t, err, &redeclarationError)
}

func TestRuntimeInvocationReturnTypeInferenceFailure(t *testing.T) {

t.Parallel()

address := common.MustBytesToAddress([]byte{0x1})

newRuntimeInterface := func() Interface {

return &TestRuntimeInterface{
Storage: NewTestLedger(nil, nil),
OnGetSigningAccounts: func() ([]common.Address, error) {
return []common.Address{address}, nil
},
}
}

runtime := NewTestInterpreterRuntime()

nextTransactionLocation := NewTransactionLocationGenerator()

tx := []byte(`
transaction{
prepare(signer: auth(Storage) &Account){
let functions = [signer.storage.save].reverse()
}
}
`)

err := runtime.ExecuteTransaction(
Script{
Source: tx,
},
Context{
Interface: newRuntimeInterface(),
Location: nextTransactionLocation(),
},
)
RequireError(t, err)

var typeErr *sema.InvocationTypeInferenceError
require.ErrorAs(t, err, &typeErr)
}
31 changes: 17 additions & 14 deletions runtime/sema/check_expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -417,20 +417,7 @@ func (checker *Checker) visitIndexExpression(
return InvalidType
}

elementType := checker.checkTypeIndexingExpression(typeIndexedType, indexExpression)
if elementType == InvalidType {
checker.report(
&InvalidTypeIndexingError{
BaseType: typeIndexedType,
IndexingExpression: indexExpression.IndexingExpression,
Range: ast.NewRangeFromPositioned(
checker.memoryGauge,
indexExpression.IndexingExpression,
),
},
)
}
return elementType
return checker.checkTypeIndexingExpression(typeIndexedType, indexExpression)
}

reportNonIndexable(targetType)
Expand All @@ -450,19 +437,35 @@ func (checker *Checker) checkTypeIndexingExpression(
})
}

reportInvalid := func() {
checker.report(
&InvalidTypeIndexingError{
BaseType: targetType,
IndexingExpression: indexExpression.IndexingExpression,
Range: ast.NewRangeFromPositioned(
checker.memoryGauge,
indexExpression.IndexingExpression,
),
},
)
}

expressionType := ast.ExpressionAsType(indexExpression.IndexingExpression)
if expressionType == nil {
reportInvalid()
return InvalidType
}

nominalTypeExpression, isNominalType := expressionType.(*ast.NominalType)
if !isNominalType {
reportInvalid()
return InvalidType
}

nominalType := checker.convertNominalType(nominalTypeExpression)

if !targetType.IsValidIndexingType(nominalType) {
reportInvalid()
return InvalidType
}

Expand Down
20 changes: 19 additions & 1 deletion runtime/sema/check_invocation_expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -503,7 +503,13 @@ func (checker *Checker) checkInvocation(

returnType = functionType.ReturnTypeAnnotation.Type.Resolve(typeArguments)
if returnType == nil {
// TODO: report error? does `checkTypeParameterInference` below already do that?
checker.report(&InvocationTypeInferenceError{
Range: ast.NewRangeFromPositioned(
checker.memoryGauge,
invocationExpression,
),
})

returnType = InvalidType
}

Expand Down Expand Up @@ -599,6 +605,12 @@ func (checker *Checker) checkInvocationRequiredArgument(
parameterType = parameterType.Resolve(typeParameters)
// If the type parameter could not be resolved, use the invalid type.
if parameterType == nil {
checker.report(&InvocationTypeInferenceError{
Range: ast.NewRangeFromPositioned(
checker.memoryGauge,
argument.Expression,
),
})
parameterType = InvalidType
}
}
Expand Down Expand Up @@ -674,6 +686,12 @@ func (checker *Checker) checkInvocationRequiredArgument(
parameterType = parameterType.Resolve(typeParameters)
// If the type parameter could not be resolved, use the invalid type.
if parameterType == nil {
checker.report(&InvocationTypeInferenceError{
Range: ast.NewRangeFromPositioned(
checker.memoryGauge,
argument.Expression,
),
})
parameterType = InvalidType
}
}
Expand Down
24 changes: 20 additions & 4 deletions runtime/sema/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -886,12 +886,12 @@ func (checker *Checker) ConvertType(t ast.Type) Type {
case *ast.InstantiationType:
return checker.convertInstantiationType(t)

case nil:
// The AST might contain "holes" if parsing failed
default:
checker.report(&UnconvertableTypeError{
Range: ast.NewRangeFromPositioned(checker.memoryGauge, t),
})
return InvalidType
}

panic(&astTypeConversionError{invalidASTType: t})
}

func CheckIntersectionType(
Expand Down Expand Up @@ -2611,6 +2611,8 @@ func (checker *Checker) visitExpressionWithForceType(

actualType = ast.AcceptExpression[Type](expr, checker)

checker.checkErrorsForInvalidExpressionTypes(actualType, expectedType)

if checker.Config.ExtendedElaborationEnabled {
checker.Elaboration.SetExpressionTypes(
expr,
Expand Down Expand Up @@ -2645,6 +2647,20 @@ func (checker *Checker) visitExpressionWithForceType(
return actualType, actualType
}

func (checker *Checker) checkErrorsForInvalidExpressionTypes(actualType Type, expectedType Type) {
// Defensive check: If an invalid type was produced,
// then also an error should have been reported for the invalid program.
//
// Check for errors first, which is cheap,
// before checking for an invalid type, which is more expensive.

if len(checker.errors) == 0 &&
(actualType.IsInvalidType() || (expectedType != nil && expectedType.IsInvalidType())) {

panic(errors.NewUnexpectedError("invalid type produced without error"))
}
}

func (checker *Checker) expressionRange(expression ast.Expression) ast.Range {
if indexExpr, ok := expression.(*ast.IndexExpression); ok {
return ast.NewRange(
Expand Down
45 changes: 35 additions & 10 deletions runtime/sema/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,16 +51,6 @@ func ErrorMessageExpectedActualTypes(
return
}

// astTypeConversionError

type astTypeConversionError struct {
invalidASTType ast.Type
}

func (e *astTypeConversionError) Error() string {
return fmt.Sprintf("cannot convert unsupported AST type: %#+v", e.invalidASTType)
}

// unsupportedOperation

type unsupportedOperation struct {
Expand Down Expand Up @@ -4826,3 +4816,38 @@ func (*NestedReferenceError) IsUserError() {}
func (e *NestedReferenceError) Error() string {
return fmt.Sprintf("cannot create a nested reference to value of type %s", e.Type.QualifiedString())
}

// InvocationTypeInferenceError

type InvocationTypeInferenceError struct {
ast.Range
}

var _ SemanticError = &InvocationTypeInferenceError{}
var _ errors.UserError = &InvocationTypeInferenceError{}

func (e *InvocationTypeInferenceError) isSemanticError() {}

func (*InvocationTypeInferenceError) IsUserError() {}

func (e *InvocationTypeInferenceError) Error() string {
return "cannot infer type of invocation"
}

// UnconvertableTypeError

type UnconvertableTypeError struct {
Type ast.Type
ast.Range
}

var _ SemanticError = &UnconvertableTypeError{}
var _ errors.UserError = &UnconvertableTypeError{}

func (e *UnconvertableTypeError) isSemanticError() {}

func (*UnconvertableTypeError) IsUserError() {}

func (e *UnconvertableTypeError) Error() string {
return fmt.Sprintf("cannot convert type `%s`", e.Type)
}
45 changes: 27 additions & 18 deletions runtime/tests/checker/account_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -424,15 +424,17 @@ func TestCheckAccountStorageLoad(t *testing.T) {
)

if domain == common.PathDomainStorage {
errs := RequireCheckerErrors(t, err, 1)
errs := RequireCheckerErrors(t, err, 2)

require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[0])
require.IsType(t, &sema.InvocationTypeInferenceError{}, errs[0])
require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1])

} else {
errs := RequireCheckerErrors(t, err, 2)
errs := RequireCheckerErrors(t, err, 3)

require.IsType(t, &sema.TypeMismatchError{}, errs[0])
require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1])
require.IsType(t, &sema.InvocationTypeInferenceError{}, errs[1])
require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[2])
}
})
}
Expand Down Expand Up @@ -552,15 +554,17 @@ func TestCheckAccountStorageCopy(t *testing.T) {
)

if domain == common.PathDomainStorage {
errs := RequireCheckerErrors(t, err, 1)
errs := RequireCheckerErrors(t, err, 2)

require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[0])
require.IsType(t, &sema.InvocationTypeInferenceError{}, errs[0])
require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1])

} else {
errs := RequireCheckerErrors(t, err, 2)
errs := RequireCheckerErrors(t, err, 3)

require.IsType(t, &sema.TypeMismatchError{}, errs[0])
require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1])
require.IsType(t, &sema.InvocationTypeInferenceError{}, errs[1])
require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[2])
}
})
}
Expand Down Expand Up @@ -686,15 +690,17 @@ func TestCheckAccountStorageBorrow(t *testing.T) {
)

if domain == common.PathDomainStorage {
errs := RequireCheckerErrors(t, err, 1)
errs := RequireCheckerErrors(t, err, 2)

require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[0])
require.IsType(t, &sema.InvocationTypeInferenceError{}, errs[0])
require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1])

} else {
errs := RequireCheckerErrors(t, err, 2)
errs := RequireCheckerErrors(t, err, 3)

require.IsType(t, &sema.TypeMismatchError{}, errs[0])
require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1])
require.IsType(t, &sema.InvocationTypeInferenceError{}, errs[1])
require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[2])
}
})

Expand All @@ -714,15 +720,17 @@ func TestCheckAccountStorageBorrow(t *testing.T) {
)

if domain == common.PathDomainStorage {
errs := RequireCheckerErrors(t, err, 1)
errs := RequireCheckerErrors(t, err, 2)

require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[0])
require.IsType(t, &sema.InvocationTypeInferenceError{}, errs[0])
require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1])

} else {
errs := RequireCheckerErrors(t, err, 2)
errs := RequireCheckerErrors(t, err, 3)

require.IsType(t, &sema.TypeMismatchError{}, errs[0])
require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1])
require.IsType(t, &sema.InvocationTypeInferenceError{}, errs[1])
require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[2])
}
})
})
Expand Down Expand Up @@ -1026,9 +1034,10 @@ func TestCheckAccountContractsBorrow(t *testing.T) {
}
`)

errors := RequireCheckerErrors(t, err, 1)
errors := RequireCheckerErrors(t, err, 2)

assert.IsType(t, &sema.TypeParameterTypeInferenceError{}, errors[0])
assert.IsType(t, &sema.InvocationTypeInferenceError{}, errors[0])
assert.IsType(t, &sema.TypeParameterTypeInferenceError{}, errors[1])
})
}

Expand Down
6 changes: 4 additions & 2 deletions runtime/tests/checker/arrays_dictionaries_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1280,6 +1280,7 @@ func TestCheckArrayMapInvalidArgs(t *testing.T) {
`,
[]sema.SemanticError{
&sema.TypeMismatchError{},
&sema.InvocationTypeInferenceError{}, // since we're not passing a function.
&sema.TypeParameterTypeInferenceError{}, // since we're not passing a function.
},
)
Expand Down Expand Up @@ -2659,9 +2660,10 @@ func TestCheckArrayToConstantSizedMissingTypeArgument(t *testing.T) {
}
`)

errs := RequireCheckerErrors(t, err, 1)
errs := RequireCheckerErrors(t, err, 2)

assert.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[0])
assert.IsType(t, &sema.InvocationTypeInferenceError{}, errs[0])
assert.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1])
}

func TestCheckArrayReferenceTypeInference(t *testing.T) {
Expand Down
1 change: 1 addition & 0 deletions runtime/tests/checker/builtinfunctions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,7 @@ func TestCheckRevertibleRandom(t *testing.T) {
"missing type argument",
`let rand = revertibleRandom()`,
[]error{
&sema.InvocationTypeInferenceError{},
&sema.TypeParameterTypeInferenceError{},
},
)
Expand Down
5 changes: 3 additions & 2 deletions runtime/tests/checker/capability_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,10 @@ func TestCheckCapability_borrow(t *testing.T) {
let r = capability.borrow()
`)

errs := RequireCheckerErrors(t, err, 1)
errs := RequireCheckerErrors(t, err, 2)

require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[0])
require.IsType(t, &sema.InvocationTypeInferenceError{}, errs[0])
require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1])
})

for _, auth := range []sema.Access{sema.UnauthorizedAccess,
Expand Down
Loading

0 comments on commit 36b5b2b

Please sign in to comment.