Skip to content

Commit

Permalink
Add ability to specify custom type parameter validation
Browse files Browse the repository at this point in the history
  • Loading branch information
darkdrag00nv2 committed Dec 2, 2023
1 parent ecd3f23 commit 7b65121
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 2 deletions.
15 changes: 15 additions & 0 deletions runtime/sema/check_invocation_expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,21 @@ func (checker *Checker) checkInvocation(
invocationExpression,
)

// The invokable type might have special checks for the type parameters.

if functionType.TypePrametersCheck != nil {
invocationRange := ast.NewRangeFromPositioned(
checker.memoryGauge,
invocationExpression,
)

functionType.TypePrametersCheck(
typeArguments,
checker.report,
invocationRange,
)
}

// Save types in the elaboration

checker.Elaboration.SetInvocationExpressionTypes(
Expand Down
2 changes: 1 addition & 1 deletion runtime/sema/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -3746,7 +3746,7 @@ type InvalidTypeArgumentError struct {
var _ SemanticError = &InvalidTypeArgumentError{}
var _ errors.UserError = &InvalidTypeArgumentError{}

func (InvalidTypeArgumentError) isSemanticError() {}
func (*InvalidTypeArgumentError) isSemanticError() {}

func (*InvalidTypeArgumentError) IsUserError() {}

Expand Down
7 changes: 7 additions & 0 deletions runtime/sema/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -2906,6 +2906,7 @@ type FunctionType struct {
ReturnTypeAnnotation TypeAnnotation
Arity *Arity
ArgumentExpressionsCheck ArgumentExpressionsCheck
TypePrametersCheck TypeParametersCheck

This comment has been minimized.

Copy link
@turbolent

turbolent Dec 4, 2023

Member

Great idea, nice! Small nit: Just like the ArgumentExpressionsCheck checks arguments passed to parameters, this check checks the type arguments passed to the type parameters. Maybe rename this field and the type with the same name to TypeArumentsCheck

This comment has been minimized.

Copy link
@darkdrag00nv2

darkdrag00nv2 Dec 5, 2023

Author Contributor

Yeah, good point. Updated the name to TypeArgumentsCheck

Members *StringMemberOrderedMap
TypeParameters []*TypeParameter
Parameters []Parameter
Expand Down Expand Up @@ -3376,6 +3377,12 @@ type ArgumentExpressionsCheck func(
invocationRange ast.Range,
)

type TypeParametersCheck func(
typeArguments *TypeParameterTypeOrderedMap,
report func(err error),
invocationRange ast.Range,
)

// BaseTypeActivation is the base activation that contains
// the types available in programs
var BaseTypeActivation = NewVariableActivation(nil)
Expand Down
19 changes: 19 additions & 0 deletions runtime/stdlib/range.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ package stdlib
import (
"fmt"

"github.com/onflow/cadence/runtime/ast"
"github.com/onflow/cadence/runtime/errors"
"github.com/onflow/cadence/runtime/interpreter"
"github.com/onflow/cadence/runtime/sema"
Expand Down Expand Up @@ -74,6 +75,24 @@ var inclusiveRangeConstructorFunctionType = func() *sema.FunctionType {
),
// `step` parameter is optional
Arity: &sema.Arity{Min: 2, Max: 3},
TypePrametersCheck: func(typeArguments *sema.TypeParameterTypeOrderedMap, report func(error), invocationRange ast.Range) {
memberType, ok := typeArguments.Get(typeParameter)
if !ok || memberType == nil {
// checker should prevent this
panic(errors.NewUnreachableError())
}

// memberType must only be a leaf integer type.
for _, ty := range sema.AllNonLeafIntegerTypes {
if memberType == ty {
report(&sema.InvalidTypeArgumentError{
TypeArgumentName: typeParameter.Name,
Range: invocationRange,
Details: fmt.Sprintf("Creation of InclusiveRange<%s> is disallowed", memberType),
})
}
}
},
}
}()

Expand Down
17 changes: 16 additions & 1 deletion runtime/tests/checker/range_value_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -398,13 +398,28 @@ func TestInclusiveRangeNonLeafIntegerTypes(t *testing.T) {
_, err := ParseAndCheckWithOptions(t, fmt.Sprintf(`
let a: %[1]s = 0
let b: %[1]s = 10
var range: InclusiveRange<%[1]s> = InclusiveRange<%[1]s>(a, b)
var range = InclusiveRange<%[1]s>(a, b)
`, ty), options)

errs := RequireCheckerErrors(t, err, 1)
assert.IsType(t, &sema.InvalidTypeArgumentError{}, errs[0])
})

t.Run(fmt.Sprintf("InclusiveRange<%s>", ty), func(t *testing.T) {
t.Parallel()

_, err := ParseAndCheckWithOptions(t, fmt.Sprintf(`
let a: %[1]s = 0
let b: %[1]s = 10
var range: InclusiveRange<%[1]s> = InclusiveRange<%[1]s>(a, b)
`, ty), options)

// One for the invocation and another for the type.
errs := RequireCheckerErrors(t, err, 2)
assert.IsType(t, &sema.InvalidTypeArgumentError{}, errs[0])
assert.IsType(t, &sema.InvalidTypeArgumentError{}, errs[1])
})

t.Run(fmt.Sprintf("InclusiveRange<%s> assignment", ty), func(t *testing.T) {
t.Parallel()

Expand Down

0 comments on commit 7b65121

Please sign in to comment.