Skip to content

Commit

Permalink
Merge pull request #3690 from onflow/bastian/master-port-internal-284
Browse files Browse the repository at this point in the history
Port internal #284
  • Loading branch information
turbolent authored Nov 22, 2024
2 parents b09231f + e29fc35 commit d555d17
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 @@ -11734,3 +11734,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)
}
45 changes: 27 additions & 18 deletions sema/account_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -425,15 +425,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 @@ -553,15 +555,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 @@ -687,15 +691,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 @@ -715,15 +721,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 @@ -1027,9 +1035,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 sema/arrays_dictionaries_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1281,6 +1281,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 @@ -2660,9 +2661,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 sema/builtinfunctions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,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 sema/capability_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,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
31 changes: 17 additions & 14 deletions 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 @@ -444,19 +431,35 @@ func (checker *Checker) checkTypeIndexingExpression(

targetExpression := indexExpression.TargetExpression

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 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 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
10 changes: 6 additions & 4 deletions sema/conditions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,10 +266,11 @@ func TestCheckInvalidFunctionPostConditionWithBeforeAndNoArgument(t *testing.T)
}
`)

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

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

t.Run("emit condition", func(t *testing.T) {
Expand All @@ -285,10 +286,11 @@ func TestCheckInvalidFunctionPostConditionWithBeforeAndNoArgument(t *testing.T)
}
`)

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

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

Expand Down
4 changes: 3 additions & 1 deletion sema/dictionary_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ func TestCheckIncompleteDictionaryType(t *testing.T) {
},
)

require.NoError(t, err)
errs := RequireCheckerErrors(t, err, 1)

assert.IsType(t, errs[0], &sema.UnconvertableTypeError{})

assert.Equal(t,
&sema.DictionaryType{
Expand Down
Loading

0 comments on commit d555d17

Please sign in to comment.