From 90f36e9d33c1631a3b74562b58ec0978edf14f02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 12 Nov 2024 10:40:27 -0800 Subject: [PATCH 01/11] report error when inference of return type of invocation fails --- runtime/runtime_test.go | 43 +++++++++++++++++ runtime/sema/check_invocation_expression.go | 8 +++- runtime/sema/errors.go | 17 +++++++ runtime/tests/checker/account_test.go | 45 ++++++++++-------- .../tests/checker/arrays_dictionaries_test.go | 8 ++-- .../tests/checker/builtinfunctions_test.go | 1 + runtime/tests/checker/capability_test.go | 5 +- runtime/tests/checker/conditions_test.go | 10 ++-- runtime/tests/checker/genericfunction_test.go | 5 +- runtime/tests/checker/invalid_test.go | 46 +++++++++++++++++++ 10 files changed, 158 insertions(+), 30 deletions(-) diff --git a/runtime/runtime_test.go b/runtime/runtime_test.go index c56c01d102..2d79c012c4 100644 --- a/runtime/runtime_test.go +++ b/runtime/runtime_test.go @@ -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.InvocationReturnTypeInferenceError + require.ErrorAs(t, err, &typeErr) +} diff --git a/runtime/sema/check_invocation_expression.go b/runtime/sema/check_invocation_expression.go index dc8a2ac634..3967fd06a9 100644 --- a/runtime/sema/check_invocation_expression.go +++ b/runtime/sema/check_invocation_expression.go @@ -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(&InvocationReturnTypeInferenceError{ + Range: ast.NewRangeFromPositioned( + checker.memoryGauge, + invocationExpression, + ), + }) + returnType = InvalidType } diff --git a/runtime/sema/errors.go b/runtime/sema/errors.go index 66efa2b57d..9b770b051e 100644 --- a/runtime/sema/errors.go +++ b/runtime/sema/errors.go @@ -4826,3 +4826,20 @@ func (*NestedReferenceError) IsUserError() {} func (e *NestedReferenceError) Error() string { return fmt.Sprintf("cannot create a nested reference to value of type %s", e.Type.QualifiedString()) } + +// InvocationReturnTypeInferenceError + +type InvocationReturnTypeInferenceError struct { + ast.Range +} + +var _ SemanticError = &InvocationReturnTypeInferenceError{} +var _ errors.UserError = &InvocationReturnTypeInferenceError{} + +func (e *InvocationReturnTypeInferenceError) isSemanticError() {} + +func (*InvocationReturnTypeInferenceError) IsUserError() {} + +func (e *InvocationReturnTypeInferenceError) Error() string { + return "cannot infer return type of invocation" +} diff --git a/runtime/tests/checker/account_test.go b/runtime/tests/checker/account_test.go index 870e9f6731..ac72e777fc 100644 --- a/runtime/tests/checker/account_test.go +++ b/runtime/tests/checker/account_test.go @@ -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.InvocationReturnTypeInferenceError{}, 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.InvocationReturnTypeInferenceError{}, errs[1]) + require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[2]) } }) } @@ -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.InvocationReturnTypeInferenceError{}, 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.InvocationReturnTypeInferenceError{}, errs[1]) + require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[2]) } }) } @@ -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.InvocationReturnTypeInferenceError{}, 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.InvocationReturnTypeInferenceError{}, errs[1]) + require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[2]) } }) @@ -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.InvocationReturnTypeInferenceError{}, 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.InvocationReturnTypeInferenceError{}, errs[1]) + require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[2]) } }) }) @@ -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.InvocationReturnTypeInferenceError{}, errors[0]) + assert.IsType(t, &sema.TypeParameterTypeInferenceError{}, errors[1]) }) } diff --git a/runtime/tests/checker/arrays_dictionaries_test.go b/runtime/tests/checker/arrays_dictionaries_test.go index ce98e677f7..c713c42726 100644 --- a/runtime/tests/checker/arrays_dictionaries_test.go +++ b/runtime/tests/checker/arrays_dictionaries_test.go @@ -1280,7 +1280,8 @@ func TestCheckArrayMapInvalidArgs(t *testing.T) { `, []sema.SemanticError{ &sema.TypeMismatchError{}, - &sema.TypeParameterTypeInferenceError{}, // since we're not passing a function. + &sema.InvocationReturnTypeInferenceError{}, // since we're not passing a function. + &sema.TypeParameterTypeInferenceError{}, // since we're not passing a function. }, ) @@ -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.InvocationReturnTypeInferenceError{}, errs[0]) + assert.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1]) } func TestCheckArrayReferenceTypeInference(t *testing.T) { diff --git a/runtime/tests/checker/builtinfunctions_test.go b/runtime/tests/checker/builtinfunctions_test.go index c2ca8e39c6..e0259ddf99 100644 --- a/runtime/tests/checker/builtinfunctions_test.go +++ b/runtime/tests/checker/builtinfunctions_test.go @@ -430,6 +430,7 @@ func TestCheckRevertibleRandom(t *testing.T) { "missing type argument", `let rand = revertibleRandom()`, []error{ + &sema.InvocationReturnTypeInferenceError{}, &sema.TypeParameterTypeInferenceError{}, }, ) diff --git a/runtime/tests/checker/capability_test.go b/runtime/tests/checker/capability_test.go index a8899d567d..55cecebea2 100644 --- a/runtime/tests/checker/capability_test.go +++ b/runtime/tests/checker/capability_test.go @@ -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.InvocationReturnTypeInferenceError{}, errs[0]) + require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1]) }) for _, auth := range []sema.Access{sema.UnauthorizedAccess, diff --git a/runtime/tests/checker/conditions_test.go b/runtime/tests/checker/conditions_test.go index 74979ccc91..fb4b472064 100644 --- a/runtime/tests/checker/conditions_test.go +++ b/runtime/tests/checker/conditions_test.go @@ -265,10 +265,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.InvocationReturnTypeInferenceError{}, errs[1]) + assert.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[2]) }) t.Run("emit condition", func(t *testing.T) { @@ -284,10 +285,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.InvocationReturnTypeInferenceError{}, errs[1]) + assert.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[2]) }) } diff --git a/runtime/tests/checker/genericfunction_test.go b/runtime/tests/checker/genericfunction_test.go index ce52b291da..3c4786276c 100644 --- a/runtime/tests/checker/genericfunction_test.go +++ b/runtime/tests/checker/genericfunction_test.go @@ -452,9 +452,10 @@ func TestCheckGenericFunctionInvocation(t *testing.T) { }, ) - errs := RequireCheckerErrors(t, err, 1) + errs := RequireCheckerErrors(t, err, 2) - assert.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[0]) + assert.IsType(t, &sema.InvocationReturnTypeInferenceError{}, errs[0]) + assert.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1]) }) t.Run("valid: one type parameter, one type argument, no parameters, no arguments, return type", func(t *testing.T) { diff --git a/runtime/tests/checker/invalid_test.go b/runtime/tests/checker/invalid_test.go index 42fd6953f8..a2a7b00df6 100644 --- a/runtime/tests/checker/invalid_test.go +++ b/runtime/tests/checker/invalid_test.go @@ -23,7 +23,9 @@ import ( "github.com/stretchr/testify/assert" + "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/sema" + "github.com/onflow/cadence/runtime/stdlib" ) func TestCheckSpuriousIdentifierAssignmentInvalidValueTypeMismatch(t *testing.T) { @@ -201,3 +203,47 @@ func TestCheckSpuriousCastWithInvalidValueTypeMismatch(t *testing.T) { assert.IsType(t, &sema.NotDeclaredError{}, errs[0]) } + +func TestCheckInvalidInvocationFunctionReturnType(t *testing.T) { + + t.Parallel() + + typeParameter := &sema.TypeParameter{ + Name: "T", + } + + fType := &sema.FunctionType{ + TypeParameters: []*sema.TypeParameter{ + typeParameter, + }, + ReturnTypeAnnotation: sema.NewTypeAnnotation( + &sema.GenericType{ + TypeParameter: typeParameter, + }, + ), + } + + baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) + baseValueActivation.DeclareValue(stdlib.StandardLibraryValue{ + Type: fType, + Name: "f", + Kind: common.DeclarationKindFunction, + }) + + _, err := ParseAndCheckWithOptions(t, + ` + let res = [f].reverse() + `, + ParseAndCheckOptions{ + Config: &sema.Config{ + BaseValueActivationHandler: func(_ common.Location) *sema.VariableActivation { + return baseValueActivation + }, + }, + }, + ) + + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.InvocationReturnTypeInferenceError{}, errs[0]) +} From 6de6720fc5123a4495720439d55f2cc6b4736bb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 12 Nov 2024 10:41:00 -0800 Subject: [PATCH 02/11] report error when AST type cannot be converted --- runtime/sema/checker.go | 8 +++---- runtime/sema/errors.go | 28 +++++++++++++++--------- runtime/tests/checker/dictionary_test.go | 4 +++- 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/runtime/sema/checker.go b/runtime/sema/checker.go index 51d5204b44..3d3f23a4c1 100644 --- a/runtime/sema/checker.go +++ b/runtime/sema/checker.go @@ -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( diff --git a/runtime/sema/errors.go b/runtime/sema/errors.go index 9b770b051e..cfac1d6d86 100644 --- a/runtime/sema/errors.go +++ b/runtime/sema/errors.go @@ -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 { @@ -4843,3 +4833,21 @@ func (*InvocationReturnTypeInferenceError) IsUserError() {} func (e *InvocationReturnTypeInferenceError) Error() string { return "cannot infer return 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) +} diff --git a/runtime/tests/checker/dictionary_test.go b/runtime/tests/checker/dictionary_test.go index 086315d76c..e0efc29cce 100644 --- a/runtime/tests/checker/dictionary_test.go +++ b/runtime/tests/checker/dictionary_test.go @@ -40,7 +40,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{ From f52f6a92f75682aea3a9a3a87bd6d651d9417665 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 12 Nov 2024 10:53:02 -0800 Subject: [PATCH 03/11] add defensive check to reject expressions with invalid type, but no error reported --- runtime/sema/checker.go | 10 +++++++ runtime/tests/checker/invalid_test.go | 38 +++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/runtime/sema/checker.go b/runtime/sema/checker.go index 3d3f23a4c1..5f46537842 100644 --- a/runtime/sema/checker.go +++ b/runtime/sema/checker.go @@ -2611,6 +2611,16 @@ func (checker *Checker) visitExpressionWithForceType( actualType = ast.AcceptExpression[Type](expr, checker) + // 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() { + panic(errors.NewUnexpectedError("invalid type produced without error")) + } + if checker.Config.ExtendedElaborationEnabled { checker.Elaboration.SetExpressionTypes( expr, diff --git a/runtime/tests/checker/invalid_test.go b/runtime/tests/checker/invalid_test.go index a2a7b00df6..8d314fc89e 100644 --- a/runtime/tests/checker/invalid_test.go +++ b/runtime/tests/checker/invalid_test.go @@ -22,8 +22,10 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/errors" "github.com/onflow/cadence/runtime/sema" "github.com/onflow/cadence/runtime/stdlib" ) @@ -247,3 +249,39 @@ func TestCheckInvalidInvocationFunctionReturnType(t *testing.T) { assert.IsType(t, &sema.InvocationReturnTypeInferenceError{}, errs[0]) } + +func TestCheckInvalidTypeDefensiveCheck(t *testing.T) { + + t.Parallel() + + baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) + baseValueActivation.DeclareValue(stdlib.StandardLibraryValue{ + Type: sema.InvalidType, + Name: "invalid", + Kind: common.DeclarationKindConstant, + }) + + var r any + func() { + defer func() { + r = recover() + }() + + _, _ = ParseAndCheckWithOptions(t, + ` + let res = invalid + `, + ParseAndCheckOptions{ + Config: &sema.Config{ + BaseValueActivationHandler: func(_ common.Location) *sema.VariableActivation { + return baseValueActivation + }, + }, + }, + ) + }() + + require.IsType(t, errors.UnexpectedError{}, r) + err := r.(errors.UnexpectedError) + require.ErrorContains(t, err, "invalid type produced without error") +} From 1ec7b3c0b8007dcf487caed8997faec1ed034e63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 12 Nov 2024 11:19:06 -0800 Subject: [PATCH 04/11] report invalid type indexing error before returning invalid type --- runtime/sema/check_expression.go | 31 +++++++++++++++------------ runtime/tests/checker/invalid_test.go | 15 +++++++++++++ 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/runtime/sema/check_expression.go b/runtime/sema/check_expression.go index 233127c3af..6a863c8be8 100644 --- a/runtime/sema/check_expression.go +++ b/runtime/sema/check_expression.go @@ -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) @@ -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 } diff --git a/runtime/tests/checker/invalid_test.go b/runtime/tests/checker/invalid_test.go index 8d314fc89e..a06ddeaee6 100644 --- a/runtime/tests/checker/invalid_test.go +++ b/runtime/tests/checker/invalid_test.go @@ -285,3 +285,18 @@ func TestCheckInvalidTypeDefensiveCheck(t *testing.T) { err := r.(errors.UnexpectedError) require.ErrorContains(t, err, "invalid type produced without error") } + +func TestCheckInvalidTypeIndexing(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + struct S {} + let s = S() + let res = s[[]] + `) + + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.InvalidTypeIndexingError{}, errs[0]) +} From 11fe59afd2c890288b9a352a22df822304e87942 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 12 Nov 2024 11:28:56 -0800 Subject: [PATCH 05/11] add test for remove with undeclared type --- runtime/tests/checker/invalid_test.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/runtime/tests/checker/invalid_test.go b/runtime/tests/checker/invalid_test.go index a06ddeaee6..e9f5fbc06f 100644 --- a/runtime/tests/checker/invalid_test.go +++ b/runtime/tests/checker/invalid_test.go @@ -300,3 +300,23 @@ func TestCheckInvalidTypeIndexing(t *testing.T) { assert.IsType(t, &sema.InvalidTypeIndexingError{}, errs[0]) } + +func TestCheckInvalidRemove(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + struct S {} + + attachment A for S {} + + fun test() { + let s = S() + remove B from s + } + `) + + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.NotDeclaredError{}, errs[0]) +} From 4be6dea1fe27e3c940b82a6b298e06659533b43e Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Tue, 12 Nov 2024 12:00:57 -0800 Subject: [PATCH 06/11] Add test for invalid generic type --- runtime/tests/checker/invocation_test.go | 58 ++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/runtime/tests/checker/invocation_test.go b/runtime/tests/checker/invocation_test.go index b55d20d668..c1a1e442b3 100644 --- a/runtime/tests/checker/invocation_test.go +++ b/runtime/tests/checker/invocation_test.go @@ -592,3 +592,61 @@ func TestCheckArgumentLabels(t *testing.T) { }) } + +func TestCheckInvocationWithIncorrectTypeParameter(t *testing.T) { + + t.Parallel() + + // function type has incorrect type-arguments: + // `fun Foo(_ a: R)` + // + funcType := &sema.FunctionType{ + ReturnTypeAnnotation: sema.VoidTypeAnnotation, + TypeParameters: []*sema.TypeParameter{ + { + Name: "T", + TypeBound: sema.AnyStructType, + }, + }, + Parameters: []sema.Parameter{ + { + Label: sema.ArgumentLabelNotRequired, + Identifier: "a", + TypeAnnotation: sema.NewTypeAnnotation( + &sema.GenericType{ + TypeParameter: &sema.TypeParameter{ + Name: "R", // This is an incorrect/undefined type-parameter + TypeBound: sema.AnyStructType, + }, + }, + ), + }, + }, + IsConstructor: false, + } + + baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) + baseValueActivation.DeclareValue(stdlib.NewStandardLibraryStaticFunction( + "foo", + funcType, + "", + nil, // no need, we only type-check + )) + + _, err := ParseAndCheckWithOptions(t, + ` + access(all) fun test() { + foo("hello") + } + `, + ParseAndCheckOptions{ + Config: &sema.Config{ + BaseValueActivationHandler: func(_ common.Location) *sema.VariableActivation { + return baseValueActivation + }, + }, + }, + ) + + require.Error(t, err) +} From a1f9e8be84876556280c75e0b228b369d153020f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 12 Nov 2024 12:40:43 -0800 Subject: [PATCH 07/11] report error when parameter type in invocation cannot be inferred --- runtime/runtime_test.go | 2 +- runtime/sema/check_invocation_expression.go | 14 +++++++++++++- runtime/sema/errors.go | 16 ++++++++-------- runtime/tests/checker/account_test.go | 18 +++++++++--------- .../tests/checker/arrays_dictionaries_test.go | 6 +++--- runtime/tests/checker/builtinfunctions_test.go | 2 +- runtime/tests/checker/capability_test.go | 2 +- runtime/tests/checker/conditions_test.go | 4 ++-- runtime/tests/checker/genericfunction_test.go | 2 +- runtime/tests/checker/invalid_test.go | 2 +- runtime/tests/checker/invocation_test.go | 5 +++-- 11 files changed, 43 insertions(+), 30 deletions(-) diff --git a/runtime/runtime_test.go b/runtime/runtime_test.go index 2d79c012c4..5efbfe7dab 100644 --- a/runtime/runtime_test.go +++ b/runtime/runtime_test.go @@ -11588,6 +11588,6 @@ func TestRuntimeInvocationReturnTypeInferenceFailure(t *testing.T) { ) RequireError(t, err) - var typeErr *sema.InvocationReturnTypeInferenceError + var typeErr *sema.InvocationTypeInferenceError require.ErrorAs(t, err, &typeErr) } diff --git a/runtime/sema/check_invocation_expression.go b/runtime/sema/check_invocation_expression.go index 3967fd06a9..bbf45de72d 100644 --- a/runtime/sema/check_invocation_expression.go +++ b/runtime/sema/check_invocation_expression.go @@ -503,7 +503,7 @@ func (checker *Checker) checkInvocation( returnType = functionType.ReturnTypeAnnotation.Type.Resolve(typeArguments) if returnType == nil { - checker.report(&InvocationReturnTypeInferenceError{ + checker.report(&InvocationTypeInferenceError{ Range: ast.NewRangeFromPositioned( checker.memoryGauge, invocationExpression, @@ -605,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 } } @@ -680,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 } } diff --git a/runtime/sema/errors.go b/runtime/sema/errors.go index cfac1d6d86..163c5ec5ba 100644 --- a/runtime/sema/errors.go +++ b/runtime/sema/errors.go @@ -4817,21 +4817,21 @@ func (e *NestedReferenceError) Error() string { return fmt.Sprintf("cannot create a nested reference to value of type %s", e.Type.QualifiedString()) } -// InvocationReturnTypeInferenceError +// InvocationTypeInferenceError -type InvocationReturnTypeInferenceError struct { +type InvocationTypeInferenceError struct { ast.Range } -var _ SemanticError = &InvocationReturnTypeInferenceError{} -var _ errors.UserError = &InvocationReturnTypeInferenceError{} +var _ SemanticError = &InvocationTypeInferenceError{} +var _ errors.UserError = &InvocationTypeInferenceError{} -func (e *InvocationReturnTypeInferenceError) isSemanticError() {} +func (e *InvocationTypeInferenceError) isSemanticError() {} -func (*InvocationReturnTypeInferenceError) IsUserError() {} +func (*InvocationTypeInferenceError) IsUserError() {} -func (e *InvocationReturnTypeInferenceError) Error() string { - return "cannot infer return type of invocation" +func (e *InvocationTypeInferenceError) Error() string { + return "cannot infer type of invocation" } // UnconvertableTypeError diff --git a/runtime/tests/checker/account_test.go b/runtime/tests/checker/account_test.go index ac72e777fc..c8c5ea0cf8 100644 --- a/runtime/tests/checker/account_test.go +++ b/runtime/tests/checker/account_test.go @@ -426,14 +426,14 @@ func TestCheckAccountStorageLoad(t *testing.T) { if domain == common.PathDomainStorage { errs := RequireCheckerErrors(t, err, 2) - require.IsType(t, &sema.InvocationReturnTypeInferenceError{}, errs[0]) + require.IsType(t, &sema.InvocationTypeInferenceError{}, errs[0]) require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1]) } else { errs := RequireCheckerErrors(t, err, 3) require.IsType(t, &sema.TypeMismatchError{}, errs[0]) - require.IsType(t, &sema.InvocationReturnTypeInferenceError{}, errs[1]) + require.IsType(t, &sema.InvocationTypeInferenceError{}, errs[1]) require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[2]) } }) @@ -556,14 +556,14 @@ func TestCheckAccountStorageCopy(t *testing.T) { if domain == common.PathDomainStorage { errs := RequireCheckerErrors(t, err, 2) - require.IsType(t, &sema.InvocationReturnTypeInferenceError{}, errs[0]) + require.IsType(t, &sema.InvocationTypeInferenceError{}, errs[0]) require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1]) } else { errs := RequireCheckerErrors(t, err, 3) require.IsType(t, &sema.TypeMismatchError{}, errs[0]) - require.IsType(t, &sema.InvocationReturnTypeInferenceError{}, errs[1]) + require.IsType(t, &sema.InvocationTypeInferenceError{}, errs[1]) require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[2]) } }) @@ -692,14 +692,14 @@ func TestCheckAccountStorageBorrow(t *testing.T) { if domain == common.PathDomainStorage { errs := RequireCheckerErrors(t, err, 2) - require.IsType(t, &sema.InvocationReturnTypeInferenceError{}, errs[0]) + require.IsType(t, &sema.InvocationTypeInferenceError{}, errs[0]) require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1]) } else { errs := RequireCheckerErrors(t, err, 3) require.IsType(t, &sema.TypeMismatchError{}, errs[0]) - require.IsType(t, &sema.InvocationReturnTypeInferenceError{}, errs[1]) + require.IsType(t, &sema.InvocationTypeInferenceError{}, errs[1]) require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[2]) } }) @@ -722,14 +722,14 @@ func TestCheckAccountStorageBorrow(t *testing.T) { if domain == common.PathDomainStorage { errs := RequireCheckerErrors(t, err, 2) - require.IsType(t, &sema.InvocationReturnTypeInferenceError{}, errs[0]) + require.IsType(t, &sema.InvocationTypeInferenceError{}, errs[0]) require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1]) } else { errs := RequireCheckerErrors(t, err, 3) require.IsType(t, &sema.TypeMismatchError{}, errs[0]) - require.IsType(t, &sema.InvocationReturnTypeInferenceError{}, errs[1]) + require.IsType(t, &sema.InvocationTypeInferenceError{}, errs[1]) require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[2]) } }) @@ -1036,7 +1036,7 @@ func TestCheckAccountContractsBorrow(t *testing.T) { errors := RequireCheckerErrors(t, err, 2) - assert.IsType(t, &sema.InvocationReturnTypeInferenceError{}, errors[0]) + assert.IsType(t, &sema.InvocationTypeInferenceError{}, errors[0]) assert.IsType(t, &sema.TypeParameterTypeInferenceError{}, errors[1]) }) } diff --git a/runtime/tests/checker/arrays_dictionaries_test.go b/runtime/tests/checker/arrays_dictionaries_test.go index c713c42726..3952c1aa2b 100644 --- a/runtime/tests/checker/arrays_dictionaries_test.go +++ b/runtime/tests/checker/arrays_dictionaries_test.go @@ -1280,8 +1280,8 @@ func TestCheckArrayMapInvalidArgs(t *testing.T) { `, []sema.SemanticError{ &sema.TypeMismatchError{}, - &sema.InvocationReturnTypeInferenceError{}, // since we're not passing a function. - &sema.TypeParameterTypeInferenceError{}, // since we're not passing a function. + &sema.InvocationTypeInferenceError{}, // since we're not passing a function. + &sema.TypeParameterTypeInferenceError{}, // since we're not passing a function. }, ) @@ -2662,7 +2662,7 @@ func TestCheckArrayToConstantSizedMissingTypeArgument(t *testing.T) { errs := RequireCheckerErrors(t, err, 2) - assert.IsType(t, &sema.InvocationReturnTypeInferenceError{}, errs[0]) + assert.IsType(t, &sema.InvocationTypeInferenceError{}, errs[0]) assert.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1]) } diff --git a/runtime/tests/checker/builtinfunctions_test.go b/runtime/tests/checker/builtinfunctions_test.go index e0259ddf99..79c931d13c 100644 --- a/runtime/tests/checker/builtinfunctions_test.go +++ b/runtime/tests/checker/builtinfunctions_test.go @@ -430,7 +430,7 @@ func TestCheckRevertibleRandom(t *testing.T) { "missing type argument", `let rand = revertibleRandom()`, []error{ - &sema.InvocationReturnTypeInferenceError{}, + &sema.InvocationTypeInferenceError{}, &sema.TypeParameterTypeInferenceError{}, }, ) diff --git a/runtime/tests/checker/capability_test.go b/runtime/tests/checker/capability_test.go index 55cecebea2..a842ed161f 100644 --- a/runtime/tests/checker/capability_test.go +++ b/runtime/tests/checker/capability_test.go @@ -87,7 +87,7 @@ func TestCheckCapability_borrow(t *testing.T) { errs := RequireCheckerErrors(t, err, 2) - require.IsType(t, &sema.InvocationReturnTypeInferenceError{}, errs[0]) + require.IsType(t, &sema.InvocationTypeInferenceError{}, errs[0]) require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1]) }) diff --git a/runtime/tests/checker/conditions_test.go b/runtime/tests/checker/conditions_test.go index fb4b472064..cfe5f73fab 100644 --- a/runtime/tests/checker/conditions_test.go +++ b/runtime/tests/checker/conditions_test.go @@ -268,7 +268,7 @@ func TestCheckInvalidFunctionPostConditionWithBeforeAndNoArgument(t *testing.T) errs := RequireCheckerErrors(t, err, 3) assert.IsType(t, &sema.InsufficientArgumentsError{}, errs[0]) - assert.IsType(t, &sema.InvocationReturnTypeInferenceError{}, errs[1]) + assert.IsType(t, &sema.InvocationTypeInferenceError{}, errs[1]) assert.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[2]) }) @@ -288,7 +288,7 @@ func TestCheckInvalidFunctionPostConditionWithBeforeAndNoArgument(t *testing.T) errs := RequireCheckerErrors(t, err, 3) assert.IsType(t, &sema.InsufficientArgumentsError{}, errs[0]) - assert.IsType(t, &sema.InvocationReturnTypeInferenceError{}, errs[1]) + assert.IsType(t, &sema.InvocationTypeInferenceError{}, errs[1]) assert.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[2]) }) } diff --git a/runtime/tests/checker/genericfunction_test.go b/runtime/tests/checker/genericfunction_test.go index 3c4786276c..f7f89c87cf 100644 --- a/runtime/tests/checker/genericfunction_test.go +++ b/runtime/tests/checker/genericfunction_test.go @@ -454,7 +454,7 @@ func TestCheckGenericFunctionInvocation(t *testing.T) { errs := RequireCheckerErrors(t, err, 2) - assert.IsType(t, &sema.InvocationReturnTypeInferenceError{}, errs[0]) + assert.IsType(t, &sema.InvocationTypeInferenceError{}, errs[0]) assert.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1]) }) diff --git a/runtime/tests/checker/invalid_test.go b/runtime/tests/checker/invalid_test.go index e9f5fbc06f..3ed32e3205 100644 --- a/runtime/tests/checker/invalid_test.go +++ b/runtime/tests/checker/invalid_test.go @@ -247,7 +247,7 @@ func TestCheckInvalidInvocationFunctionReturnType(t *testing.T) { errs := RequireCheckerErrors(t, err, 1) - assert.IsType(t, &sema.InvocationReturnTypeInferenceError{}, errs[0]) + assert.IsType(t, &sema.InvocationTypeInferenceError{}, errs[0]) } func TestCheckInvalidTypeDefensiveCheck(t *testing.T) { diff --git a/runtime/tests/checker/invocation_test.go b/runtime/tests/checker/invocation_test.go index c1a1e442b3..3d0d8f6949 100644 --- a/runtime/tests/checker/invocation_test.go +++ b/runtime/tests/checker/invocation_test.go @@ -622,7 +622,6 @@ func TestCheckInvocationWithIncorrectTypeParameter(t *testing.T) { ), }, }, - IsConstructor: false, } baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) @@ -648,5 +647,7 @@ func TestCheckInvocationWithIncorrectTypeParameter(t *testing.T) { }, ) - require.Error(t, err) + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.InvocationTypeInferenceError{}, errs[0]) } From 39f6655dd835792dc9669802c674f80dc2e9982c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 12 Nov 2024 12:41:53 -0800 Subject: [PATCH 08/11] improve defensive check Co-authored-by: Supun Setunga --- runtime/sema/checker.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/sema/checker.go b/runtime/sema/checker.go index 5f46537842..9747258d9c 100644 --- a/runtime/sema/checker.go +++ b/runtime/sema/checker.go @@ -2617,7 +2617,7 @@ func (checker *Checker) visitExpressionWithForceType( // Check for errors first, which is cheap, // before checking for an invalid type, which is more expensive. - if len(checker.errors) == 0 && actualType.IsInvalidType() { + if len(checker.errors) == 0 && (actualType.IsInvalidType() || (expectedType != nil && expectedType.IsInvalidType())) { panic(errors.NewUnexpectedError("invalid type produced without error")) } From d1d5626290f2fe48d4f0d420011ca594ff1e5057 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 12 Nov 2024 12:43:49 -0800 Subject: [PATCH 09/11] refactor defensive check into function --- runtime/sema/checker.go | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/runtime/sema/checker.go b/runtime/sema/checker.go index 9747258d9c..fa3486715a 100644 --- a/runtime/sema/checker.go +++ b/runtime/sema/checker.go @@ -2611,15 +2611,7 @@ func (checker *Checker) visitExpressionWithForceType( actualType = ast.AcceptExpression[Type](expr, checker) - // 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")) - } + checker.checkErrorsForInvalidExpressionTypes(actualType, expectedType) if checker.Config.ExtendedElaborationEnabled { checker.Elaboration.SetExpressionTypes( @@ -2655,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( From b4f27e1b5dba3a56cf2b49cd6b4e64abdbe229bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Wed, 13 Nov 2024 12:10:25 -0800 Subject: [PATCH 10/11] gate new behaviour (errors for all invalid type cases) behind a feature flag --- runtime/environment.go | 9 +- runtime/runtime_test.go | 85 ++++--- runtime/sema/check_invocation_expression.go | 42 ++-- runtime/sema/checker.go | 16 +- runtime/sema/config.go | 2 + runtime/sema/errors.go | 10 + runtime/tests/checker/account_test.go | 215 +++++++++++++----- .../tests/checker/arrays_dictionaries_test.go | 127 +++++++---- .../tests/checker/builtinfunctions_test.go | 2 +- runtime/tests/checker/capability_test.go | 50 +++- runtime/tests/checker/conditions_test.go | 113 ++++++--- runtime/tests/checker/dictionary_test.go | 58 +++-- runtime/tests/checker/genericfunction_test.go | 78 +++++-- runtime/tests/checker/invalid_test.go | 164 ++++++++----- runtime/tests/checker/invocation_test.go | 117 ++++++---- runtime/tests/checker/utils.go | 2 + 16 files changed, 750 insertions(+), 340 deletions(-) diff --git a/runtime/environment.go b/runtime/environment.go index f2607eb5bd..bcf2298df7 100644 --- a/runtime/environment.go +++ b/runtime/environment.go @@ -22,6 +22,7 @@ import ( "time" "go.opentelemetry.io/otel/attribute" + "golang.org/x/mod/semver" "github.com/onflow/cadence" "github.com/onflow/cadence/runtime/activations" @@ -1461,6 +1462,9 @@ func (e *interpreterEnvironment) newValidateAccountCapabilitiesPublishHandler() } } +// TODO: +const MinimumRequiredVersionForInvalidTypeErrorFixes = "v1.0.3" + func (e *interpreterEnvironment) configureVersionedFeatures() { var ( minimumRequiredVersion string @@ -1473,6 +1477,7 @@ func (e *interpreterEnvironment) configureVersionedFeatures() { panic(err) } - // No feature flags yet - _ = minimumRequiredVersion + e.CheckerConfig.InvalidTypeErrorFixesEnabled = + minimumRequiredVersion != "" && + semver.Compare(MinimumRequiredVersionForInvalidTypeErrorFixes, minimumRequiredVersion) >= 0 } diff --git a/runtime/runtime_test.go b/runtime/runtime_test.go index 5efbfe7dab..647ed2116b 100644 --- a/runtime/runtime_test.go +++ b/runtime/runtime_test.go @@ -11553,41 +11553,66 @@ func TestRuntimeInvocationReturnTypeInferenceFailure(t *testing.T) { t.Parallel() - address := common.MustBytesToAddress([]byte{0x1}) + test := func(invalidTypeErrorFixesEnabled bool) { - newRuntimeInterface := func() Interface { + name := fmt.Sprintf( + "error fixes enabled: %v", + invalidTypeErrorFixesEnabled, + ) - return &TestRuntimeInterface{ - Storage: NewTestLedger(nil, nil), - OnGetSigningAccounts: func() ([]common.Address, error) { - return []common.Address{address}, nil - }, - } - } + t.Run(name, func(t *testing.T) { + t.Parallel() - runtime := NewTestInterpreterRuntime() + address := common.MustBytesToAddress([]byte{0x1}) - nextTransactionLocation := NewTransactionLocationGenerator() + runtimeInterface := &TestRuntimeInterface{ + Storage: NewTestLedger(nil, nil), + OnGetSigningAccounts: func() ([]common.Address, error) { + return []common.Address{address}, nil + }, + OnMinimumRequiredVersion: func() (string, error) { + if invalidTypeErrorFixesEnabled { + return MinimumRequiredVersionForInvalidTypeErrorFixes, nil + } + return "", nil + }, + } - tx := []byte(` - transaction{ - prepare(signer: auth(Storage) &Account){ - let functions = [signer.storage.save].reverse() - } - } - `) + runtime := NewTestInterpreterRuntime() - err := runtime.ExecuteTransaction( - Script{ - Source: tx, - }, - Context{ - Interface: newRuntimeInterface(), - Location: nextTransactionLocation(), - }, - ) - RequireError(t, err) + nextTransactionLocation := NewTransactionLocationGenerator() - var typeErr *sema.InvocationTypeInferenceError - require.ErrorAs(t, err, &typeErr) + tx := []byte(` + transaction{ + prepare(signer: auth(Storage) &Account){ + let functions = [signer.storage.save].reverse() + } + } + `) + + err := runtime.ExecuteTransaction( + Script{ + Source: tx, + }, + Context{ + Interface: runtimeInterface, + Location: nextTransactionLocation(), + }, + ) + + RequireError(t, err) + + if invalidTypeErrorFixesEnabled { + var typeErr *sema.InvocationTypeInferenceError + require.ErrorAs(t, err, &typeErr) + } else { + var transferErr interpreter.ValueTransferTypeError + require.ErrorAs(t, err, &transferErr) + } + }) + } + + for _, invalidTypeErrorFixesEnabled := range []bool{true, false} { + test(invalidTypeErrorFixesEnabled) + } } diff --git a/runtime/sema/check_invocation_expression.go b/runtime/sema/check_invocation_expression.go index bbf45de72d..9c91033e75 100644 --- a/runtime/sema/check_invocation_expression.go +++ b/runtime/sema/check_invocation_expression.go @@ -503,12 +503,14 @@ func (checker *Checker) checkInvocation( returnType = functionType.ReturnTypeAnnotation.Type.Resolve(typeArguments) if returnType == nil { - checker.report(&InvocationTypeInferenceError{ - Range: ast.NewRangeFromPositioned( - checker.memoryGauge, - invocationExpression, - ), - }) + if checker.Config.InvalidTypeErrorFixesEnabled { + checker.report(&InvocationTypeInferenceError{ + Range: ast.NewRangeFromPositioned( + checker.memoryGauge, + invocationExpression, + ), + }) + } returnType = InvalidType } @@ -605,12 +607,14 @@ 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, - ), - }) + if checker.Config.InvalidTypeErrorFixesEnabled { + checker.report(&InvocationTypeInferenceError{ + Range: ast.NewRangeFromPositioned( + checker.memoryGauge, + argument.Expression, + ), + }) + } parameterType = InvalidType } } @@ -686,12 +690,14 @@ 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, - ), - }) + if checker.Config.InvalidTypeErrorFixesEnabled { + checker.report(&InvocationTypeInferenceError{ + Range: ast.NewRangeFromPositioned( + checker.memoryGauge, + argument.Expression, + ), + }) + } parameterType = InvalidType } } diff --git a/runtime/sema/checker.go b/runtime/sema/checker.go index fa3486715a..9fa38951f8 100644 --- a/runtime/sema/checker.go +++ b/runtime/sema/checker.go @@ -886,12 +886,16 @@ func (checker *Checker) ConvertType(t ast.Type) Type { case *ast.InstantiationType: return checker.convertInstantiationType(t) - default: - checker.report(&UnconvertableTypeError{ - Range: ast.NewRangeFromPositioned(checker.memoryGauge, t), - }) + case nil: + if checker.Config.InvalidTypeErrorFixesEnabled { + checker.report(&UnconvertableTypeError{ + Range: ast.NewRangeFromPositioned(checker.memoryGauge, t), + }) + } return InvalidType } + + panic(&astTypeConversionError{invalidASTType: t}) } func CheckIntersectionType( @@ -2611,7 +2615,9 @@ func (checker *Checker) visitExpressionWithForceType( actualType = ast.AcceptExpression[Type](expr, checker) - checker.checkErrorsForInvalidExpressionTypes(actualType, expectedType) + if checker.Config.InvalidTypeErrorFixesEnabled { + checker.checkErrorsForInvalidExpressionTypes(actualType, expectedType) + } if checker.Config.ExtendedElaborationEnabled { checker.Elaboration.SetExpressionTypes( diff --git a/runtime/sema/config.go b/runtime/sema/config.go index 06f6e9ce95..28c19feea5 100644 --- a/runtime/sema/config.go +++ b/runtime/sema/config.go @@ -55,4 +55,6 @@ type Config struct { AllowStaticDeclarations bool // AttachmentsEnabled determines if attachments are enabled AttachmentsEnabled bool + // InvalidTypeErrorFixesEnabled determines if errors for invalid type errors are reported + InvalidTypeErrorFixesEnabled bool } diff --git a/runtime/sema/errors.go b/runtime/sema/errors.go index 163c5ec5ba..04b32afeb0 100644 --- a/runtime/sema/errors.go +++ b/runtime/sema/errors.go @@ -51,6 +51,16 @@ 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 { diff --git a/runtime/tests/checker/account_test.go b/runtime/tests/checker/account_test.go index c8c5ea0cf8..d6d4c0b65d 100644 --- a/runtime/tests/checker/account_test.go +++ b/runtime/tests/checker/account_test.go @@ -401,18 +401,19 @@ func TestCheckAccountStorageLoad(t *testing.T) { require.IsType(t, &sema.InvalidAccessError{}, errs[0]) }) - testMissingTypeArguments := func(domain common.PathDomain) { + testMissingTypeArguments := func(domain common.PathDomain, invalidTypeErrorFixesEnabled bool) { testName := fmt.Sprintf( - "missing type argument, %s", + "missing type argument, %s, %v", domain.Identifier(), + invalidTypeErrorFixesEnabled, ) t.Run(testName, func(t *testing.T) { t.Parallel() - _, err := ParseAndCheck(t, + _, err := ParseAndCheckWithOptions(t, fmt.Sprintf( ` fun test(storage: auth(Storage) &Account.Storage) { @@ -421,20 +422,38 @@ func TestCheckAccountStorageLoad(t *testing.T) { `, domain.Identifier(), ), + ParseAndCheckOptions{ + Config: &sema.Config{ + InvalidTypeErrorFixesEnabled: invalidTypeErrorFixesEnabled, + }, + }, ) if domain == common.PathDomainStorage { - errs := RequireCheckerErrors(t, err, 2) + if invalidTypeErrorFixesEnabled { + errs := RequireCheckerErrors(t, err, 2) + + require.IsType(t, &sema.InvocationTypeInferenceError{}, errs[0]) + require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1]) + } else { + errs := RequireCheckerErrors(t, err, 1) - require.IsType(t, &sema.InvocationTypeInferenceError{}, errs[0]) - require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1]) + require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[0]) + } } else { - errs := RequireCheckerErrors(t, err, 3) + if invalidTypeErrorFixesEnabled { + errs := RequireCheckerErrors(t, err, 3) - require.IsType(t, &sema.TypeMismatchError{}, errs[0]) - require.IsType(t, &sema.InvocationTypeInferenceError{}, errs[1]) - require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[2]) + require.IsType(t, &sema.TypeMismatchError{}, errs[0]) + require.IsType(t, &sema.InvocationTypeInferenceError{}, errs[1]) + require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[2]) + } else { + errs := RequireCheckerErrors(t, err, 2) + + require.IsType(t, &sema.TypeMismatchError{}, errs[0]) + require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1]) + } } }) } @@ -506,7 +525,9 @@ func TestCheckAccountStorageLoad(t *testing.T) { } for _, domain := range common.AllPathDomainsByIdentifier { - testMissingTypeArguments(domain) + for _, invalidTypeErrorFixesEnabled := range []bool{false, true} { + testMissingTypeArguments(domain, invalidTypeErrorFixesEnabled) + } testExplicitTypeArgument(domain) } } @@ -529,18 +550,19 @@ func TestCheckAccountStorageCopy(t *testing.T) { require.IsType(t, &sema.InvalidAccessError{}, errs[0]) }) - testMissingTypeArgument := func(domain common.PathDomain) { + testMissingTypeArgument := func(domain common.PathDomain, invalidTypeErrorFixesEnabled bool) { testName := fmt.Sprintf( - "missing type argument, %s", + "missing type argument, %s, %v", domain.Identifier(), + invalidTypeErrorFixesEnabled, ) t.Run(testName, func(t *testing.T) { t.Parallel() - _, err := ParseAndCheck(t, + _, err := ParseAndCheckWithOptions(t, fmt.Sprintf( ` struct S {} @@ -551,20 +573,39 @@ func TestCheckAccountStorageCopy(t *testing.T) { `, domain.Identifier(), ), + ParseAndCheckOptions{ + Config: &sema.Config{ + InvalidTypeErrorFixesEnabled: invalidTypeErrorFixesEnabled, + }, + }, ) if domain == common.PathDomainStorage { - errs := RequireCheckerErrors(t, err, 2) - require.IsType(t, &sema.InvocationTypeInferenceError{}, errs[0]) - require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1]) + if invalidTypeErrorFixesEnabled { + errs := RequireCheckerErrors(t, err, 2) + + require.IsType(t, &sema.InvocationTypeInferenceError{}, errs[0]) + require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1]) + } else { + errs := RequireCheckerErrors(t, err, 1) + + require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[0]) + } } else { - errs := RequireCheckerErrors(t, err, 3) + if invalidTypeErrorFixesEnabled { + errs := RequireCheckerErrors(t, err, 3) - require.IsType(t, &sema.TypeMismatchError{}, errs[0]) - require.IsType(t, &sema.InvocationTypeInferenceError{}, errs[1]) - require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[2]) + require.IsType(t, &sema.TypeMismatchError{}, errs[0]) + require.IsType(t, &sema.InvocationTypeInferenceError{}, errs[1]) + require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[2]) + } else { + errs := RequireCheckerErrors(t, err, 2) + + require.IsType(t, &sema.TypeMismatchError{}, errs[0]) + require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1]) + } } }) } @@ -640,7 +681,9 @@ func TestCheckAccountStorageCopy(t *testing.T) { } for _, domain := range common.AllPathDomainsByIdentifier { - testMissingTypeArgument(domain) + for _, invalidTypeErrorFixesEnabled := range []bool{false, true} { + testMissingTypeArgument(domain, invalidTypeErrorFixesEnabled) + } testExplicitTypeArgument(domain) } } @@ -663,11 +706,12 @@ func TestCheckAccountStorageBorrow(t *testing.T) { require.IsType(t, &sema.InvalidAccessError{}, errs[0]) }) - testMissingTypeArgument := func(domain common.PathDomain) { + testMissingTypeArgument := func(domain common.PathDomain, invalidTypeErrorFixesEnabled bool) { testName := fmt.Sprintf( - "missing type argument, %s", + "missing type argument, %s, %v", domain.Identifier(), + invalidTypeErrorFixesEnabled, ) t.Run(testName, func(t *testing.T) { @@ -678,7 +722,7 @@ func TestCheckAccountStorageBorrow(t *testing.T) { t.Parallel() - _, err := ParseAndCheck(t, + _, err := ParseAndCheckWithOptions(t, fmt.Sprintf( ` fun test(storage: auth(Storage) &Account.Storage) { @@ -687,20 +731,38 @@ func TestCheckAccountStorageBorrow(t *testing.T) { `, domain.Identifier(), ), + ParseAndCheckOptions{ + Config: &sema.Config{ + InvalidTypeErrorFixesEnabled: invalidTypeErrorFixesEnabled, + }, + }, ) if domain == common.PathDomainStorage { - errs := RequireCheckerErrors(t, err, 2) + if invalidTypeErrorFixesEnabled { + errs := RequireCheckerErrors(t, err, 2) - require.IsType(t, &sema.InvocationTypeInferenceError{}, errs[0]) - require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1]) + require.IsType(t, &sema.InvocationTypeInferenceError{}, errs[0]) + require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1]) + } else { + errs := RequireCheckerErrors(t, err, 1) - } else { - errs := RequireCheckerErrors(t, err, 3) + require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[0]) + } - require.IsType(t, &sema.TypeMismatchError{}, errs[0]) - require.IsType(t, &sema.InvocationTypeInferenceError{}, errs[1]) - require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[2]) + } else { + if invalidTypeErrorFixesEnabled { + errs := RequireCheckerErrors(t, err, 3) + + require.IsType(t, &sema.TypeMismatchError{}, errs[0]) + require.IsType(t, &sema.InvocationTypeInferenceError{}, errs[1]) + require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[2]) + } else { + errs := RequireCheckerErrors(t, err, 2) + + require.IsType(t, &sema.TypeMismatchError{}, errs[0]) + require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1]) + } } }) @@ -708,7 +770,7 @@ func TestCheckAccountStorageBorrow(t *testing.T) { t.Parallel() - _, err := ParseAndCheck(t, + _, err := ParseAndCheckWithOptions(t, fmt.Sprintf( ` fun test(storage: auth(Storage) &Account.Storage) { @@ -717,20 +779,38 @@ func TestCheckAccountStorageBorrow(t *testing.T) { `, domain.Identifier(), ), + ParseAndCheckOptions{ + Config: &sema.Config{ + InvalidTypeErrorFixesEnabled: invalidTypeErrorFixesEnabled, + }, + }, ) if domain == common.PathDomainStorage { - errs := RequireCheckerErrors(t, err, 2) + if invalidTypeErrorFixesEnabled { + errs := RequireCheckerErrors(t, err, 2) - require.IsType(t, &sema.InvocationTypeInferenceError{}, errs[0]) - require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1]) + require.IsType(t, &sema.InvocationTypeInferenceError{}, errs[0]) + require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1]) + } else { + errs := RequireCheckerErrors(t, err, 1) - } else { - errs := RequireCheckerErrors(t, err, 3) + require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[0]) + } - require.IsType(t, &sema.TypeMismatchError{}, errs[0]) - require.IsType(t, &sema.InvocationTypeInferenceError{}, errs[1]) - require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[2]) + } else { + if invalidTypeErrorFixesEnabled { + errs := RequireCheckerErrors(t, err, 3) + + require.IsType(t, &sema.TypeMismatchError{}, errs[0]) + require.IsType(t, &sema.InvocationTypeInferenceError{}, errs[1]) + require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[2]) + } else { + errs := RequireCheckerErrors(t, err, 2) + + require.IsType(t, &sema.TypeMismatchError{}, errs[0]) + require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1]) + } } }) }) @@ -888,7 +968,9 @@ func TestCheckAccountStorageBorrow(t *testing.T) { } for _, domain := range common.AllPathDomainsByIdentifier { - testMissingTypeArgument(domain) + for _, invalidTypeErrorFixesEnabled := range []bool{false, true} { + testMissingTypeArgument(domain, invalidTypeErrorFixesEnabled) + } for _, auth := range []sema.Access{ sema.UnauthorizedAccess, @@ -1023,22 +1105,45 @@ func TestCheckAccountContractsBorrow(t *testing.T) { require.NoError(t, err) }) - t.Run("invalid borrow contract: missing type argument", func(t *testing.T) { - t.Parallel() + testInvalidBorrowContractMissingTypeArgument := func(t *testing.T, invalidTypeErrorFixesEnabled bool) { + name := fmt.Sprintf( + "invalid borrow contract: missing type argument, error fixes enabled: %v", + invalidTypeErrorFixesEnabled, + ) + t.Run(name, func(t *testing.T) { + t.Parallel() - _, err := ParseAndCheck(t, ` - contract C {} + _, err := ParseAndCheckWithOptions(t, + ` + contract C {} - fun test(contracts: &Account.Contracts): &AnyStruct { - return contracts.borrow(name: "foo")! - } - `) + fun test(contracts: &Account.Contracts): &AnyStruct { + return contracts.borrow(name: "foo")! + } + `, + ParseAndCheckOptions{ + Config: &sema.Config{ + InvalidTypeErrorFixesEnabled: invalidTypeErrorFixesEnabled, + }, + }, + ) - errors := RequireCheckerErrors(t, err, 2) + if invalidTypeErrorFixesEnabled { + errors := RequireCheckerErrors(t, err, 2) - assert.IsType(t, &sema.InvocationTypeInferenceError{}, errors[0]) - assert.IsType(t, &sema.TypeParameterTypeInferenceError{}, errors[1]) - }) + assert.IsType(t, &sema.InvocationTypeInferenceError{}, errors[0]) + assert.IsType(t, &sema.TypeParameterTypeInferenceError{}, errors[1]) + } else { + errors := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.TypeParameterTypeInferenceError{}, errors[0]) + } + }) + } + + for _, invalidTypeErrorFixesEnabled := range []bool{false, true} { + testInvalidBorrowContractMissingTypeArgument(t, invalidTypeErrorFixesEnabled) + } } func TestCheckAccountContractsAdd(t *testing.T) { diff --git a/runtime/tests/checker/arrays_dictionaries_test.go b/runtime/tests/checker/arrays_dictionaries_test.go index 3952c1aa2b..d98205bdb0 100644 --- a/runtime/tests/checker/arrays_dictionaries_test.go +++ b/runtime/tests/checker/arrays_dictionaries_test.go @@ -1262,44 +1262,64 @@ func TestCheckArrayMapInvalidArgs(t *testing.T) { t.Parallel() - testInvalidArgs := func(code string, expectedErrors []sema.SemanticError) { - _, err := ParseAndCheck(t, code) + testNotAFunction := func(invalidTypeErrorFixesEnabled bool) { - errs := RequireCheckerErrors(t, err, len(expectedErrors)) + name := fmt.Sprintf("not a function, error fixes enabled: %v", invalidTypeErrorFixesEnabled) + t.Run(name, func(t *testing.T) { + t.Parallel() - for i, e := range expectedErrors { - assert.IsType(t, e, errs[i]) - } + _, err := ParseAndCheckWithOptions(t, + ` + fun test() { + let x = [1, 2, 3] + let y = x.map(100) + } + `, + ParseAndCheckOptions{ + Config: &sema.Config{ + InvalidTypeErrorFixesEnabled: invalidTypeErrorFixesEnabled, + }, + }, + ) + + if invalidTypeErrorFixesEnabled { + errs := RequireCheckerErrors(t, err, 3) + + assert.IsType(t, &sema.TypeMismatchError{}, errs[0]) + assert.IsType(t, &sema.InvocationTypeInferenceError{}, errs[1]) // since we're not passing a function. + assert.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[2]) // since we're not passing a function. + } else { + errs := RequireCheckerErrors(t, err, 2) + + assert.IsType(t, &sema.TypeMismatchError{}, errs[0]) + assert.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1]) // since we're not passing a function. + } + }) } - testInvalidArgs(` - fun test() { - let x = [1, 2, 3] - let y = x.map(100) - } - `, - []sema.SemanticError{ - &sema.TypeMismatchError{}, - &sema.InvocationTypeInferenceError{}, // since we're not passing a function. - &sema.TypeParameterTypeInferenceError{}, // since we're not passing a function. - }, - ) + for _, invalidTypeErrorFixesEnabled := range []bool{true, false} { + testNotAFunction(invalidTypeErrorFixesEnabled) + } - testInvalidArgs(` - fun test() { - let x = [1, 2, 3] - let trueForEvenInt16 = - fun (_ x: Int16): Bool { - return x % 2 == 0 - } + t.Run("function, invalid parameter type", func(t *testing.T) { + t.Parallel() - let y: [Bool] = x.map(trueForEvenInt16) - } - `, - []sema.SemanticError{ - &sema.TypeMismatchError{}, - }, - ) + _, err := ParseAndCheck(t, ` + fun test() { + let x = [1, 2, 3] + let trueForEvenInt16 = + fun (_ x: Int16): Bool { + return x % 2 == 0 + } + + let y: [Bool] = x.map(trueForEvenInt16) + } + `) + + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.TypeMismatchError{}, errs[0]) + }) } func TestCheckResourceArrayMapInvalid(t *testing.T) { @@ -2653,17 +2673,42 @@ func TestCheckArrayToConstantSizedMissingTypeArgument(t *testing.T) { t.Parallel() - _, err := ParseAndCheck(t, ` - fun test() { - let x: [Int16] = [1, 2, 3] - let y = x.toConstantSized() - } - `) + test := func(invalidTypeErrorFixesEnabled bool) { - errs := RequireCheckerErrors(t, err, 2) + name := fmt.Sprintf("fixes enabled: %v", invalidTypeErrorFixesEnabled) + + t.Run(name, func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheckWithOptions(t, ` + fun test() { + let x: [Int16] = [1, 2, 3] + let y = x.toConstantSized() + } + `, + ParseAndCheckOptions{ + Config: &sema.Config{ + InvalidTypeErrorFixesEnabled: invalidTypeErrorFixesEnabled, + }, + }, + ) - assert.IsType(t, &sema.InvocationTypeInferenceError{}, errs[0]) - assert.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1]) + if invalidTypeErrorFixesEnabled { + errs := RequireCheckerErrors(t, err, 2) + + assert.IsType(t, &sema.InvocationTypeInferenceError{}, errs[0]) + assert.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1]) + } else { + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[0]) + } + }) + } + + for _, invalidTypeErrorFixesEnabled := range []bool{true, false} { + test(invalidTypeErrorFixesEnabled) + } } func TestCheckArrayReferenceTypeInference(t *testing.T) { diff --git a/runtime/tests/checker/builtinfunctions_test.go b/runtime/tests/checker/builtinfunctions_test.go index 79c931d13c..68d23e86ec 100644 --- a/runtime/tests/checker/builtinfunctions_test.go +++ b/runtime/tests/checker/builtinfunctions_test.go @@ -288,6 +288,7 @@ func TestCheckRevertibleRandom(t *testing.T) { BaseValueActivationHandler: func(_ common.Location) *sema.VariableActivation { return baseValueActivation }, + InvalidTypeErrorFixesEnabled: false, }, } } @@ -430,7 +431,6 @@ func TestCheckRevertibleRandom(t *testing.T) { "missing type argument", `let rand = revertibleRandom()`, []error{ - &sema.InvocationTypeInferenceError{}, &sema.TypeParameterTypeInferenceError{}, }, ) diff --git a/runtime/tests/checker/capability_test.go b/runtime/tests/checker/capability_test.go index a842ed161f..e8154db6e2 100644 --- a/runtime/tests/checker/capability_test.go +++ b/runtime/tests/checker/capability_test.go @@ -22,7 +22,9 @@ import ( "fmt" "testing" + "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/sema" + "github.com/onflow/cadence/runtime/stdlib" "github.com/onflow/cadence/runtime/tests/utils" "github.com/stretchr/testify/assert" @@ -76,20 +78,50 @@ func TestCheckCapability_borrow(t *testing.T) { t.Parallel() - t.Run("missing type argument", func(t *testing.T) { + testMissingTypeArgument := func(invalidTypeErrorFixesEnabled bool) { - _, err := ParseAndCheckWithPanic(t, ` + name := fmt.Sprintf( + "missing type argument, error fixes enabled: %v", + invalidTypeErrorFixesEnabled, + ) - let capability: Capability = panic("") + t.Run(name, func(t *testing.T) { + t.Parallel() - let r = capability.borrow() - `) + baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) + baseValueActivation.DeclareValue(stdlib.PanicFunction) - errs := RequireCheckerErrors(t, err, 2) + _, err := ParseAndCheckWithOptions(t, + ` + let capability: Capability = panic("") - require.IsType(t, &sema.InvocationTypeInferenceError{}, errs[0]) - require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1]) - }) + let r = capability.borrow() + `, + ParseAndCheckOptions{ + Config: &sema.Config{ + BaseValueActivationHandler: func(_ common.Location) *sema.VariableActivation { + return baseValueActivation + }, + InvalidTypeErrorFixesEnabled: invalidTypeErrorFixesEnabled, + }, + }, + ) + if invalidTypeErrorFixesEnabled { + errs := RequireCheckerErrors(t, err, 2) + + require.IsType(t, &sema.InvocationTypeInferenceError{}, errs[0]) + require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1]) + } else { + errs := RequireCheckerErrors(t, err, 1) + + require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[0]) + } + }) + } + + for _, invalidTypeErrorFixesEnabled := range []bool{false, true} { + testMissingTypeArgument(invalidTypeErrorFixesEnabled) + } for _, auth := range []sema.Access{sema.UnauthorizedAccess, sema.NewEntitlementSetAccess([]*sema.EntitlementType{{ diff --git a/runtime/tests/checker/conditions_test.go b/runtime/tests/checker/conditions_test.go index cfe5f73fab..772741f51d 100644 --- a/runtime/tests/checker/conditions_test.go +++ b/runtime/tests/checker/conditions_test.go @@ -19,6 +19,7 @@ package checker import ( + "fmt" "testing" "github.com/stretchr/testify/assert" @@ -254,43 +255,87 @@ func TestCheckInvalidFunctionPostConditionWithBeforeAndNoArgument(t *testing.T) t.Parallel() - t.Run("test condition", func(t *testing.T) { - t.Parallel() - - _, err := ParseAndCheck(t, ` - fun test(x: Int) { - post { - before() != 0 - } - } - `) - - errs := RequireCheckerErrors(t, err, 3) - - assert.IsType(t, &sema.InsufficientArgumentsError{}, errs[0]) - assert.IsType(t, &sema.InvocationTypeInferenceError{}, errs[1]) - assert.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[2]) - }) - - t.Run("emit condition", func(t *testing.T) { - t.Parallel() + test := func(invalidTypeErrorFixesEnabled bool) { - _, err := ParseAndCheck(t, ` - event Foo(x: Int) - - fun test(x: Int) { - post { - emit Foo(x: before()) - } - } - `) + name := fmt.Sprintf( + "error fixes enabled: %v", + invalidTypeErrorFixesEnabled, + ) - errs := RequireCheckerErrors(t, err, 3) + t.Run(name, func(t *testing.T) { + t.Parallel() + + t.Run("test condition", func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheckWithOptions(t, + ` + fun test(x: Int) { + post { + before() != 0 + } + } + `, + ParseAndCheckOptions{ + Config: &sema.Config{ + InvalidTypeErrorFixesEnabled: invalidTypeErrorFixesEnabled, + }, + }, + ) + + if invalidTypeErrorFixesEnabled { + errs := RequireCheckerErrors(t, err, 3) + + assert.IsType(t, &sema.InsufficientArgumentsError{}, errs[0]) + assert.IsType(t, &sema.InvocationTypeInferenceError{}, errs[1]) + assert.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[2]) + } else { + errs := RequireCheckerErrors(t, err, 2) + + assert.IsType(t, &sema.InsufficientArgumentsError{}, errs[0]) + assert.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1]) + } + }) + + t.Run("emit condition", func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheckWithOptions(t, + ` + event Foo(x: Int) + + fun test(x: Int) { + post { + emit Foo(x: before()) + } + } + `, + ParseAndCheckOptions{ + Config: &sema.Config{ + InvalidTypeErrorFixesEnabled: invalidTypeErrorFixesEnabled, + }, + }, + ) + + if invalidTypeErrorFixesEnabled { + errs := RequireCheckerErrors(t, err, 3) + + assert.IsType(t, &sema.InsufficientArgumentsError{}, errs[0]) + assert.IsType(t, &sema.InvocationTypeInferenceError{}, errs[1]) + assert.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[2]) + } else { + errs := RequireCheckerErrors(t, err, 2) + + assert.IsType(t, &sema.InsufficientArgumentsError{}, errs[0]) + assert.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1]) + } + }) + }) + } - assert.IsType(t, &sema.InsufficientArgumentsError{}, errs[0]) - assert.IsType(t, &sema.InvocationTypeInferenceError{}, errs[1]) - assert.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[2]) - }) + for _, invalidTypeErrorFixesEnabled := range []bool{true, false} { + test(invalidTypeErrorFixesEnabled) + } } func TestCheckInvalidFunctionPreConditionWithBefore(t *testing.T) { diff --git a/runtime/tests/checker/dictionary_test.go b/runtime/tests/checker/dictionary_test.go index e0efc29cce..1887e8a454 100644 --- a/runtime/tests/checker/dictionary_test.go +++ b/runtime/tests/checker/dictionary_test.go @@ -19,6 +19,7 @@ package checker import ( + "fmt" "testing" "github.com/stretchr/testify/assert" @@ -31,26 +32,49 @@ func TestCheckIncompleteDictionaryType(t *testing.T) { t.Parallel() - checker, err := ParseAndCheckWithOptions(t, - ` - let dict: {Int:} = {} - `, - ParseAndCheckOptions{ - IgnoreParseError: true, - }, - ) + test := func(invalidTypeErrorFixesEnabled bool) { - errs := RequireCheckerErrors(t, err, 1) + name := fmt.Sprintf( + "error fixes enabled: %v", + invalidTypeErrorFixesEnabled, + ) - assert.IsType(t, errs[0], &sema.UnconvertableTypeError{}) + t.Run(name, func(t *testing.T) { + t.Parallel() - assert.Equal(t, - &sema.DictionaryType{ - KeyType: sema.IntType, - ValueType: sema.InvalidType, - }, - RequireGlobalValue(t, checker.Elaboration, "dict"), - ) + checker, err := ParseAndCheckWithOptions(t, + ` + let dict: {Int:} = {} + `, + ParseAndCheckOptions{ + IgnoreParseError: true, + Config: &sema.Config{ + InvalidTypeErrorFixesEnabled: invalidTypeErrorFixesEnabled, + }, + }, + ) + + if invalidTypeErrorFixesEnabled { + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, errs[0], &sema.UnconvertableTypeError{}) + } else { + require.NoError(t, err) + } + + assert.Equal(t, + &sema.DictionaryType{ + KeyType: sema.IntType, + ValueType: sema.InvalidType, + }, + RequireGlobalValue(t, checker.Elaboration, "dict"), + ) + }) + } + + for _, invalidTypeErrorFixesEnabled := range []bool{true, false} { + test(invalidTypeErrorFixesEnabled) + } } func TestCheckMetaKeyType(t *testing.T) { diff --git a/runtime/tests/checker/genericfunction_test.go b/runtime/tests/checker/genericfunction_test.go index f7f89c87cf..b1310705f6 100644 --- a/runtime/tests/checker/genericfunction_test.go +++ b/runtime/tests/checker/genericfunction_test.go @@ -431,31 +431,67 @@ func TestCheckGenericFunctionInvocation(t *testing.T) { t.Parallel() - typeParameter := &sema.TypeParameter{ - Name: "T", - TypeBound: nil, - } + test := func(invalidTypeErrorFixesEnabled bool) { - _, err := parseAndCheckWithTestValue(t, - ` - let res = test() - `, - &sema.FunctionType{ - TypeParameters: []*sema.TypeParameter{ - typeParameter, - }, - ReturnTypeAnnotation: sema.NewTypeAnnotation( - &sema.GenericType{ - TypeParameter: typeParameter, + name := fmt.Sprintf( + "error fixes enabled: %v", + invalidTypeErrorFixesEnabled, + ) + + t.Run(name, func(t *testing.T) { + t.Parallel() + + typeParameter := &sema.TypeParameter{ + Name: "T", + TypeBound: nil, + } + + baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) + baseValueActivation.DeclareValue(stdlib.StandardLibraryValue{ + Name: "test", + Type: &sema.FunctionType{ + TypeParameters: []*sema.TypeParameter{ + typeParameter, + }, + ReturnTypeAnnotation: sema.NewTypeAnnotation( + &sema.GenericType{ + TypeParameter: typeParameter, + }, + ), }, - ), - }, - ) + Kind: common.DeclarationKindConstant, + }) - errs := RequireCheckerErrors(t, err, 2) + _, err := ParseAndCheckWithOptions(t, + ` + let res = test() + `, + ParseAndCheckOptions{ + Config: &sema.Config{ + BaseValueActivationHandler: func(_ common.Location) *sema.VariableActivation { + return baseValueActivation + }, + InvalidTypeErrorFixesEnabled: invalidTypeErrorFixesEnabled, + }, + }, + ) - assert.IsType(t, &sema.InvocationTypeInferenceError{}, errs[0]) - assert.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1]) + if invalidTypeErrorFixesEnabled { + errs := RequireCheckerErrors(t, err, 2) + + assert.IsType(t, &sema.InvocationTypeInferenceError{}, errs[0]) + assert.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1]) + } else { + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[0]) + } + }) + } + + for _, invalidTypeErrorFixesEnabled := range []bool{true, false} { + test(invalidTypeErrorFixesEnabled) + } }) t.Run("valid: one type parameter, one type argument, no parameters, no arguments, return type", func(t *testing.T) { diff --git a/runtime/tests/checker/invalid_test.go b/runtime/tests/checker/invalid_test.go index 3ed32e3205..aa7d1aba6a 100644 --- a/runtime/tests/checker/invalid_test.go +++ b/runtime/tests/checker/invalid_test.go @@ -19,6 +19,7 @@ package checker import ( + "fmt" "testing" "github.com/stretchr/testify/assert" @@ -210,80 +211,123 @@ func TestCheckInvalidInvocationFunctionReturnType(t *testing.T) { t.Parallel() - typeParameter := &sema.TypeParameter{ - Name: "T", - } + test := func(invalidTypeErrorFixesEnabled bool) { - fType := &sema.FunctionType{ - TypeParameters: []*sema.TypeParameter{ - typeParameter, - }, - ReturnTypeAnnotation: sema.NewTypeAnnotation( - &sema.GenericType{ - TypeParameter: typeParameter, - }, - ), - } + name := fmt.Sprintf( + "error fixes enabled: %v", + invalidTypeErrorFixesEnabled, + ) - baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) - baseValueActivation.DeclareValue(stdlib.StandardLibraryValue{ - Type: fType, - Name: "f", - Kind: common.DeclarationKindFunction, - }) + t.Run(name, func(t *testing.T) { + t.Parallel() - _, err := ParseAndCheckWithOptions(t, - ` - let res = [f].reverse() - `, - ParseAndCheckOptions{ - Config: &sema.Config{ - BaseValueActivationHandler: func(_ common.Location) *sema.VariableActivation { - return baseValueActivation + typeParameter := &sema.TypeParameter{ + Name: "T", + } + + fType := &sema.FunctionType{ + TypeParameters: []*sema.TypeParameter{ + typeParameter, }, - }, - }, - ) + ReturnTypeAnnotation: sema.NewTypeAnnotation( + &sema.GenericType{ + TypeParameter: typeParameter, + }, + ), + } + + baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) + baseValueActivation.DeclareValue(stdlib.StandardLibraryValue{ + Type: fType, + Name: "f", + Kind: common.DeclarationKindFunction, + }) + + _, err := ParseAndCheckWithOptions(t, + ` + let res = [f].reverse() + `, + ParseAndCheckOptions{ + Config: &sema.Config{ + BaseValueActivationHandler: func(_ common.Location) *sema.VariableActivation { + return baseValueActivation + }, + InvalidTypeErrorFixesEnabled: invalidTypeErrorFixesEnabled, + }, + }, + ) - errs := RequireCheckerErrors(t, err, 1) + if invalidTypeErrorFixesEnabled { - assert.IsType(t, &sema.InvocationTypeInferenceError{}, errs[0]) + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.InvocationTypeInferenceError{}, errs[0]) + } else { + require.NoError(t, err) + } + }) + } + + for _, invalidTypeErrorFixesEnabled := range []bool{true, false} { + test(invalidTypeErrorFixesEnabled) + } } func TestCheckInvalidTypeDefensiveCheck(t *testing.T) { t.Parallel() - baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) - baseValueActivation.DeclareValue(stdlib.StandardLibraryValue{ - Type: sema.InvalidType, - Name: "invalid", - Kind: common.DeclarationKindConstant, - }) - - var r any - func() { - defer func() { - r = recover() - }() - - _, _ = ParseAndCheckWithOptions(t, - ` - let res = invalid - `, - ParseAndCheckOptions{ - Config: &sema.Config{ - BaseValueActivationHandler: func(_ common.Location) *sema.VariableActivation { - return baseValueActivation - }, - }, - }, + test := func(invalidTypeErrorFixesEnabled bool) { + + name := fmt.Sprintf( + "error fixes enabled: %v", + invalidTypeErrorFixesEnabled, ) - }() - require.IsType(t, errors.UnexpectedError{}, r) - err := r.(errors.UnexpectedError) - require.ErrorContains(t, err, "invalid type produced without error") + t.Run(name, func(t *testing.T) { + t.Parallel() + + baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) + baseValueActivation.DeclareValue(stdlib.StandardLibraryValue{ + Type: sema.InvalidType, + Name: "invalid", + Kind: common.DeclarationKindConstant, + }) + + var r any + func() { + defer func() { + r = recover() + }() + + _, _ = ParseAndCheckWithOptions(t, + ` + let res = invalid + `, + ParseAndCheckOptions{ + Config: &sema.Config{ + BaseValueActivationHandler: func(_ common.Location) *sema.VariableActivation { + return baseValueActivation + }, + InvalidTypeErrorFixesEnabled: invalidTypeErrorFixesEnabled, + }, + }, + ) + }() + + if invalidTypeErrorFixesEnabled { + require.IsType(t, errors.UnexpectedError{}, r) + err := r.(errors.UnexpectedError) + require.ErrorContains(t, err, "invalid type produced without error") + } else { + require.Nil(t, r) + } + }) + } + + for _, invalidTypeErrorFixesEnabled := range []bool{true, false} { + test(invalidTypeErrorFixesEnabled) + } } func TestCheckInvalidTypeIndexing(t *testing.T) { diff --git a/runtime/tests/checker/invocation_test.go b/runtime/tests/checker/invocation_test.go index 3d0d8f6949..fc88ec267d 100644 --- a/runtime/tests/checker/invocation_test.go +++ b/runtime/tests/checker/invocation_test.go @@ -19,6 +19,7 @@ package checker import ( + "fmt" "testing" "github.com/stretchr/testify/assert" @@ -597,57 +598,79 @@ func TestCheckInvocationWithIncorrectTypeParameter(t *testing.T) { t.Parallel() - // function type has incorrect type-arguments: - // `fun Foo(_ a: R)` - // - funcType := &sema.FunctionType{ - ReturnTypeAnnotation: sema.VoidTypeAnnotation, - TypeParameters: []*sema.TypeParameter{ - { - Name: "T", - TypeBound: sema.AnyStructType, - }, - }, - Parameters: []sema.Parameter{ - { - Label: sema.ArgumentLabelNotRequired, - Identifier: "a", - TypeAnnotation: sema.NewTypeAnnotation( - &sema.GenericType{ - TypeParameter: &sema.TypeParameter{ - Name: "R", // This is an incorrect/undefined type-parameter - TypeBound: sema.AnyStructType, - }, + test := func(invalidTypeErrorFixesEnabled bool) { + + name := fmt.Sprintf( + "error fixes enabled: %v", + invalidTypeErrorFixesEnabled, + ) + + t.Run(name, func(t *testing.T) { + t.Parallel() + + // function type has incorrect type-arguments: + // `fun Foo(_ a: R)` + // + funcType := &sema.FunctionType{ + ReturnTypeAnnotation: sema.VoidTypeAnnotation, + TypeParameters: []*sema.TypeParameter{ + { + Name: "T", + TypeBound: sema.AnyStructType, }, - ), - }, - }, - } + }, + Parameters: []sema.Parameter{ + { + Label: sema.ArgumentLabelNotRequired, + Identifier: "a", + TypeAnnotation: sema.NewTypeAnnotation( + &sema.GenericType{ + TypeParameter: &sema.TypeParameter{ + Name: "R", // This is an incorrect/undefined type-parameter + TypeBound: sema.AnyStructType, + }, + }, + ), + }, + }, + } - baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) - baseValueActivation.DeclareValue(stdlib.NewStandardLibraryStaticFunction( - "foo", - funcType, - "", - nil, // no need, we only type-check - )) + baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) + baseValueActivation.DeclareValue(stdlib.NewStandardLibraryStaticFunction( + "foo", + funcType, + "", + nil, // no need, we only type-check + )) - _, err := ParseAndCheckWithOptions(t, - ` - access(all) fun test() { - foo("hello") - } - `, - ParseAndCheckOptions{ - Config: &sema.Config{ - BaseValueActivationHandler: func(_ common.Location) *sema.VariableActivation { - return baseValueActivation + _, err := ParseAndCheckWithOptions(t, + ` + access(all) fun test() { + foo("hello") + } + `, + ParseAndCheckOptions{ + Config: &sema.Config{ + BaseValueActivationHandler: func(_ common.Location) *sema.VariableActivation { + return baseValueActivation + }, + InvalidTypeErrorFixesEnabled: invalidTypeErrorFixesEnabled, + }, }, - }, - }, - ) + ) - errs := RequireCheckerErrors(t, err, 1) + if invalidTypeErrorFixesEnabled { - assert.IsType(t, &sema.InvocationTypeInferenceError{}, errs[0]) + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.InvocationTypeInferenceError{}, errs[0]) + } else { + require.NoError(t, err) + } + }) + } + + for _, invalidTypeErrorFixesEnabled := range []bool{true, false} { + test(invalidTypeErrorFixesEnabled) + } } diff --git a/runtime/tests/checker/utils.go b/runtime/tests/checker/utils.go index 9849a13d66..c883e10c2d 100644 --- a/runtime/tests/checker/utils.go +++ b/runtime/tests/checker/utils.go @@ -170,6 +170,8 @@ func ParseAndCheckWithOptionsAndMemoryMetering( } func RequireCheckerErrors(t *testing.T, err error, count int) []error { + t.Helper() + if count <= 0 { require.NoError(t, err) return nil From aecd31a9651a811ab3e8968ca9891bcb9a9ad45d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 19 Nov 2024 15:44:31 -0800 Subject: [PATCH 11/11] Revert "gate new behaviour (errors for all invalid type cases) behind a feature flag" This reverts commit 3e2ecc99b3a819ffb123723f62777a2655bbbae6. --- runtime/environment.go | 9 +- runtime/runtime_test.go | 85 +++---- runtime/sema/check_invocation_expression.go | 42 ++-- runtime/sema/checker.go | 16 +- runtime/sema/config.go | 2 - runtime/sema/errors.go | 10 - runtime/tests/checker/account_test.go | 215 +++++------------- .../tests/checker/arrays_dictionaries_test.go | 127 ++++------- .../tests/checker/builtinfunctions_test.go | 2 +- runtime/tests/checker/capability_test.go | 50 +--- runtime/tests/checker/conditions_test.go | 113 +++------ runtime/tests/checker/dictionary_test.go | 58 ++--- runtime/tests/checker/genericfunction_test.go | 78 ++----- runtime/tests/checker/invalid_test.go | 164 +++++-------- runtime/tests/checker/invocation_test.go | 117 ++++------ runtime/tests/checker/utils.go | 2 - 16 files changed, 340 insertions(+), 750 deletions(-) diff --git a/runtime/environment.go b/runtime/environment.go index bcf2298df7..f2607eb5bd 100644 --- a/runtime/environment.go +++ b/runtime/environment.go @@ -22,7 +22,6 @@ import ( "time" "go.opentelemetry.io/otel/attribute" - "golang.org/x/mod/semver" "github.com/onflow/cadence" "github.com/onflow/cadence/runtime/activations" @@ -1462,9 +1461,6 @@ func (e *interpreterEnvironment) newValidateAccountCapabilitiesPublishHandler() } } -// TODO: -const MinimumRequiredVersionForInvalidTypeErrorFixes = "v1.0.3" - func (e *interpreterEnvironment) configureVersionedFeatures() { var ( minimumRequiredVersion string @@ -1477,7 +1473,6 @@ func (e *interpreterEnvironment) configureVersionedFeatures() { panic(err) } - e.CheckerConfig.InvalidTypeErrorFixesEnabled = - minimumRequiredVersion != "" && - semver.Compare(MinimumRequiredVersionForInvalidTypeErrorFixes, minimumRequiredVersion) >= 0 + // No feature flags yet + _ = minimumRequiredVersion } diff --git a/runtime/runtime_test.go b/runtime/runtime_test.go index 647ed2116b..5efbfe7dab 100644 --- a/runtime/runtime_test.go +++ b/runtime/runtime_test.go @@ -11553,66 +11553,41 @@ func TestRuntimeInvocationReturnTypeInferenceFailure(t *testing.T) { t.Parallel() - test := func(invalidTypeErrorFixesEnabled bool) { - - name := fmt.Sprintf( - "error fixes enabled: %v", - invalidTypeErrorFixesEnabled, - ) - - t.Run(name, func(t *testing.T) { - t.Parallel() - - address := common.MustBytesToAddress([]byte{0x1}) - - runtimeInterface := &TestRuntimeInterface{ - Storage: NewTestLedger(nil, nil), - OnGetSigningAccounts: func() ([]common.Address, error) { - return []common.Address{address}, nil - }, - OnMinimumRequiredVersion: func() (string, error) { - if invalidTypeErrorFixesEnabled { - return MinimumRequiredVersionForInvalidTypeErrorFixes, nil - } - return "", nil - }, - } + address := common.MustBytesToAddress([]byte{0x1}) - runtime := NewTestInterpreterRuntime() + newRuntimeInterface := func() Interface { - nextTransactionLocation := NewTransactionLocationGenerator() + return &TestRuntimeInterface{ + Storage: NewTestLedger(nil, nil), + OnGetSigningAccounts: func() ([]common.Address, error) { + return []common.Address{address}, nil + }, + } + } - tx := []byte(` - transaction{ - prepare(signer: auth(Storage) &Account){ - let functions = [signer.storage.save].reverse() - } - } - `) + runtime := NewTestInterpreterRuntime() - err := runtime.ExecuteTransaction( - Script{ - Source: tx, - }, - Context{ - Interface: runtimeInterface, - Location: nextTransactionLocation(), - }, - ) + nextTransactionLocation := NewTransactionLocationGenerator() - RequireError(t, err) + tx := []byte(` + transaction{ + prepare(signer: auth(Storage) &Account){ + let functions = [signer.storage.save].reverse() + } + } + `) - if invalidTypeErrorFixesEnabled { - var typeErr *sema.InvocationTypeInferenceError - require.ErrorAs(t, err, &typeErr) - } else { - var transferErr interpreter.ValueTransferTypeError - require.ErrorAs(t, err, &transferErr) - } - }) - } + err := runtime.ExecuteTransaction( + Script{ + Source: tx, + }, + Context{ + Interface: newRuntimeInterface(), + Location: nextTransactionLocation(), + }, + ) + RequireError(t, err) - for _, invalidTypeErrorFixesEnabled := range []bool{true, false} { - test(invalidTypeErrorFixesEnabled) - } + var typeErr *sema.InvocationTypeInferenceError + require.ErrorAs(t, err, &typeErr) } diff --git a/runtime/sema/check_invocation_expression.go b/runtime/sema/check_invocation_expression.go index 9c91033e75..bbf45de72d 100644 --- a/runtime/sema/check_invocation_expression.go +++ b/runtime/sema/check_invocation_expression.go @@ -503,14 +503,12 @@ func (checker *Checker) checkInvocation( returnType = functionType.ReturnTypeAnnotation.Type.Resolve(typeArguments) if returnType == nil { - if checker.Config.InvalidTypeErrorFixesEnabled { - checker.report(&InvocationTypeInferenceError{ - Range: ast.NewRangeFromPositioned( - checker.memoryGauge, - invocationExpression, - ), - }) - } + checker.report(&InvocationTypeInferenceError{ + Range: ast.NewRangeFromPositioned( + checker.memoryGauge, + invocationExpression, + ), + }) returnType = InvalidType } @@ -607,14 +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 { - if checker.Config.InvalidTypeErrorFixesEnabled { - checker.report(&InvocationTypeInferenceError{ - Range: ast.NewRangeFromPositioned( - checker.memoryGauge, - argument.Expression, - ), - }) - } + checker.report(&InvocationTypeInferenceError{ + Range: ast.NewRangeFromPositioned( + checker.memoryGauge, + argument.Expression, + ), + }) parameterType = InvalidType } } @@ -690,14 +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 { - if checker.Config.InvalidTypeErrorFixesEnabled { - checker.report(&InvocationTypeInferenceError{ - Range: ast.NewRangeFromPositioned( - checker.memoryGauge, - argument.Expression, - ), - }) - } + checker.report(&InvocationTypeInferenceError{ + Range: ast.NewRangeFromPositioned( + checker.memoryGauge, + argument.Expression, + ), + }) parameterType = InvalidType } } diff --git a/runtime/sema/checker.go b/runtime/sema/checker.go index 9fa38951f8..fa3486715a 100644 --- a/runtime/sema/checker.go +++ b/runtime/sema/checker.go @@ -886,16 +886,12 @@ func (checker *Checker) ConvertType(t ast.Type) Type { case *ast.InstantiationType: return checker.convertInstantiationType(t) - case nil: - if checker.Config.InvalidTypeErrorFixesEnabled { - checker.report(&UnconvertableTypeError{ - Range: ast.NewRangeFromPositioned(checker.memoryGauge, t), - }) - } + default: + checker.report(&UnconvertableTypeError{ + Range: ast.NewRangeFromPositioned(checker.memoryGauge, t), + }) return InvalidType } - - panic(&astTypeConversionError{invalidASTType: t}) } func CheckIntersectionType( @@ -2615,9 +2611,7 @@ func (checker *Checker) visitExpressionWithForceType( actualType = ast.AcceptExpression[Type](expr, checker) - if checker.Config.InvalidTypeErrorFixesEnabled { - checker.checkErrorsForInvalidExpressionTypes(actualType, expectedType) - } + checker.checkErrorsForInvalidExpressionTypes(actualType, expectedType) if checker.Config.ExtendedElaborationEnabled { checker.Elaboration.SetExpressionTypes( diff --git a/runtime/sema/config.go b/runtime/sema/config.go index 28c19feea5..06f6e9ce95 100644 --- a/runtime/sema/config.go +++ b/runtime/sema/config.go @@ -55,6 +55,4 @@ type Config struct { AllowStaticDeclarations bool // AttachmentsEnabled determines if attachments are enabled AttachmentsEnabled bool - // InvalidTypeErrorFixesEnabled determines if errors for invalid type errors are reported - InvalidTypeErrorFixesEnabled bool } diff --git a/runtime/sema/errors.go b/runtime/sema/errors.go index 04b32afeb0..163c5ec5ba 100644 --- a/runtime/sema/errors.go +++ b/runtime/sema/errors.go @@ -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 { diff --git a/runtime/tests/checker/account_test.go b/runtime/tests/checker/account_test.go index d6d4c0b65d..c8c5ea0cf8 100644 --- a/runtime/tests/checker/account_test.go +++ b/runtime/tests/checker/account_test.go @@ -401,19 +401,18 @@ func TestCheckAccountStorageLoad(t *testing.T) { require.IsType(t, &sema.InvalidAccessError{}, errs[0]) }) - testMissingTypeArguments := func(domain common.PathDomain, invalidTypeErrorFixesEnabled bool) { + testMissingTypeArguments := func(domain common.PathDomain) { testName := fmt.Sprintf( - "missing type argument, %s, %v", + "missing type argument, %s", domain.Identifier(), - invalidTypeErrorFixesEnabled, ) t.Run(testName, func(t *testing.T) { t.Parallel() - _, err := ParseAndCheckWithOptions(t, + _, err := ParseAndCheck(t, fmt.Sprintf( ` fun test(storage: auth(Storage) &Account.Storage) { @@ -422,38 +421,20 @@ func TestCheckAccountStorageLoad(t *testing.T) { `, domain.Identifier(), ), - ParseAndCheckOptions{ - Config: &sema.Config{ - InvalidTypeErrorFixesEnabled: invalidTypeErrorFixesEnabled, - }, - }, ) if domain == common.PathDomainStorage { - if invalidTypeErrorFixesEnabled { - errs := RequireCheckerErrors(t, err, 2) - - require.IsType(t, &sema.InvocationTypeInferenceError{}, errs[0]) - require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1]) - } else { - 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 { - if invalidTypeErrorFixesEnabled { - errs := RequireCheckerErrors(t, err, 3) + errs := RequireCheckerErrors(t, err, 3) - require.IsType(t, &sema.TypeMismatchError{}, errs[0]) - require.IsType(t, &sema.InvocationTypeInferenceError{}, errs[1]) - require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[2]) - } else { - errs := RequireCheckerErrors(t, err, 2) - - require.IsType(t, &sema.TypeMismatchError{}, errs[0]) - require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1]) - } + require.IsType(t, &sema.TypeMismatchError{}, errs[0]) + require.IsType(t, &sema.InvocationTypeInferenceError{}, errs[1]) + require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[2]) } }) } @@ -525,9 +506,7 @@ func TestCheckAccountStorageLoad(t *testing.T) { } for _, domain := range common.AllPathDomainsByIdentifier { - for _, invalidTypeErrorFixesEnabled := range []bool{false, true} { - testMissingTypeArguments(domain, invalidTypeErrorFixesEnabled) - } + testMissingTypeArguments(domain) testExplicitTypeArgument(domain) } } @@ -550,19 +529,18 @@ func TestCheckAccountStorageCopy(t *testing.T) { require.IsType(t, &sema.InvalidAccessError{}, errs[0]) }) - testMissingTypeArgument := func(domain common.PathDomain, invalidTypeErrorFixesEnabled bool) { + testMissingTypeArgument := func(domain common.PathDomain) { testName := fmt.Sprintf( - "missing type argument, %s, %v", + "missing type argument, %s", domain.Identifier(), - invalidTypeErrorFixesEnabled, ) t.Run(testName, func(t *testing.T) { t.Parallel() - _, err := ParseAndCheckWithOptions(t, + _, err := ParseAndCheck(t, fmt.Sprintf( ` struct S {} @@ -573,39 +551,20 @@ func TestCheckAccountStorageCopy(t *testing.T) { `, domain.Identifier(), ), - ParseAndCheckOptions{ - Config: &sema.Config{ - InvalidTypeErrorFixesEnabled: invalidTypeErrorFixesEnabled, - }, - }, ) if domain == common.PathDomainStorage { + errs := RequireCheckerErrors(t, err, 2) - if invalidTypeErrorFixesEnabled { - errs := RequireCheckerErrors(t, err, 2) - - require.IsType(t, &sema.InvocationTypeInferenceError{}, errs[0]) - require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1]) - } else { - errs := RequireCheckerErrors(t, err, 1) - - require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[0]) - } + require.IsType(t, &sema.InvocationTypeInferenceError{}, errs[0]) + require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1]) } else { - if invalidTypeErrorFixesEnabled { - errs := RequireCheckerErrors(t, err, 3) + errs := RequireCheckerErrors(t, err, 3) - require.IsType(t, &sema.TypeMismatchError{}, errs[0]) - require.IsType(t, &sema.InvocationTypeInferenceError{}, errs[1]) - require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[2]) - } else { - errs := RequireCheckerErrors(t, err, 2) - - require.IsType(t, &sema.TypeMismatchError{}, errs[0]) - require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1]) - } + require.IsType(t, &sema.TypeMismatchError{}, errs[0]) + require.IsType(t, &sema.InvocationTypeInferenceError{}, errs[1]) + require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[2]) } }) } @@ -681,9 +640,7 @@ func TestCheckAccountStorageCopy(t *testing.T) { } for _, domain := range common.AllPathDomainsByIdentifier { - for _, invalidTypeErrorFixesEnabled := range []bool{false, true} { - testMissingTypeArgument(domain, invalidTypeErrorFixesEnabled) - } + testMissingTypeArgument(domain) testExplicitTypeArgument(domain) } } @@ -706,12 +663,11 @@ func TestCheckAccountStorageBorrow(t *testing.T) { require.IsType(t, &sema.InvalidAccessError{}, errs[0]) }) - testMissingTypeArgument := func(domain common.PathDomain, invalidTypeErrorFixesEnabled bool) { + testMissingTypeArgument := func(domain common.PathDomain) { testName := fmt.Sprintf( - "missing type argument, %s, %v", + "missing type argument, %s", domain.Identifier(), - invalidTypeErrorFixesEnabled, ) t.Run(testName, func(t *testing.T) { @@ -722,7 +678,7 @@ func TestCheckAccountStorageBorrow(t *testing.T) { t.Parallel() - _, err := ParseAndCheckWithOptions(t, + _, err := ParseAndCheck(t, fmt.Sprintf( ` fun test(storage: auth(Storage) &Account.Storage) { @@ -731,38 +687,20 @@ func TestCheckAccountStorageBorrow(t *testing.T) { `, domain.Identifier(), ), - ParseAndCheckOptions{ - Config: &sema.Config{ - InvalidTypeErrorFixesEnabled: invalidTypeErrorFixesEnabled, - }, - }, ) if domain == common.PathDomainStorage { - if invalidTypeErrorFixesEnabled { - errs := RequireCheckerErrors(t, err, 2) - - require.IsType(t, &sema.InvocationTypeInferenceError{}, errs[0]) - require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1]) - } else { - 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 { - if invalidTypeErrorFixesEnabled { - errs := RequireCheckerErrors(t, err, 3) - - require.IsType(t, &sema.TypeMismatchError{}, errs[0]) - require.IsType(t, &sema.InvocationTypeInferenceError{}, errs[1]) - require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[2]) - } else { - errs := RequireCheckerErrors(t, err, 2) - - require.IsType(t, &sema.TypeMismatchError{}, errs[0]) - require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1]) - } + errs := RequireCheckerErrors(t, err, 3) + + require.IsType(t, &sema.TypeMismatchError{}, errs[0]) + require.IsType(t, &sema.InvocationTypeInferenceError{}, errs[1]) + require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[2]) } }) @@ -770,7 +708,7 @@ func TestCheckAccountStorageBorrow(t *testing.T) { t.Parallel() - _, err := ParseAndCheckWithOptions(t, + _, err := ParseAndCheck(t, fmt.Sprintf( ` fun test(storage: auth(Storage) &Account.Storage) { @@ -779,38 +717,20 @@ func TestCheckAccountStorageBorrow(t *testing.T) { `, domain.Identifier(), ), - ParseAndCheckOptions{ - Config: &sema.Config{ - InvalidTypeErrorFixesEnabled: invalidTypeErrorFixesEnabled, - }, - }, ) if domain == common.PathDomainStorage { - if invalidTypeErrorFixesEnabled { - errs := RequireCheckerErrors(t, err, 2) - - require.IsType(t, &sema.InvocationTypeInferenceError{}, errs[0]) - require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1]) - } else { - 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 { - if invalidTypeErrorFixesEnabled { - errs := RequireCheckerErrors(t, err, 3) - - require.IsType(t, &sema.TypeMismatchError{}, errs[0]) - require.IsType(t, &sema.InvocationTypeInferenceError{}, errs[1]) - require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[2]) - } else { - errs := RequireCheckerErrors(t, err, 2) - - require.IsType(t, &sema.TypeMismatchError{}, errs[0]) - require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1]) - } + errs := RequireCheckerErrors(t, err, 3) + + require.IsType(t, &sema.TypeMismatchError{}, errs[0]) + require.IsType(t, &sema.InvocationTypeInferenceError{}, errs[1]) + require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[2]) } }) }) @@ -968,9 +888,7 @@ func TestCheckAccountStorageBorrow(t *testing.T) { } for _, domain := range common.AllPathDomainsByIdentifier { - for _, invalidTypeErrorFixesEnabled := range []bool{false, true} { - testMissingTypeArgument(domain, invalidTypeErrorFixesEnabled) - } + testMissingTypeArgument(domain) for _, auth := range []sema.Access{ sema.UnauthorizedAccess, @@ -1105,45 +1023,22 @@ func TestCheckAccountContractsBorrow(t *testing.T) { require.NoError(t, err) }) - testInvalidBorrowContractMissingTypeArgument := func(t *testing.T, invalidTypeErrorFixesEnabled bool) { - name := fmt.Sprintf( - "invalid borrow contract: missing type argument, error fixes enabled: %v", - invalidTypeErrorFixesEnabled, - ) - t.Run(name, func(t *testing.T) { - t.Parallel() - - _, err := ParseAndCheckWithOptions(t, - ` - contract C {} - - fun test(contracts: &Account.Contracts): &AnyStruct { - return contracts.borrow(name: "foo")! - } - `, - ParseAndCheckOptions{ - Config: &sema.Config{ - InvalidTypeErrorFixesEnabled: invalidTypeErrorFixesEnabled, - }, - }, - ) + t.Run("invalid borrow contract: missing type argument", func(t *testing.T) { + t.Parallel() - if invalidTypeErrorFixesEnabled { - errors := RequireCheckerErrors(t, err, 2) + _, err := ParseAndCheck(t, ` + contract C {} - assert.IsType(t, &sema.InvocationTypeInferenceError{}, errors[0]) - assert.IsType(t, &sema.TypeParameterTypeInferenceError{}, errors[1]) - } else { - errors := RequireCheckerErrors(t, err, 1) + fun test(contracts: &Account.Contracts): &AnyStruct { + return contracts.borrow(name: "foo")! + } + `) - assert.IsType(t, &sema.TypeParameterTypeInferenceError{}, errors[0]) - } - }) - } + errors := RequireCheckerErrors(t, err, 2) - for _, invalidTypeErrorFixesEnabled := range []bool{false, true} { - testInvalidBorrowContractMissingTypeArgument(t, invalidTypeErrorFixesEnabled) - } + assert.IsType(t, &sema.InvocationTypeInferenceError{}, errors[0]) + assert.IsType(t, &sema.TypeParameterTypeInferenceError{}, errors[1]) + }) } func TestCheckAccountContractsAdd(t *testing.T) { diff --git a/runtime/tests/checker/arrays_dictionaries_test.go b/runtime/tests/checker/arrays_dictionaries_test.go index d98205bdb0..3952c1aa2b 100644 --- a/runtime/tests/checker/arrays_dictionaries_test.go +++ b/runtime/tests/checker/arrays_dictionaries_test.go @@ -1262,64 +1262,44 @@ func TestCheckArrayMapInvalidArgs(t *testing.T) { t.Parallel() - testNotAFunction := func(invalidTypeErrorFixesEnabled bool) { - - name := fmt.Sprintf("not a function, error fixes enabled: %v", invalidTypeErrorFixesEnabled) - t.Run(name, func(t *testing.T) { - t.Parallel() - - _, err := ParseAndCheckWithOptions(t, - ` - fun test() { - let x = [1, 2, 3] - let y = x.map(100) - } - `, - ParseAndCheckOptions{ - Config: &sema.Config{ - InvalidTypeErrorFixesEnabled: invalidTypeErrorFixesEnabled, - }, - }, - ) - - if invalidTypeErrorFixesEnabled { - errs := RequireCheckerErrors(t, err, 3) - - assert.IsType(t, &sema.TypeMismatchError{}, errs[0]) - assert.IsType(t, &sema.InvocationTypeInferenceError{}, errs[1]) // since we're not passing a function. - assert.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[2]) // since we're not passing a function. - } else { - errs := RequireCheckerErrors(t, err, 2) + testInvalidArgs := func(code string, expectedErrors []sema.SemanticError) { + _, err := ParseAndCheck(t, code) - assert.IsType(t, &sema.TypeMismatchError{}, errs[0]) - assert.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1]) // since we're not passing a function. - } - }) - } + errs := RequireCheckerErrors(t, err, len(expectedErrors)) - for _, invalidTypeErrorFixesEnabled := range []bool{true, false} { - testNotAFunction(invalidTypeErrorFixesEnabled) + for i, e := range expectedErrors { + assert.IsType(t, e, errs[i]) + } } - t.Run("function, invalid parameter type", func(t *testing.T) { - t.Parallel() - - _, err := ParseAndCheck(t, ` - fun test() { - let x = [1, 2, 3] - let trueForEvenInt16 = - fun (_ x: Int16): Bool { - return x % 2 == 0 - } - - let y: [Bool] = x.map(trueForEvenInt16) - } - `) + testInvalidArgs(` + fun test() { + let x = [1, 2, 3] + let y = x.map(100) + } + `, + []sema.SemanticError{ + &sema.TypeMismatchError{}, + &sema.InvocationTypeInferenceError{}, // since we're not passing a function. + &sema.TypeParameterTypeInferenceError{}, // since we're not passing a function. + }, + ) - errs := RequireCheckerErrors(t, err, 1) + testInvalidArgs(` + fun test() { + let x = [1, 2, 3] + let trueForEvenInt16 = + fun (_ x: Int16): Bool { + return x % 2 == 0 + } - assert.IsType(t, &sema.TypeMismatchError{}, errs[0]) - }) + let y: [Bool] = x.map(trueForEvenInt16) + } + `, + []sema.SemanticError{ + &sema.TypeMismatchError{}, + }, + ) } func TestCheckResourceArrayMapInvalid(t *testing.T) { @@ -2673,42 +2653,17 @@ func TestCheckArrayToConstantSizedMissingTypeArgument(t *testing.T) { t.Parallel() - test := func(invalidTypeErrorFixesEnabled bool) { - - name := fmt.Sprintf("fixes enabled: %v", invalidTypeErrorFixesEnabled) - - t.Run(name, func(t *testing.T) { - t.Parallel() - - _, err := ParseAndCheckWithOptions(t, ` - fun test() { - let x: [Int16] = [1, 2, 3] - let y = x.toConstantSized() - } - `, - ParseAndCheckOptions{ - Config: &sema.Config{ - InvalidTypeErrorFixesEnabled: invalidTypeErrorFixesEnabled, - }, - }, - ) - - if invalidTypeErrorFixesEnabled { - errs := RequireCheckerErrors(t, err, 2) - - assert.IsType(t, &sema.InvocationTypeInferenceError{}, errs[0]) - assert.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1]) - } else { - errs := RequireCheckerErrors(t, err, 1) + _, err := ParseAndCheck(t, ` + fun test() { + let x: [Int16] = [1, 2, 3] + let y = x.toConstantSized() + } + `) - assert.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[0]) - } - }) - } + errs := RequireCheckerErrors(t, err, 2) - for _, invalidTypeErrorFixesEnabled := range []bool{true, false} { - test(invalidTypeErrorFixesEnabled) - } + assert.IsType(t, &sema.InvocationTypeInferenceError{}, errs[0]) + assert.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1]) } func TestCheckArrayReferenceTypeInference(t *testing.T) { diff --git a/runtime/tests/checker/builtinfunctions_test.go b/runtime/tests/checker/builtinfunctions_test.go index 68d23e86ec..79c931d13c 100644 --- a/runtime/tests/checker/builtinfunctions_test.go +++ b/runtime/tests/checker/builtinfunctions_test.go @@ -288,7 +288,6 @@ func TestCheckRevertibleRandom(t *testing.T) { BaseValueActivationHandler: func(_ common.Location) *sema.VariableActivation { return baseValueActivation }, - InvalidTypeErrorFixesEnabled: false, }, } } @@ -431,6 +430,7 @@ func TestCheckRevertibleRandom(t *testing.T) { "missing type argument", `let rand = revertibleRandom()`, []error{ + &sema.InvocationTypeInferenceError{}, &sema.TypeParameterTypeInferenceError{}, }, ) diff --git a/runtime/tests/checker/capability_test.go b/runtime/tests/checker/capability_test.go index e8154db6e2..a842ed161f 100644 --- a/runtime/tests/checker/capability_test.go +++ b/runtime/tests/checker/capability_test.go @@ -22,9 +22,7 @@ import ( "fmt" "testing" - "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/sema" - "github.com/onflow/cadence/runtime/stdlib" "github.com/onflow/cadence/runtime/tests/utils" "github.com/stretchr/testify/assert" @@ -78,50 +76,20 @@ func TestCheckCapability_borrow(t *testing.T) { t.Parallel() - testMissingTypeArgument := func(invalidTypeErrorFixesEnabled bool) { + t.Run("missing type argument", func(t *testing.T) { - name := fmt.Sprintf( - "missing type argument, error fixes enabled: %v", - invalidTypeErrorFixesEnabled, - ) + _, err := ParseAndCheckWithPanic(t, ` - t.Run(name, func(t *testing.T) { - t.Parallel() + let capability: Capability = panic("") - baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) - baseValueActivation.DeclareValue(stdlib.PanicFunction) + let r = capability.borrow() + `) - _, err := ParseAndCheckWithOptions(t, - ` - let capability: Capability = panic("") + errs := RequireCheckerErrors(t, err, 2) - let r = capability.borrow() - `, - ParseAndCheckOptions{ - Config: &sema.Config{ - BaseValueActivationHandler: func(_ common.Location) *sema.VariableActivation { - return baseValueActivation - }, - InvalidTypeErrorFixesEnabled: invalidTypeErrorFixesEnabled, - }, - }, - ) - if invalidTypeErrorFixesEnabled { - errs := RequireCheckerErrors(t, err, 2) - - require.IsType(t, &sema.InvocationTypeInferenceError{}, errs[0]) - require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1]) - } else { - errs := RequireCheckerErrors(t, err, 1) - - require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[0]) - } - }) - } - - for _, invalidTypeErrorFixesEnabled := range []bool{false, true} { - testMissingTypeArgument(invalidTypeErrorFixesEnabled) - } + require.IsType(t, &sema.InvocationTypeInferenceError{}, errs[0]) + require.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1]) + }) for _, auth := range []sema.Access{sema.UnauthorizedAccess, sema.NewEntitlementSetAccess([]*sema.EntitlementType{{ diff --git a/runtime/tests/checker/conditions_test.go b/runtime/tests/checker/conditions_test.go index 772741f51d..cfe5f73fab 100644 --- a/runtime/tests/checker/conditions_test.go +++ b/runtime/tests/checker/conditions_test.go @@ -19,7 +19,6 @@ package checker import ( - "fmt" "testing" "github.com/stretchr/testify/assert" @@ -255,87 +254,43 @@ func TestCheckInvalidFunctionPostConditionWithBeforeAndNoArgument(t *testing.T) t.Parallel() - test := func(invalidTypeErrorFixesEnabled bool) { + t.Run("test condition", func(t *testing.T) { + t.Parallel() - name := fmt.Sprintf( - "error fixes enabled: %v", - invalidTypeErrorFixesEnabled, - ) + _, err := ParseAndCheck(t, ` + fun test(x: Int) { + post { + before() != 0 + } + } + `) - t.Run(name, func(t *testing.T) { - t.Parallel() - - t.Run("test condition", func(t *testing.T) { - t.Parallel() - - _, err := ParseAndCheckWithOptions(t, - ` - fun test(x: Int) { - post { - before() != 0 - } - } - `, - ParseAndCheckOptions{ - Config: &sema.Config{ - InvalidTypeErrorFixesEnabled: invalidTypeErrorFixesEnabled, - }, - }, - ) - - if invalidTypeErrorFixesEnabled { - errs := RequireCheckerErrors(t, err, 3) - - assert.IsType(t, &sema.InsufficientArgumentsError{}, errs[0]) - assert.IsType(t, &sema.InvocationTypeInferenceError{}, errs[1]) - assert.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[2]) - } else { - errs := RequireCheckerErrors(t, err, 2) - - assert.IsType(t, &sema.InsufficientArgumentsError{}, errs[0]) - assert.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1]) - } - }) - - t.Run("emit condition", func(t *testing.T) { - t.Parallel() - - _, err := ParseAndCheckWithOptions(t, - ` - event Foo(x: Int) - - fun test(x: Int) { - post { - emit Foo(x: before()) - } - } - `, - ParseAndCheckOptions{ - Config: &sema.Config{ - InvalidTypeErrorFixesEnabled: invalidTypeErrorFixesEnabled, - }, - }, - ) - - if invalidTypeErrorFixesEnabled { - errs := RequireCheckerErrors(t, err, 3) - - assert.IsType(t, &sema.InsufficientArgumentsError{}, errs[0]) - assert.IsType(t, &sema.InvocationTypeInferenceError{}, errs[1]) - assert.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[2]) - } else { - errs := RequireCheckerErrors(t, err, 2) - - assert.IsType(t, &sema.InsufficientArgumentsError{}, errs[0]) - assert.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1]) - } - }) - }) - } + errs := RequireCheckerErrors(t, err, 3) + + assert.IsType(t, &sema.InsufficientArgumentsError{}, errs[0]) + assert.IsType(t, &sema.InvocationTypeInferenceError{}, errs[1]) + assert.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[2]) + }) + + t.Run("emit condition", func(t *testing.T) { + t.Parallel() - for _, invalidTypeErrorFixesEnabled := range []bool{true, false} { - test(invalidTypeErrorFixesEnabled) - } + _, err := ParseAndCheck(t, ` + event Foo(x: Int) + + fun test(x: Int) { + post { + emit Foo(x: before()) + } + } + `) + + errs := RequireCheckerErrors(t, err, 3) + + assert.IsType(t, &sema.InsufficientArgumentsError{}, errs[0]) + assert.IsType(t, &sema.InvocationTypeInferenceError{}, errs[1]) + assert.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[2]) + }) } func TestCheckInvalidFunctionPreConditionWithBefore(t *testing.T) { diff --git a/runtime/tests/checker/dictionary_test.go b/runtime/tests/checker/dictionary_test.go index 1887e8a454..e0efc29cce 100644 --- a/runtime/tests/checker/dictionary_test.go +++ b/runtime/tests/checker/dictionary_test.go @@ -19,7 +19,6 @@ package checker import ( - "fmt" "testing" "github.com/stretchr/testify/assert" @@ -32,49 +31,26 @@ func TestCheckIncompleteDictionaryType(t *testing.T) { t.Parallel() - test := func(invalidTypeErrorFixesEnabled bool) { - - name := fmt.Sprintf( - "error fixes enabled: %v", - invalidTypeErrorFixesEnabled, - ) - - t.Run(name, func(t *testing.T) { - t.Parallel() - - checker, err := ParseAndCheckWithOptions(t, - ` - let dict: {Int:} = {} - `, - ParseAndCheckOptions{ - IgnoreParseError: true, - Config: &sema.Config{ - InvalidTypeErrorFixesEnabled: invalidTypeErrorFixesEnabled, - }, - }, - ) - - if invalidTypeErrorFixesEnabled { - errs := RequireCheckerErrors(t, err, 1) + checker, err := ParseAndCheckWithOptions(t, + ` + let dict: {Int:} = {} + `, + ParseAndCheckOptions{ + IgnoreParseError: true, + }, + ) - assert.IsType(t, errs[0], &sema.UnconvertableTypeError{}) - } else { - require.NoError(t, err) - } + errs := RequireCheckerErrors(t, err, 1) - assert.Equal(t, - &sema.DictionaryType{ - KeyType: sema.IntType, - ValueType: sema.InvalidType, - }, - RequireGlobalValue(t, checker.Elaboration, "dict"), - ) - }) - } + assert.IsType(t, errs[0], &sema.UnconvertableTypeError{}) - for _, invalidTypeErrorFixesEnabled := range []bool{true, false} { - test(invalidTypeErrorFixesEnabled) - } + assert.Equal(t, + &sema.DictionaryType{ + KeyType: sema.IntType, + ValueType: sema.InvalidType, + }, + RequireGlobalValue(t, checker.Elaboration, "dict"), + ) } func TestCheckMetaKeyType(t *testing.T) { diff --git a/runtime/tests/checker/genericfunction_test.go b/runtime/tests/checker/genericfunction_test.go index b1310705f6..f7f89c87cf 100644 --- a/runtime/tests/checker/genericfunction_test.go +++ b/runtime/tests/checker/genericfunction_test.go @@ -431,67 +431,31 @@ func TestCheckGenericFunctionInvocation(t *testing.T) { t.Parallel() - test := func(invalidTypeErrorFixesEnabled bool) { - - name := fmt.Sprintf( - "error fixes enabled: %v", - invalidTypeErrorFixesEnabled, - ) - - t.Run(name, func(t *testing.T) { - t.Parallel() - - typeParameter := &sema.TypeParameter{ - Name: "T", - TypeBound: nil, - } - - baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) - baseValueActivation.DeclareValue(stdlib.StandardLibraryValue{ - Name: "test", - Type: &sema.FunctionType{ - TypeParameters: []*sema.TypeParameter{ - typeParameter, - }, - ReturnTypeAnnotation: sema.NewTypeAnnotation( - &sema.GenericType{ - TypeParameter: typeParameter, - }, - ), - }, - Kind: common.DeclarationKindConstant, - }) + typeParameter := &sema.TypeParameter{ + Name: "T", + TypeBound: nil, + } - _, err := ParseAndCheckWithOptions(t, - ` - let res = test() - `, - ParseAndCheckOptions{ - Config: &sema.Config{ - BaseValueActivationHandler: func(_ common.Location) *sema.VariableActivation { - return baseValueActivation - }, - InvalidTypeErrorFixesEnabled: invalidTypeErrorFixesEnabled, - }, + _, err := parseAndCheckWithTestValue(t, + ` + let res = test() + `, + &sema.FunctionType{ + TypeParameters: []*sema.TypeParameter{ + typeParameter, + }, + ReturnTypeAnnotation: sema.NewTypeAnnotation( + &sema.GenericType{ + TypeParameter: typeParameter, }, - ) - - if invalidTypeErrorFixesEnabled { - errs := RequireCheckerErrors(t, err, 2) - - assert.IsType(t, &sema.InvocationTypeInferenceError{}, errs[0]) - assert.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1]) - } else { - errs := RequireCheckerErrors(t, err, 1) + ), + }, + ) - assert.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[0]) - } - }) - } + errs := RequireCheckerErrors(t, err, 2) - for _, invalidTypeErrorFixesEnabled := range []bool{true, false} { - test(invalidTypeErrorFixesEnabled) - } + assert.IsType(t, &sema.InvocationTypeInferenceError{}, errs[0]) + assert.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1]) }) t.Run("valid: one type parameter, one type argument, no parameters, no arguments, return type", func(t *testing.T) { diff --git a/runtime/tests/checker/invalid_test.go b/runtime/tests/checker/invalid_test.go index aa7d1aba6a..3ed32e3205 100644 --- a/runtime/tests/checker/invalid_test.go +++ b/runtime/tests/checker/invalid_test.go @@ -19,7 +19,6 @@ package checker import ( - "fmt" "testing" "github.com/stretchr/testify/assert" @@ -211,123 +210,80 @@ func TestCheckInvalidInvocationFunctionReturnType(t *testing.T) { t.Parallel() - test := func(invalidTypeErrorFixesEnabled bool) { - - name := fmt.Sprintf( - "error fixes enabled: %v", - invalidTypeErrorFixesEnabled, - ) + typeParameter := &sema.TypeParameter{ + Name: "T", + } - t.Run(name, func(t *testing.T) { - t.Parallel() + fType := &sema.FunctionType{ + TypeParameters: []*sema.TypeParameter{ + typeParameter, + }, + ReturnTypeAnnotation: sema.NewTypeAnnotation( + &sema.GenericType{ + TypeParameter: typeParameter, + }, + ), + } - typeParameter := &sema.TypeParameter{ - Name: "T", - } + baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) + baseValueActivation.DeclareValue(stdlib.StandardLibraryValue{ + Type: fType, + Name: "f", + Kind: common.DeclarationKindFunction, + }) - fType := &sema.FunctionType{ - TypeParameters: []*sema.TypeParameter{ - typeParameter, - }, - ReturnTypeAnnotation: sema.NewTypeAnnotation( - &sema.GenericType{ - TypeParameter: typeParameter, - }, - ), - } - - baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) - baseValueActivation.DeclareValue(stdlib.StandardLibraryValue{ - Type: fType, - Name: "f", - Kind: common.DeclarationKindFunction, - }) - - _, err := ParseAndCheckWithOptions(t, - ` - let res = [f].reverse() - `, - ParseAndCheckOptions{ - Config: &sema.Config{ - BaseValueActivationHandler: func(_ common.Location) *sema.VariableActivation { - return baseValueActivation - }, - InvalidTypeErrorFixesEnabled: invalidTypeErrorFixesEnabled, - }, + _, err := ParseAndCheckWithOptions(t, + ` + let res = [f].reverse() + `, + ParseAndCheckOptions{ + Config: &sema.Config{ + BaseValueActivationHandler: func(_ common.Location) *sema.VariableActivation { + return baseValueActivation }, - ) - - if invalidTypeErrorFixesEnabled { - - errs := RequireCheckerErrors(t, err, 1) + }, + }, + ) - assert.IsType(t, &sema.InvocationTypeInferenceError{}, errs[0]) - } else { - require.NoError(t, err) - } - }) - } + errs := RequireCheckerErrors(t, err, 1) - for _, invalidTypeErrorFixesEnabled := range []bool{true, false} { - test(invalidTypeErrorFixesEnabled) - } + assert.IsType(t, &sema.InvocationTypeInferenceError{}, errs[0]) } func TestCheckInvalidTypeDefensiveCheck(t *testing.T) { t.Parallel() - test := func(invalidTypeErrorFixesEnabled bool) { - - name := fmt.Sprintf( - "error fixes enabled: %v", - invalidTypeErrorFixesEnabled, - ) - - t.Run(name, func(t *testing.T) { - t.Parallel() - - baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) - baseValueActivation.DeclareValue(stdlib.StandardLibraryValue{ - Type: sema.InvalidType, - Name: "invalid", - Kind: common.DeclarationKindConstant, - }) - - var r any - func() { - defer func() { - r = recover() - }() - - _, _ = ParseAndCheckWithOptions(t, - ` - let res = invalid - `, - ParseAndCheckOptions{ - Config: &sema.Config{ - BaseValueActivationHandler: func(_ common.Location) *sema.VariableActivation { - return baseValueActivation - }, - InvalidTypeErrorFixesEnabled: invalidTypeErrorFixesEnabled, - }, + baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) + baseValueActivation.DeclareValue(stdlib.StandardLibraryValue{ + Type: sema.InvalidType, + Name: "invalid", + Kind: common.DeclarationKindConstant, + }) + + var r any + func() { + defer func() { + r = recover() + }() + + _, _ = ParseAndCheckWithOptions(t, + ` + let res = invalid + `, + ParseAndCheckOptions{ + Config: &sema.Config{ + BaseValueActivationHandler: func(_ common.Location) *sema.VariableActivation { + return baseValueActivation }, - ) - }() - - if invalidTypeErrorFixesEnabled { - require.IsType(t, errors.UnexpectedError{}, r) - err := r.(errors.UnexpectedError) - require.ErrorContains(t, err, "invalid type produced without error") - } else { - require.Nil(t, r) - } - }) - } + }, + }, + ) + }() - for _, invalidTypeErrorFixesEnabled := range []bool{true, false} { - test(invalidTypeErrorFixesEnabled) - } + require.IsType(t, errors.UnexpectedError{}, r) + err := r.(errors.UnexpectedError) + require.ErrorContains(t, err, "invalid type produced without error") } func TestCheckInvalidTypeIndexing(t *testing.T) { diff --git a/runtime/tests/checker/invocation_test.go b/runtime/tests/checker/invocation_test.go index fc88ec267d..3d0d8f6949 100644 --- a/runtime/tests/checker/invocation_test.go +++ b/runtime/tests/checker/invocation_test.go @@ -19,7 +19,6 @@ package checker import ( - "fmt" "testing" "github.com/stretchr/testify/assert" @@ -598,79 +597,57 @@ func TestCheckInvocationWithIncorrectTypeParameter(t *testing.T) { t.Parallel() - test := func(invalidTypeErrorFixesEnabled bool) { - - name := fmt.Sprintf( - "error fixes enabled: %v", - invalidTypeErrorFixesEnabled, - ) - - t.Run(name, func(t *testing.T) { - t.Parallel() - - // function type has incorrect type-arguments: - // `fun Foo(_ a: R)` - // - funcType := &sema.FunctionType{ - ReturnTypeAnnotation: sema.VoidTypeAnnotation, - TypeParameters: []*sema.TypeParameter{ - { - Name: "T", - TypeBound: sema.AnyStructType, - }, - }, - Parameters: []sema.Parameter{ - { - Label: sema.ArgumentLabelNotRequired, - Identifier: "a", - TypeAnnotation: sema.NewTypeAnnotation( - &sema.GenericType{ - TypeParameter: &sema.TypeParameter{ - Name: "R", // This is an incorrect/undefined type-parameter - TypeBound: sema.AnyStructType, - }, - }, - ), - }, - }, - } - - baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) - baseValueActivation.DeclareValue(stdlib.NewStandardLibraryStaticFunction( - "foo", - funcType, - "", - nil, // no need, we only type-check - )) - - _, err := ParseAndCheckWithOptions(t, - ` - access(all) fun test() { - foo("hello") - } - `, - ParseAndCheckOptions{ - Config: &sema.Config{ - BaseValueActivationHandler: func(_ common.Location) *sema.VariableActivation { - return baseValueActivation + // function type has incorrect type-arguments: + // `fun Foo(_ a: R)` + // + funcType := &sema.FunctionType{ + ReturnTypeAnnotation: sema.VoidTypeAnnotation, + TypeParameters: []*sema.TypeParameter{ + { + Name: "T", + TypeBound: sema.AnyStructType, + }, + }, + Parameters: []sema.Parameter{ + { + Label: sema.ArgumentLabelNotRequired, + Identifier: "a", + TypeAnnotation: sema.NewTypeAnnotation( + &sema.GenericType{ + TypeParameter: &sema.TypeParameter{ + Name: "R", // This is an incorrect/undefined type-parameter + TypeBound: sema.AnyStructType, }, - InvalidTypeErrorFixesEnabled: invalidTypeErrorFixesEnabled, }, - }, - ) + ), + }, + }, + } - if invalidTypeErrorFixesEnabled { + baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) + baseValueActivation.DeclareValue(stdlib.NewStandardLibraryStaticFunction( + "foo", + funcType, + "", + nil, // no need, we only type-check + )) - errs := RequireCheckerErrors(t, err, 1) + _, err := ParseAndCheckWithOptions(t, + ` + access(all) fun test() { + foo("hello") + } + `, + ParseAndCheckOptions{ + Config: &sema.Config{ + BaseValueActivationHandler: func(_ common.Location) *sema.VariableActivation { + return baseValueActivation + }, + }, + }, + ) - assert.IsType(t, &sema.InvocationTypeInferenceError{}, errs[0]) - } else { - require.NoError(t, err) - } - }) - } + errs := RequireCheckerErrors(t, err, 1) - for _, invalidTypeErrorFixesEnabled := range []bool{true, false} { - test(invalidTypeErrorFixesEnabled) - } + assert.IsType(t, &sema.InvocationTypeInferenceError{}, errs[0]) } diff --git a/runtime/tests/checker/utils.go b/runtime/tests/checker/utils.go index c883e10c2d..9849a13d66 100644 --- a/runtime/tests/checker/utils.go +++ b/runtime/tests/checker/utils.go @@ -170,8 +170,6 @@ func ParseAndCheckWithOptionsAndMemoryMetering( } func RequireCheckerErrors(t *testing.T, err error, count int) []error { - t.Helper() - if count <= 0 { require.NoError(t, err) return nil