Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Disallow InclusiveRange<T> if T is a non-leaf integer #2959

Merged
16 changes: 11 additions & 5 deletions runtime/program_params_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/onflow/cadence/encoding/json"
"github.com/onflow/cadence/runtime/common"
"github.com/onflow/cadence/runtime/sema"
"github.com/onflow/cadence/runtime/tests/checker"
. "github.com/onflow/cadence/runtime/tests/utils"
)

Expand Down Expand Up @@ -323,7 +324,6 @@ func TestRuntimeScriptParameterTypeValidation(t *testing.T) {
assert.NoError(t, err)
})

// Since InclusiveRange isn't covariant.
t.Run("Invalid InclusiveRange<Integer>", func(t *testing.T) {
t.Parallel()

Expand All @@ -338,8 +338,11 @@ func TestRuntimeScriptParameterTypeValidation(t *testing.T) {
cadence.NewInclusiveRange(cadence.NewInt16(1), cadence.NewInt16(2), cadence.NewInt16(1)),
)

var entryPointErr *InvalidEntryPointArgumentError
require.ErrorAs(t, err, &entryPointErr)
var checkerError *sema.CheckerError
require.ErrorAs(t, err, &checkerError)

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

t.Run("Invalid InclusiveRange<Int16> with mixed value types", func(t *testing.T) {
Expand Down Expand Up @@ -374,8 +377,11 @@ func TestRuntimeScriptParameterTypeValidation(t *testing.T) {
cadence.NewInclusiveRange(cadence.NewInt16(1), cadence.NewUInt(2), cadence.NewUInt(1)),
)

var entryPointErr *InvalidEntryPointArgumentError
require.ErrorAs(t, err, &entryPointErr)
var checkerError *sema.CheckerError
require.ErrorAs(t, err, &checkerError)

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

t.Run("Capability", func(t *testing.T) {
Expand Down
11 changes: 10 additions & 1 deletion runtime/sema/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -2244,7 +2244,16 @@ func (checker *Checker) convertInstantiationType(t *ast.InstantiationType) Type
return ty
}

return parameterizedType.Instantiate(typeArguments, checker.report)
astRange := ast.NewRange(
checker.memoryGauge,
t.TypeArgumentsStartPos,
t.EndPos,
)
return parameterizedType.Instantiate(
typeArguments,
checker.report,
&astRange,
)
}

func (checker *Checker) VisitExpression(expr ast.Expression, expectedType Type) Type {
Expand Down
23 changes: 23 additions & 0 deletions runtime/sema/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -3735,6 +3735,29 @@ func (e *MissingTypeArgumentError) Error() string {
return fmt.Sprintf("non-optional type argument %s missing", e.TypeArgumentName)
}

// InvalidTypeArgumentError

type InvalidTypeArgumentError struct {
TypeArgumentName string
Details string
ast.Range
}

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

func (e *InvalidTypeArgumentError) isSemanticError() {}
darkdrag00nv2 marked this conversation as resolved.
Show resolved Hide resolved

func (*InvalidTypeArgumentError) IsUserError() {}

func (e *InvalidTypeArgumentError) Error() string {
return fmt.Sprintf("type argument %s invalid", e.TypeArgumentName)
}

func (e *InvalidTypeArgumentError) SecondaryError() string {
return e.Details
}

// TypeParameterTypeInferenceError

type TypeParameterTypeInferenceError struct {
Expand Down
37 changes: 30 additions & 7 deletions runtime/sema/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@
type ParameterizedType interface {
Type
TypeParameters() []*TypeParameter
Instantiate(typeArguments []Type, report func(err error)) Type
Instantiate(typeArguments []Type, report func(err error), astRange *ast.Range) Type
turbolent marked this conversation as resolved.
Show resolved Hide resolved
BaseType() Type
TypeArguments() []Type
}
Expand All @@ -288,6 +288,7 @@
func(err error) {
panic(errors.NewUnexpectedErrorFromCause(err))
},
nil,
)
}

Expand Down Expand Up @@ -3497,13 +3498,15 @@
Word256Type,
}

var AllNonLeafIntegerTypes = []Type{
IntegerType,
SignedIntegerType,
}

var AllIntegerTypes = common.Concat(
AllUnsignedIntegerTypes,
AllSignedIntegerTypes,
[]Type{
IntegerType,
SignedIntegerType,
},
AllNonLeafIntegerTypes,
)

var AllNumberTypes = common.Concat(
Expand Down Expand Up @@ -5399,8 +5402,28 @@
return &InclusiveRangeType{}
}

func (t *InclusiveRangeType) Instantiate(typeArguments []Type, report func(err error)) Type {
func (t *InclusiveRangeType) Instantiate(
typeArguments []Type,
report func(err error),
astRange *ast.Range,
) Type {
memberType := typeArguments[0]

if astRange == nil {
panic(errors.NewUnreachableError())

Check warning on line 5413 in runtime/sema/type.go

View check run for this annotation

Codecov / codecov/patch

runtime/sema/type.go#L5413

Added line #L5413 was not covered by tests
}

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

return &InclusiveRangeType{
MemberType: memberType,
}
Expand Down Expand Up @@ -7258,7 +7281,7 @@
}
}

func (t *CapabilityType) Instantiate(typeArguments []Type, _ func(err error)) Type {
func (t *CapabilityType) Instantiate(typeArguments []Type, _ func(err error), _ *ast.Range) Type {
borrowType := typeArguments[0]
return &CapabilityType{
BorrowType: borrowType,
Expand Down
6 changes: 6 additions & 0 deletions runtime/tests/checker/for_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@ func TestCheckForInclusiveRange(t *testing.T) {
baseValueActivation.DeclareValue(stdlib.InclusiveRangeConstructorFunction)

for _, typ := range sema.AllIntegerTypes {
// Only test leaf integer types
switch typ {
case sema.IntegerType, sema.SignedIntegerType:
continue
}

code := fmt.Sprintf(`
fun test() {
let start : %[1]s = 1
Expand Down
45 changes: 45 additions & 0 deletions runtime/tests/checker/range_value_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -377,3 +377,48 @@ func TestCheckInclusiveRangeConstructionInvalid(t *testing.T) {
[]error{&sema.MissingTypeArgumentError{}},
)
}

func TestInclusiveRangeNonLeafIntegerTypes(t *testing.T) {

t.Parallel()

baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation)
baseValueActivation.DeclareValue(stdlib.InclusiveRangeConstructorFunction)

options := ParseAndCheckOptions{
Config: &sema.Config{
BaseValueActivation: baseValueActivation,
},
}

test := func(t *testing.T, ty sema.Type) {
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)
darkdrag00nv2 marked this conversation as resolved.
Show resolved Hide resolved
`, ty), options)

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

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

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

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

for _, ty := range sema.AllNonLeafIntegerTypes {
test(t, ty)
}
}
66 changes: 2 additions & 64 deletions runtime/tests/interpreter/dynamic_casting_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1471,15 +1471,6 @@ func TestInterpretDynamicCastingInclusiveRange(t *testing.T) {

t.Parallel()

types := []sema.Type{
&sema.InclusiveRangeType{
MemberType: sema.IntType,
},
&sema.InclusiveRangeType{
MemberType: sema.IntegerType,
},
}

baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation)
baseValueActivation.DeclareValue(stdlib.InclusiveRangeConstructorFunction)

Expand All @@ -1498,66 +1489,13 @@ func TestInterpretDynamicCastingInclusiveRange(t *testing.T) {
for operation, returnsOptional := range dynamicCastingOperations {

t.Run(operation.Symbol(), func(t *testing.T) {

for _, fromType := range types {
for _, targetType := range types {

t.Run(fmt.Sprintf("valid: from %s to %s", fromType, targetType), func(t *testing.T) {

inter, err := parseCheckAndInterpretWithOptions(t,
fmt.Sprintf(
`
let x: InclusiveRange<Int> = InclusiveRange(10, 20)
let y: %[1]s = x
let z: %[2]s? = y %[3]s %[2]s
`,
fromType,
targetType,
operation.Symbol(),
),
options,
)
require.NoError(t, err)

expectedInclusiveRange := interpreter.NewInclusiveRangeValue(
inter,
interpreter.EmptyLocationRange,
interpreter.NewUnmeteredIntValueFromInt64(10),
interpreter.NewUnmeteredIntValueFromInt64(20),
interpreter.InclusiveRangeStaticType{
ElementType: interpreter.PrimitiveStaticTypeInt,
},
sema.NewInclusiveRangeType(nil, sema.IntType),
)

AssertValuesEqual(
t,
inter,
expectedInclusiveRange,
inter.Globals.Get("y").GetValue(),
)

AssertValuesEqual(
t,
inter,
interpreter.NewUnmeteredSomeValueNonCopying(
expectedInclusiveRange,
),
inter.Globals.Get("z").GetValue(),
)
})
}

// We cannot test for invalid casts for T since InclusiveRange<T> has a type bound Integer on T.
}

t.Run("invalid upcast", func(t *testing.T) {
t.Run("invalid cast", func(t *testing.T) {

inter, err := parseCheckAndInterpretWithOptions(t,
fmt.Sprintf(
`
fun test(): InclusiveRange<UInt256>? {
let x: InclusiveRange<Integer> = InclusiveRange(10, 20)
let x: InclusiveRange<Int> = InclusiveRange(10, 20)
return x %s InclusiveRange<UInt256>
}
`,
Expand Down
Loading