diff --git a/runtime/account_test.go b/runtime/account_test.go index a38777893a..a16a731a70 100644 --- a/runtime/account_test.go +++ b/runtime/account_test.go @@ -2361,7 +2361,7 @@ func TestGetAuthAccount(t *testing.T) { errs := checker.RequireCheckerErrors(t, err, 1) - assert.IsType(t, &sema.ArgumentCountError{}, errs[0]) + assert.IsType(t, &sema.InsufficientArgumentsError{}, errs[0]) }) t.Run("too many args", func(t *testing.T) { @@ -2388,7 +2388,7 @@ func TestGetAuthAccount(t *testing.T) { ) errs := checker.RequireCheckerErrors(t, err, 1) - assert.IsType(t, &sema.ArgumentCountError{}, errs[0]) + assert.IsType(t, &sema.ExcessiveArgumentsError{}, errs[0]) }) t.Run("transaction", func(t *testing.T) { diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index 3791f68676..67312a7469 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -423,12 +423,15 @@ func (interpreter *Interpreter) InvokeExternally( if argumentCount != parameterCount { - // if the function has defined optional parameters, - // then the provided arguments must be equal to or greater than - // the number of required parameters. - if functionType.RequiredArgumentCount == nil || - argumentCount < *functionType.RequiredArgumentCount { + if argumentCount < functionType.Arity.MinCount(parameterCount) { + return nil, ArgumentCountError{ + ParameterCount: parameterCount, + ArgumentCount: argumentCount, + } + } + maxCount := functionType.Arity.MaxCount(parameterCount) + if maxCount != nil && argumentCount > *maxCount { return nil, ArgumentCountError{ ParameterCount: parameterCount, ArgumentCount: argumentCount, @@ -1065,7 +1068,6 @@ func (interpreter *Interpreter) declareNonEnumCompositeValue( ReturnTypeAnnotation: sema.TypeAnnotation{ Type: compositeType, }, - RequiredArgumentCount: nil, } var initializerFunction FunctionValue diff --git a/runtime/sema/authaccount.go b/runtime/sema/authaccount.go index e1e880473c..9f879ec8ab 100644 --- a/runtime/sema/authaccount.go +++ b/runtime/sema/authaccount.go @@ -23,7 +23,7 @@ package sema var AuthAccountTypeLinkAccountFunctionTypePathParameterTypeAnnotation = AuthAccountTypeLinkAccountFunctionType.Parameters[0].TypeAnnotation func init() { - AuthAccountContractsTypeAddFunctionType.RequiredArgumentCount = RequiredArgumentCount(2) + AuthAccountContractsTypeAddFunctionType.Arity = &Arity{Min: 2} AuthAccountTypeGetCapabilityFunctionTypeParameterT.Optional = true PublicAccountTypeGetCapabilityFunctionTypeParameterT.Optional = true } diff --git a/runtime/sema/check_invocation_expression.go b/runtime/sema/check_invocation_expression.go index aee6b6652e..a1ffe69b42 100644 --- a/runtime/sema/check_invocation_expression.go +++ b/runtime/sema/check_invocation_expression.go @@ -374,7 +374,7 @@ func (checker *Checker) checkInvocation( returnType Type, ) { parameterCount := len(functionType.Parameters) - requiredArgumentCount := functionType.RequiredArgumentCount + arity := functionType.Arity typeParameterCount := len(functionType.TypeParameters) // Check the type arguments and bind them to type parameters @@ -421,7 +421,7 @@ func (checker *Checker) checkInvocation( checker.checkInvocationArgumentCount( argumentCount, parameterCount, - requiredArgumentCount, + arity, invocationExpression, ) @@ -592,23 +592,28 @@ func (checker *Checker) checkInvocationRequiredArgument( func (checker *Checker) checkInvocationArgumentCount( argumentCount int, parameterCount int, - requiredArgumentCount *int, + arity *Arity, pos ast.HasPosition, ) { - - if argumentCount == parameterCount { + minCount := arity.MinCount(parameterCount) + if argumentCount < minCount { + checker.report( + &InsufficientArgumentsError{ + MinCount: minCount, + ActualCount: argumentCount, + Range: ast.NewRangeFromPositioned(checker.memoryGauge, pos), + }, + ) return } - // TODO: improve - if requiredArgumentCount == nil || - argumentCount < *requiredArgumentCount { - + maxCount := arity.MaxCount(parameterCount) + if maxCount != nil && argumentCount > *maxCount { checker.report( - &ArgumentCountError{ - ParameterCount: parameterCount, - ArgumentCount: argumentCount, - Range: ast.NewRangeFromPositioned(checker.memoryGauge, pos), + &ExcessiveArgumentsError{ + MaxCount: *maxCount, + ActualCount: argumentCount, + Range: ast.NewRangeFromPositioned(checker.memoryGauge, pos), }, ) } diff --git a/runtime/sema/errors.go b/runtime/sema/errors.go index 9012a60450..42486ac4fb 100644 --- a/runtime/sema/errors.go +++ b/runtime/sema/errors.go @@ -411,31 +411,59 @@ func (e *NotCallableError) Error() string { ) } -// ArgumentCountError +// InsufficientArgumentsError -type ArgumentCountError struct { - ParameterCount int - ArgumentCount int +type InsufficientArgumentsError struct { + MinCount int + ActualCount int ast.Range } -var _ SemanticError = &ArgumentCountError{} -var _ errors.UserError = &ArgumentCountError{} -var _ errors.SecondaryError = &ArgumentCountError{} +var _ SemanticError = &InsufficientArgumentsError{} +var _ errors.UserError = &InsufficientArgumentsError{} +var _ errors.SecondaryError = &InsufficientArgumentsError{} -func (*ArgumentCountError) isSemanticError() {} +func (*InsufficientArgumentsError) isSemanticError() {} -func (*ArgumentCountError) IsUserError() {} +func (*InsufficientArgumentsError) IsUserError() {} -func (e *ArgumentCountError) Error() string { - return "incorrect number of arguments" +func (e *InsufficientArgumentsError) Error() string { + return "too few arguments" } -func (e *ArgumentCountError) SecondaryError() string { +func (e *InsufficientArgumentsError) SecondaryError() string { return fmt.Sprintf( - "expected %d, got %d", - e.ParameterCount, - e.ArgumentCount, + "expected at least %d, got %d", + e.MinCount, + e.ActualCount, + ) +} + +// ExcessiveArgumentsError + +type ExcessiveArgumentsError struct { + MaxCount int + ActualCount int + ast.Range +} + +var _ SemanticError = &ExcessiveArgumentsError{} +var _ errors.UserError = &ExcessiveArgumentsError{} +var _ errors.SecondaryError = &ExcessiveArgumentsError{} + +func (*ExcessiveArgumentsError) isSemanticError() {} + +func (*ExcessiveArgumentsError) IsUserError() {} + +func (e *ExcessiveArgumentsError) Error() string { + return "too many arguments" +} + +func (e *ExcessiveArgumentsError) SecondaryError() string { + return fmt.Sprintf( + "expected up to %d, got %d", + e.MaxCount, + e.ActualCount, ) } diff --git a/runtime/sema/type.go b/runtime/sema/type.go index e2746a10f0..b5a6d31c54 100644 --- a/runtime/sema/type.go +++ b/runtime/sema/type.go @@ -2655,10 +2655,38 @@ func formatFunctionType( return builder.String() } +// Arity + +type Arity struct { + Min int + Max int +} + +func (arity *Arity) MinCount(parameterCount int) int { + minCount := parameterCount + if arity != nil { + minCount = arity.Min + } + + return minCount +} + +func (arity *Arity) MaxCount(parameterCount int) *int { + maxCount := parameterCount + if arity != nil { + if arity.Max < parameterCount { + return nil + } + maxCount = arity.Max + } + + return &maxCount +} + // FunctionType type FunctionType struct { ReturnTypeAnnotation TypeAnnotation - RequiredArgumentCount *int + Arity *Arity ArgumentExpressionsCheck ArgumentExpressionsCheck Members *StringMemberOrderedMap TypeParameters []*TypeParameter @@ -2670,10 +2698,6 @@ type FunctionType struct { var _ Type = &FunctionType{} -func RequiredArgumentCount(count int) *int { - return &count -} - func (*FunctionType) IsType() {} func (t *FunctionType) Tag() TypeTag { @@ -2994,10 +3018,10 @@ func (t *FunctionType) RewriteWithRestrictedTypes() (Type, bool) { } return &FunctionType{ - TypeParameters: rewrittenTypeParameters, - Parameters: rewrittenParameters, - ReturnTypeAnnotation: NewTypeAnnotation(rewrittenReturnType), - RequiredArgumentCount: t.RequiredArgumentCount, + TypeParameters: rewrittenTypeParameters, + Parameters: rewrittenParameters, + ReturnTypeAnnotation: NewTypeAnnotation(rewrittenReturnType), + Arity: t.Arity, }, true } else { return t, false @@ -3106,9 +3130,9 @@ func (t *FunctionType) Resolve(typeArguments *TypeParameterTypeOrderedMap) Type } return &FunctionType{ - Parameters: newParameters, - ReturnTypeAnnotation: NewTypeAnnotation(newReturnType), - RequiredArgumentCount: t.RequiredArgumentCount, + Parameters: newParameters, + ReturnTypeAnnotation: NewTypeAnnotation(newReturnType), + Arity: t.Arity, } } diff --git a/runtime/stdlib/assert.go b/runtime/stdlib/assert.go index 9b6c73ba0f..7fc5da6456 100644 --- a/runtime/stdlib/assert.go +++ b/runtime/stdlib/assert.go @@ -50,7 +50,8 @@ var assertFunctionType = &sema.FunctionType{ ReturnTypeAnnotation: sema.NewTypeAnnotation( sema.VoidType, ), - RequiredArgumentCount: sema.RequiredArgumentCount(1), + // `message` parameter is optional + Arity: &sema.Arity{Min: 1, Max: 2}, } var AssertFunction = NewStandardLibraryFunction( diff --git a/runtime/stdlib/builtin_test.go b/runtime/stdlib/builtin_test.go index 6e1a7741b9..d0efd6c0a0 100644 --- a/runtime/stdlib/builtin_test.go +++ b/runtime/stdlib/builtin_test.go @@ -22,6 +22,7 @@ import ( "testing" "github.com/onflow/cadence/runtime/activations" + "github.com/onflow/cadence/runtime/tests/checker" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -86,10 +87,81 @@ func newInterpreter(t *testing.T, code string, valueDeclarations ...StandardLibr return inter } -func TestAssert(t *testing.T) { +func TestCheckAssert(t *testing.T) { t.Parallel() + baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) + baseValueActivation.DeclareValue(AssertFunction) + + parseAndCheck := func(t *testing.T, code string) (*sema.Checker, error) { + return checker.ParseAndCheckWithOptions(t, + code, + checker.ParseAndCheckOptions{ + Config: &sema.Config{ + BaseValueActivation: baseValueActivation, + }, + }, + ) + } + + t.Run("too few arguments", func(t *testing.T) { + + _, err := parseAndCheck(t, `let _ = assert()`) + + errs := checker.RequireCheckerErrors(t, err, 1) + require.IsType(t, errs[0], &sema.InsufficientArgumentsError{}) + }) + + t.Run("invalid first argument", func(t *testing.T) { + + _, err := parseAndCheck(t, `let _ = assert(1)`) + + errs := checker.RequireCheckerErrors(t, err, 1) + require.IsType(t, errs[0], &sema.TypeMismatchError{}) + }) + + t.Run("no message", func(t *testing.T) { + + _, err := parseAndCheck(t, `let _ = assert(true)`) + + require.NoError(t, err) + }) + + t.Run("with message", func(t *testing.T) { + + _, err := parseAndCheck(t, `let _ = assert(true, message: "foo")`) + + require.NoError(t, err) + }) + + t.Run("invalid message", func(t *testing.T) { + + _, err := parseAndCheck(t, `let _ = assert(true, message: 1)`) + + errs := checker.RequireCheckerErrors(t, err, 1) + require.IsType(t, errs[0], &sema.TypeMismatchError{}) + }) + + t.Run("missing argument label for message", func(t *testing.T) { + + _, err := parseAndCheck(t, `let _ = assert(true, "")`) + + errs := checker.RequireCheckerErrors(t, err, 1) + require.IsType(t, errs[0], &sema.MissingArgumentLabelError{}) + }) + + t.Run("too many arguments", func(t *testing.T) { + + _, err := parseAndCheck(t, `let _ = assert(true, message: "foo", true)`) + + errs := checker.RequireCheckerErrors(t, err, 1) + require.IsType(t, errs[0], &sema.ExcessiveArgumentsError{}) + }) +} + +func TestInterpretAssert(t *testing.T) { + inter := newInterpreter(t, `pub let test = assert`, AssertFunction, @@ -131,7 +203,58 @@ func TestAssert(t *testing.T) { assert.NoError(t, err) } -func TestPanic(t *testing.T) { +func TestCheckPanic(t *testing.T) { + + t.Parallel() + + baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) + baseValueActivation.DeclareValue(PanicFunction) + + parseAndCheck := func(t *testing.T, code string) (*sema.Checker, error) { + return checker.ParseAndCheckWithOptions(t, + code, + checker.ParseAndCheckOptions{ + Config: &sema.Config{ + BaseValueActivation: baseValueActivation, + }, + }, + ) + } + + t.Run("too few arguments", func(t *testing.T) { + + _, err := parseAndCheck(t, `let _ = panic()`) + + errs := checker.RequireCheckerErrors(t, err, 1) + require.IsType(t, errs[0], &sema.InsufficientArgumentsError{}) + }) + + t.Run("message", func(t *testing.T) { + + _, err := parseAndCheck(t, `let _ = panic("test")`) + + require.NoError(t, err) + + }) + + t.Run("invalid message", func(t *testing.T) { + + _, err := parseAndCheck(t, `let _ = panic(true)`) + + errs := checker.RequireCheckerErrors(t, err, 1) + require.IsType(t, errs[0], &sema.TypeMismatchError{}) + }) + + t.Run("too many arguments", func(t *testing.T) { + + _, err := parseAndCheck(t, `let _ = panic("test", 1)`) + + errs := checker.RequireCheckerErrors(t, err, 1) + require.IsType(t, errs[0], &sema.ExcessiveArgumentsError{}) + }) +} + +func TestInterpretPanic(t *testing.T) { t.Parallel() diff --git a/runtime/stdlib/test_contract.go b/runtime/stdlib/test_contract.go index 1305225812..1513b356fa 100644 --- a/runtime/stdlib/test_contract.go +++ b/runtime/stdlib/test_contract.go @@ -75,7 +75,8 @@ var testTypeAssertFunctionType = &sema.FunctionType{ ReturnTypeAnnotation: sema.NewTypeAnnotation( sema.VoidType, ), - RequiredArgumentCount: sema.RequiredArgumentCount(1), + // `message` parameter is optional + Arity: &sema.Arity{Min: 1, Max: 2}, } var testTypeAssertFunction = interpreter.NewUnmeteredHostFunctionValue( @@ -132,7 +133,6 @@ var testTypeAssertEqualFunctionType = &sema.FunctionType{ ), }, }, - RequiredArgumentCount: sema.RequiredArgumentCount(2), ReturnTypeAnnotation: sema.NewTypeAnnotation( sema.VoidType, ), @@ -195,7 +195,8 @@ var testTypeFailFunctionType = &sema.FunctionType{ ReturnTypeAnnotation: sema.NewTypeAnnotation( sema.VoidType, ), - RequiredArgumentCount: sema.RequiredArgumentCount(0), + // `message` parameter is optional + Arity: &sema.Arity{Min: 0, Max: 1}, } var testTypeFailFunction = interpreter.NewUnmeteredHostFunctionValue( @@ -910,7 +911,6 @@ func newTestTypeExpectFailureFunctionType() *sema.FunctionType { ReturnTypeAnnotation: sema.NewTypeAnnotation( sema.VoidType, ), - RequiredArgumentCount: sema.RequiredArgumentCount(2), } } diff --git a/runtime/tests/checker/arrays_dictionaries_test.go b/runtime/tests/checker/arrays_dictionaries_test.go index 9bcf610093..ad551e260b 100644 --- a/runtime/tests/checker/arrays_dictionaries_test.go +++ b/runtime/tests/checker/arrays_dictionaries_test.go @@ -963,7 +963,7 @@ func TestCheckInvalidArrayRemoveFirst(t *testing.T) { errs := RequireCheckerErrors(t, err, 1) - assert.IsType(t, &sema.ArgumentCountError{}, errs[0]) + assert.IsType(t, &sema.ExcessiveArgumentsError{}, errs[0]) } func TestCheckInvalidArrayRemoveFirstFromConstantSized(t *testing.T) { diff --git a/runtime/tests/checker/builtinfunctions_test.go b/runtime/tests/checker/builtinfunctions_test.go index bf8c135c41..12e0cb4f14 100644 --- a/runtime/tests/checker/builtinfunctions_test.go +++ b/runtime/tests/checker/builtinfunctions_test.go @@ -130,8 +130,8 @@ func TestCheckAddressFromBytes(t *testing.T) { runInvalidCase(t, "[\"abc\"]", &sema.TypeMismatchError{}) runInvalidCase(t, "1", &sema.TypeMismatchError{}) - runInvalidCase(t, "[1], [2, 3, 4]", &sema.ArgumentCountError{}) - runInvalidCase(t, "", &sema.ArgumentCountError{}) + runInvalidCase(t, "[1], [2, 3, 4]", &sema.ExcessiveArgumentsError{}) + runInvalidCase(t, "", &sema.InsufficientArgumentsError{}) runInvalidCase(t, "typo: [1]", &sema.IncorrectArgumentLabelError{}) } @@ -180,8 +180,8 @@ func TestCheckAddressFromString(t *testing.T) { runInvalidCase(t, "[1232]", &sema.TypeMismatchError{}) runInvalidCase(t, "1", &sema.TypeMismatchError{}) - runInvalidCase(t, "\"0x1\", \"0x2\"", &sema.ArgumentCountError{}) - runInvalidCase(t, "", &sema.ArgumentCountError{}) + runInvalidCase(t, "\"0x1\", \"0x2\"", &sema.ExcessiveArgumentsError{}) + runInvalidCase(t, "", &sema.InsufficientArgumentsError{}) runInvalidCase(t, "typo: \"0x1\"", &sema.IncorrectArgumentLabelError{}) } @@ -262,8 +262,8 @@ func TestCheckFromBigEndianBytes(t *testing.T) { runValidCase(t, ty, "[1, 2, 100, 4, 45, 12]") runInvalidCase(t, ty, "\"abcd\"", &sema.TypeMismatchError{}) - runInvalidCase(t, ty, "", &sema.ArgumentCountError{}) - runInvalidCase(t, ty, "[1], [2, 4]", &sema.ArgumentCountError{}) + runInvalidCase(t, ty, "", &sema.InsufficientArgumentsError{}) + runInvalidCase(t, ty, "[1], [2, 4]", &sema.ExcessiveArgumentsError{}) runInvalidCase(t, ty, "typo: [1]", &sema.IncorrectArgumentLabelError{}) } } diff --git a/runtime/tests/checker/casting_test.go b/runtime/tests/checker/casting_test.go index 0b5ac1f9ed..043d419504 100644 --- a/runtime/tests/checker/casting_test.go +++ b/runtime/tests/checker/casting_test.go @@ -6049,8 +6049,7 @@ func TestCheckStaticCastElaboration(t *testing.T) { ), }, }, - ReturnTypeAnnotation: sema.NewTypeAnnotation(sema.VoidType), - RequiredArgumentCount: nil, + ReturnTypeAnnotation: sema.NewTypeAnnotation(sema.VoidType), }, ) diff --git a/runtime/tests/checker/conditions_test.go b/runtime/tests/checker/conditions_test.go index a7fe05a0f3..c90e5f814d 100644 --- a/runtime/tests/checker/conditions_test.go +++ b/runtime/tests/checker/conditions_test.go @@ -145,7 +145,7 @@ func TestCheckInvalidFunctionPostConditionWithBeforeAndNoArgument(t *testing.T) errs := RequireCheckerErrors(t, err, 2) - assert.IsType(t, &sema.ArgumentCountError{}, errs[0]) + assert.IsType(t, &sema.InsufficientArgumentsError{}, errs[0]) assert.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1]) } diff --git a/runtime/tests/checker/genericfunction_test.go b/runtime/tests/checker/genericfunction_test.go index 1282d8753a..326221b719 100644 --- a/runtime/tests/checker/genericfunction_test.go +++ b/runtime/tests/checker/genericfunction_test.go @@ -66,10 +66,7 @@ func TestCheckGenericFunction(t *testing.T) { variant, ), &sema.FunctionType{ - TypeParameters: nil, - Parameters: nil, - ReturnTypeAnnotation: sema.NewTypeAnnotation(sema.VoidType), - RequiredArgumentCount: nil, + ReturnTypeAnnotation: sema.NewTypeAnnotation(sema.VoidType), }, ) @@ -91,10 +88,7 @@ func TestCheckGenericFunction(t *testing.T) { let res = test() `, &sema.FunctionType{ - TypeParameters: nil, - Parameters: nil, - ReturnTypeAnnotation: sema.NewTypeAnnotation(sema.VoidType), - RequiredArgumentCount: nil, + ReturnTypeAnnotation: sema.NewTypeAnnotation(sema.VoidType), }, ) @@ -120,9 +114,7 @@ func TestCheckGenericFunction(t *testing.T) { TypeParameters: []*sema.TypeParameter{ typeParameter, }, - Parameters: nil, - ReturnTypeAnnotation: sema.NewTypeAnnotation(sema.VoidType), - RequiredArgumentCount: nil, + ReturnTypeAnnotation: sema.NewTypeAnnotation(sema.VoidType), }, ) @@ -148,9 +140,7 @@ func TestCheckGenericFunction(t *testing.T) { TypeParameters: []*sema.TypeParameter{ typeParameter, }, - Parameters: nil, - ReturnTypeAnnotation: sema.NewTypeAnnotation(sema.VoidType), - RequiredArgumentCount: nil, + ReturnTypeAnnotation: sema.NewTypeAnnotation(sema.VoidType), }, ) @@ -199,8 +189,7 @@ func TestCheckGenericFunction(t *testing.T) { ), }, }, - ReturnTypeAnnotation: sema.NewTypeAnnotation(sema.VoidType), - RequiredArgumentCount: nil, + ReturnTypeAnnotation: sema.NewTypeAnnotation(sema.VoidType), }, ) @@ -249,14 +238,13 @@ func TestCheckGenericFunction(t *testing.T) { ), }, }, - ReturnTypeAnnotation: sema.NewTypeAnnotation(sema.VoidType), - RequiredArgumentCount: nil, + ReturnTypeAnnotation: sema.NewTypeAnnotation(sema.VoidType), }, ) errs := RequireCheckerErrors(t, err, 2) - assert.IsType(t, &sema.ArgumentCountError{}, errs[0]) + assert.IsType(t, &sema.InsufficientArgumentsError{}, errs[0]) assert.IsType(t, &sema.TypeParameterTypeInferenceError{}, errs[1]) }) @@ -288,8 +276,7 @@ func TestCheckGenericFunction(t *testing.T) { ), }, }, - ReturnTypeAnnotation: sema.NewTypeAnnotation(sema.VoidType), - RequiredArgumentCount: nil, + ReturnTypeAnnotation: sema.NewTypeAnnotation(sema.VoidType), }, ) @@ -327,8 +314,7 @@ func TestCheckGenericFunction(t *testing.T) { ), }, }, - ReturnTypeAnnotation: sema.NewTypeAnnotation(sema.VoidType), - RequiredArgumentCount: nil, + ReturnTypeAnnotation: sema.NewTypeAnnotation(sema.VoidType), }, ) @@ -372,8 +358,7 @@ func TestCheckGenericFunction(t *testing.T) { ), }, }, - ReturnTypeAnnotation: sema.NewTypeAnnotation(sema.VoidType), - RequiredArgumentCount: nil, + ReturnTypeAnnotation: sema.NewTypeAnnotation(sema.VoidType), }, ) @@ -431,8 +416,7 @@ func TestCheckGenericFunction(t *testing.T) { ), }, }, - ReturnTypeAnnotation: sema.NewTypeAnnotation(sema.VoidType), - RequiredArgumentCount: nil, + ReturnTypeAnnotation: sema.NewTypeAnnotation(sema.VoidType), }, ) @@ -459,13 +443,11 @@ func TestCheckGenericFunction(t *testing.T) { TypeParameters: []*sema.TypeParameter{ typeParameter, }, - Parameters: nil, ReturnTypeAnnotation: sema.NewTypeAnnotation( &sema.GenericType{ TypeParameter: typeParameter, }, ), - RequiredArgumentCount: nil, }, ) @@ -491,13 +473,11 @@ func TestCheckGenericFunction(t *testing.T) { TypeParameters: []*sema.TypeParameter{ typeParameter, }, - Parameters: nil, ReturnTypeAnnotation: sema.NewTypeAnnotation( &sema.GenericType{ TypeParameter: typeParameter, }, ), - RequiredArgumentCount: nil, }, ) @@ -556,7 +536,6 @@ func TestCheckGenericFunction(t *testing.T) { TypeParameter: typeParameter, }, ), - RequiredArgumentCount: nil, }, ) @@ -599,9 +578,7 @@ func TestCheckGenericFunction(t *testing.T) { TypeParameters: []*sema.TypeParameter{ typeParameter, }, - Parameters: nil, - ReturnTypeAnnotation: sema.NewTypeAnnotation(sema.VoidType), - RequiredArgumentCount: nil, + ReturnTypeAnnotation: sema.NewTypeAnnotation(sema.VoidType), }, ) @@ -639,9 +616,7 @@ func TestCheckGenericFunction(t *testing.T) { TypeParameters: []*sema.TypeParameter{ typeParameter, }, - Parameters: nil, - ReturnTypeAnnotation: sema.NewTypeAnnotation(sema.VoidType), - RequiredArgumentCount: nil, + ReturnTypeAnnotation: sema.NewTypeAnnotation(sema.VoidType), }, ) @@ -678,8 +653,7 @@ func TestCheckGenericFunction(t *testing.T) { ), }, }, - ReturnTypeAnnotation: sema.NewTypeAnnotation(sema.VoidType), - RequiredArgumentCount: nil, + ReturnTypeAnnotation: sema.NewTypeAnnotation(sema.VoidType), }, ) @@ -757,7 +731,6 @@ func TestCheckGenericFunction(t *testing.T) { }, ), ), - RequiredArgumentCount: nil, }, ) @@ -865,7 +838,6 @@ func TestCheckGenericFunction(t *testing.T) { }, ), ), - RequiredArgumentCount: nil, }, ) @@ -905,8 +877,7 @@ func TestCheckGenericFunctionIsInvalid(t *testing.T) { ), }, }, - ReturnTypeAnnotation: sema.NewTypeAnnotation(sema.VoidType), - RequiredArgumentCount: nil, + ReturnTypeAnnotation: sema.NewTypeAnnotation(sema.VoidType), } assert.False(t, genericFunctionType.IsInvalidType()) diff --git a/runtime/tests/checker/integer_test.go b/runtime/tests/checker/integer_test.go index c07a7b6163..6f55526aba 100644 --- a/runtime/tests/checker/integer_test.go +++ b/runtime/tests/checker/integer_test.go @@ -475,7 +475,7 @@ func TestCheckInvalidIntegerConversionFunctionWithoutArgs(t *testing.T) { errs := RequireCheckerErrors(t, err, 1) - assert.IsType(t, &sema.ArgumentCountError{}, errs[0]) + assert.IsType(t, &sema.InsufficientArgumentsError{}, errs[0]) }) } diff --git a/runtime/tests/checker/invocation_test.go b/runtime/tests/checker/invocation_test.go index 31757f21b9..f5f6239428 100644 --- a/runtime/tests/checker/invocation_test.go +++ b/runtime/tests/checker/invocation_test.go @@ -44,7 +44,7 @@ func TestCheckInvalidFunctionCallWithTooFewArguments(t *testing.T) { errs := RequireCheckerErrors(t, err, 1) - assert.IsType(t, &sema.ArgumentCountError{}, errs[0]) + assert.IsType(t, &sema.InsufficientArgumentsError{}, errs[0]) } func TestCheckFunctionCallWithArgumentLabel(t *testing.T) { @@ -172,8 +172,7 @@ func TestCheckInvalidFunctionCallWithTooManyArguments(t *testing.T) { errs := RequireCheckerErrors(t, err, 2) - assert.IsType(t, &sema.ArgumentCountError{}, errs[0]) - + assert.IsType(t, &sema.ExcessiveArgumentsError{}, errs[0]) assert.IsType(t, &sema.MissingArgumentLabelError{}, errs[1]) } @@ -319,11 +318,7 @@ func TestCheckInvocationWithOnlyVarargs(t *testing.T) { ReturnTypeAnnotation: sema.TypeAnnotation{ Type: sema.VoidType, }, - RequiredArgumentCount: func() *int { - // NOTE: important to check *all* arguments are optional - var count = 0 - return &count - }(), + Arity: &sema.Arity{Max: -1}, }, "", nil, diff --git a/runtime/tests/checker/metatype_test.go b/runtime/tests/checker/metatype_test.go index 5ad0277773..44f65b4b6d 100644 --- a/runtime/tests/checker/metatype_test.go +++ b/runtime/tests/checker/metatype_test.go @@ -134,7 +134,7 @@ func TestCheckIsInstance(t *testing.T) { code: ` let result = (1).isInstance(Type(), Type()) `, - expectedErrorType: &sema.ArgumentCountError{}, + expectedErrorType: &sema.ExcessiveArgumentsError{}, }, } @@ -209,7 +209,7 @@ func TestCheckIsSubtype(t *testing.T) { code: ` let result = Type().isSubtype() `, - expectedErrorType: &sema.ArgumentCountError{}, + expectedErrorType: &sema.InsufficientArgumentsError{}, }, { name: "isSubtype argument must be named", @@ -223,7 +223,7 @@ func TestCheckIsSubtype(t *testing.T) { code: ` let result = Type().isSubtype(of: Type(), Type()) `, - expectedErrorType: &sema.ArgumentCountError{}, + expectedErrorType: &sema.ExcessiveArgumentsError{}, }, } diff --git a/runtime/tests/checker/runtimetype_test.go b/runtime/tests/checker/runtimetype_test.go index bee08077e9..a2aea9373f 100644 --- a/runtime/tests/checker/runtimetype_test.go +++ b/runtime/tests/checker/runtimetype_test.go @@ -70,14 +70,14 @@ func TestCheckOptionalTypeConstructor(t *testing.T) { code: ` let result = OptionalType(Type(), Type()) `, - expectedError: &sema.ArgumentCountError{}, + expectedError: &sema.ExcessiveArgumentsError{}, }, { name: "too few args", code: ` let result = OptionalType() `, - expectedError: &sema.ArgumentCountError{}, + expectedError: &sema.InsufficientArgumentsError{}, }, } @@ -142,14 +142,14 @@ func TestCheckVariableSizedArrayTypeConstructor(t *testing.T) { code: ` let result = VariableSizedArrayType(Type(), Type()) `, - expectedError: &sema.ArgumentCountError{}, + expectedError: &sema.ExcessiveArgumentsError{}, }, { name: "too few args", code: ` let result = VariableSizedArrayType() `, - expectedError: &sema.ArgumentCountError{}, + expectedError: &sema.InsufficientArgumentsError{}, }, } @@ -221,21 +221,21 @@ func TestCheckConstantSizedArrayTypeConstructor(t *testing.T) { code: ` let result = ConstantSizedArrayType(type:Type(), size: 3, 4) `, - expectedError: &sema.ArgumentCountError{}, + expectedError: &sema.ExcessiveArgumentsError{}, }, { name: "one arg", code: ` let result = ConstantSizedArrayType(type: Type()) `, - expectedError: &sema.ArgumentCountError{}, + expectedError: &sema.InsufficientArgumentsError{}, }, { name: "no args", code: ` let result = ConstantSizedArrayType() `, - expectedError: &sema.ArgumentCountError{}, + expectedError: &sema.InsufficientArgumentsError{}, }, { name: "second label missing", @@ -322,21 +322,21 @@ func TestCheckDictionaryTypeConstructor(t *testing.T) { code: ` let result = DictionaryType(key: Type(), value: Type(), 4) `, - expectedError: &sema.ArgumentCountError{}, + expectedError: &sema.ExcessiveArgumentsError{}, }, { name: "one arg", code: ` let result = DictionaryType(key: Type()) `, - expectedError: &sema.ArgumentCountError{}, + expectedError: &sema.InsufficientArgumentsError{}, }, { name: "no args", code: ` let result = DictionaryType() `, - expectedError: &sema.ArgumentCountError{}, + expectedError: &sema.InsufficientArgumentsError{}, }, { name: "first label missing", @@ -400,14 +400,14 @@ func TestCheckCompositeTypeConstructor(t *testing.T) { code: ` let result = CompositeType("", 3) `, - expectedError: &sema.ArgumentCountError{}, + expectedError: &sema.ExcessiveArgumentsError{}, }, { name: "no args", code: ` let result = CompositeType() `, - expectedError: &sema.ArgumentCountError{}, + expectedError: &sema.InsufficientArgumentsError{}, }, } @@ -457,14 +457,14 @@ func TestCheckInterfaceTypeConstructor(t *testing.T) { code: ` let result = InterfaceType("", 3) `, - expectedError: &sema.ArgumentCountError{}, + expectedError: &sema.ExcessiveArgumentsError{}, }, { name: "no args", code: ` let result = InterfaceType() `, - expectedError: &sema.ArgumentCountError{}, + expectedError: &sema.InsufficientArgumentsError{}, }, } @@ -535,21 +535,21 @@ func TestCheckFunctionTypeConstructor(t *testing.T) { code: ` let result = FunctionType(parameters: [Type(), Type()], return: Type(), 4) `, - expectedError: &sema.ArgumentCountError{}, + expectedError: &sema.ExcessiveArgumentsError{}, }, { name: "one arg", code: ` let result = FunctionType(parameters: [Type(), Type()]) `, - expectedError: &sema.ArgumentCountError{}, + expectedError: &sema.InsufficientArgumentsError{}, }, { name: "no args", code: ` let result = FunctionType() `, - expectedError: &sema.ArgumentCountError{}, + expectedError: &sema.InsufficientArgumentsError{}, }, { name: "first label missing", @@ -628,21 +628,21 @@ func TestCheckReferenceTypeConstructor(t *testing.T) { code: ` let result = ReferenceType(authorized: true, type: Type(), Type()) `, - expectedError: &sema.ArgumentCountError{}, + expectedError: &sema.ExcessiveArgumentsError{}, }, { name: "one arg", code: ` let result = ReferenceType(authorized: true) `, - expectedError: &sema.ArgumentCountError{}, + expectedError: &sema.InsufficientArgumentsError{}, }, { name: "no args", code: ` let result = ReferenceType() `, - expectedError: &sema.ArgumentCountError{}, + expectedError: &sema.InsufficientArgumentsError{}, }, { name: "first label missing", @@ -731,21 +731,21 @@ func TestCheckRestrictedTypeConstructor(t *testing.T) { code: ` let result = RestrictedType(identifier: "A", restrictions: ["I1"], ["I2"]) `, - expectedError: &sema.ArgumentCountError{}, + expectedError: &sema.ExcessiveArgumentsError{}, }, { name: "one arg", code: ` let result = RestrictedType(identifier: "A") `, - expectedError: &sema.ArgumentCountError{}, + expectedError: &sema.InsufficientArgumentsError{}, }, { name: "no args", code: ` let result = RestrictedType() `, - expectedError: &sema.ArgumentCountError{}, + expectedError: &sema.InsufficientArgumentsError{}, }, { name: "missing first label", @@ -824,14 +824,14 @@ func TestCheckCapabilityTypeConstructor(t *testing.T) { code: ` let result = CapabilityType(Type(), Type()) `, - expectedError: &sema.ArgumentCountError{}, + expectedError: &sema.ExcessiveArgumentsError{}, }, { name: "too few args", code: ` let result = CapabilityType() `, - expectedError: &sema.ArgumentCountError{}, + expectedError: &sema.InsufficientArgumentsError{}, }, } diff --git a/runtime/tests/checker/type_inference_test.go b/runtime/tests/checker/type_inference_test.go index 97c0a0aa1b..af024b4f16 100644 --- a/runtime/tests/checker/type_inference_test.go +++ b/runtime/tests/checker/type_inference_test.go @@ -363,8 +363,7 @@ func TestCheckFunctionArgumentTypeInference(t *testing.T) { ), }, }, - ReturnTypeAnnotation: sema.NewTypeAnnotation(sema.VoidType), - RequiredArgumentCount: nil, + ReturnTypeAnnotation: sema.NewTypeAnnotation(sema.VoidType), }, ) diff --git a/runtime/tests/interpreter/interpreter_test.go b/runtime/tests/interpreter/interpreter_test.go index 5e56e230bd..b74c7c47ab 100644 --- a/runtime/tests/interpreter/interpreter_test.go +++ b/runtime/tests/interpreter/interpreter_test.go @@ -1985,7 +1985,111 @@ func TestInterpretHostFunctionWithVariableArguments(t *testing.T) { ReturnTypeAnnotation: sema.NewTypeAnnotation( sema.VoidType, ), - RequiredArgumentCount: sema.RequiredArgumentCount(1), + Arity: &sema.Arity{Min: 1}, + }, + ``, + func(invocation interpreter.Invocation) interpreter.Value { + called = true + + require.Len(t, invocation.ArgumentTypes, 3) + assert.IsType(t, sema.IntType, invocation.ArgumentTypes[0]) + assert.IsType(t, sema.BoolType, invocation.ArgumentTypes[1]) + assert.IsType(t, sema.StringType, invocation.ArgumentTypes[2]) + + require.Len(t, invocation.Arguments, 3) + + inter := invocation.Interpreter + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredIntValueFromInt64(1), + invocation.Arguments[0], + ) + + AssertValuesEqual( + t, + inter, + interpreter.TrueValue, + invocation.Arguments[1], + ) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredStringValue("test"), + invocation.Arguments[2], + ) + + return interpreter.Void + }, + ) + + baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) + baseValueActivation.DeclareValue(testFunction) + + checker, err := sema.NewChecker( + program, + TestLocation, + nil, + &sema.Config{ + BaseValueActivation: baseValueActivation, + AccessCheckMode: sema.AccessCheckModeStrict, + }, + ) + require.NoError(t, err) + + err = checker.Check() + require.NoError(t, err) + + storage := newUnmeteredInMemoryStorage() + + baseActivation := activations.NewActivation(nil, interpreter.BaseActivation) + interpreter.Declare(baseActivation, testFunction) + + inter, err := interpreter.NewInterpreter( + interpreter.ProgramFromChecker(checker), + checker.Location, + &interpreter.Config{ + Storage: storage, + BaseActivation: baseActivation, + }, + ) + require.NoError(t, err) + + err = inter.Interpret() + require.NoError(t, err) + + assert.True(t, called) +} + +func TestInterpretHostFunctionWithOptionalArguments(t *testing.T) { + + t.Parallel() + + const code = ` + pub let nothing = test(1, true, "test") + ` + program, err := parser.ParseProgram(nil, []byte(code), parser.Config{}) + + require.NoError(t, err) + + called := false + + testFunction := stdlib.NewStandardLibraryFunction( + "test", + &sema.FunctionType{ + Parameters: []sema.Parameter{ + { + Label: sema.ArgumentLabelNotRequired, + Identifier: "value", + TypeAnnotation: sema.NewTypeAnnotation(sema.IntType), + }, + }, + ReturnTypeAnnotation: sema.NewTypeAnnotation( + sema.VoidType, + ), + Arity: &sema.Arity{Min: 1, Max: 3}, }, ``, func(invocation interpreter.Invocation) interpreter.Value {