diff --git a/runtime/interpreter/value_range.go b/runtime/interpreter/value_range.go index df14ed4886..41a8859adc 100644 --- a/runtime/interpreter/value_range.go +++ b/runtime/interpreter/value_range.go @@ -26,7 +26,7 @@ import ( "github.com/onflow/cadence/runtime/sema" ) -// NewInclusiveRangeValue constructs a InclusiveRange value with the provided start, end with default value of step. +// NewInclusiveRangeValue constructs an InclusiveRange value with the provided start, end with default value of step. func NewInclusiveRangeValue( interpreter *Interpreter, locationRange LocationRange, @@ -42,6 +42,8 @@ func NewInclusiveRangeValue( step := getValueForIntegerType(1, rangeType.ElementType) if startComparable.Greater(interpreter, endInclusiveComparable, locationRange) { + // TODO: Disallow unsigned integers to have a negative step. + negatedStep, ok := step.Negate(interpreter, locationRange).(IntegerValue) if !ok { panic(errors.NewUnreachableError()) @@ -53,7 +55,7 @@ func NewInclusiveRangeValue( return NewInclusiveRangeValueWithStep(interpreter, locationRange, start, end, step, rangeType) } -// NewInclusiveRangeValue constructs a InclusiveRange value with the provided start, end & step. +// NewInclusiveRangeValue constructs an InclusiveRange value with the provided start, end & step. func NewInclusiveRangeValueWithStep( interpreter *Interpreter, locationRange LocationRange, @@ -63,6 +65,8 @@ func NewInclusiveRangeValueWithStep( rangeType InclusiveRangeStaticType, ) *CompositeValue { + // TODO: Validate that if start > end, then the type is signed integer. + // Validate that the step is non-zero. if step.Equal(interpreter, locationRange, getValueForIntegerType(0, rangeType.ElementType)) { panic(InclusiveRangeConstructionError{ diff --git a/runtime/tests/checker/range_value_test.go b/runtime/tests/checker/range_value_test.go index b6af07f571..8adfd7135f 100644 --- a/runtime/tests/checker/range_value_test.go +++ b/runtime/tests/checker/range_value_test.go @@ -34,7 +34,7 @@ type inclusiveRangeConstructionTest struct { s, e, step int64 } -func TestInclusiveRangeConstruction(t *testing.T) { +func TestInclusiveRangeConstructionValid(t *testing.T) { t.Parallel() baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) @@ -275,3 +275,91 @@ func TestInclusiveRangeConstruction(t *testing.T) { runValidCase(t, testCase, false) } } + +func TestInclusiveRangeConstructionInvalid(t *testing.T) { + // t.Parallel() + + baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) + baseValueActivation.DeclareValue(stdlib.InclusiveRangeConstructorFunction) + + runInvalidCase := func(t *testing.T, label, code string, expectedErrorTypes []interface{}) { + t.Run(label, func(t *testing.T) { + // t.Parallel() + + _, err := ParseAndCheckWithOptions(t, code, + ParseAndCheckOptions{ + Config: &sema.Config{ + BaseValueActivation: baseValueActivation, + }, + }, + ) + + errs := RequireCheckerErrors(t, err, len(expectedErrorTypes)) + for i, err := range expectedErrorTypes { + assert.IsType(t, err, errs[i]) + } + }) + } + + for _, integerType := range sema.AllIntegerTypes { + // Only test leaf types + switch integerType { + case sema.IntegerType, sema.SignedIntegerType: + continue + } + + typeString := integerType.String() + + // Wrong type of arguments + + // Any integer type name other than the integerType is sufficient. + // There is nothing special about the Int128 and Int256 types here. + differentTypeString := sema.Int128TypeName + if typeString == differentTypeString { + differentTypeString = sema.Int256TypeName + } + + runInvalidCase( + t, + typeString, + fmt.Sprintf("let r = InclusiveRange(%s(1), %s(2))", typeString, differentTypeString), + []interface{}{&sema.TypeParameterTypeMismatchError{}, &sema.TypeMismatchError{}}, + ) + runInvalidCase( + t, + typeString, + fmt.Sprintf("let r = InclusiveRange(%s(1), %s(10), step: %s(2))", typeString, typeString, differentTypeString), + []interface{}{&sema.TypeParameterTypeMismatchError{}, &sema.TypeMismatchError{}}, + ) + + // Not enough arguments + runInvalidCase( + t, + typeString, + fmt.Sprintf("let r = InclusiveRange(%s(1))", typeString), + []interface{}{&sema.ArgumentCountError{}}, + ) + + // Label for step not provided + runInvalidCase( + t, + typeString, + fmt.Sprintf("let r = InclusiveRange(%s(1), %s(0), %s(10))", typeString, typeString, typeString), + []interface{}{&sema.MissingArgumentLabelError{}}, + ) + + // Label for start and end provided + runInvalidCase( + t, + typeString, + fmt.Sprintf("let r = InclusiveRange(start: %s(1), %s(0))", typeString, typeString), + []interface{}{&sema.IncorrectArgumentLabelError{}}, + ) + runInvalidCase( + t, + typeString, + fmt.Sprintf("let r = InclusiveRange(%s(1), end: %s(0))", typeString, typeString), + []interface{}{&sema.IncorrectArgumentLabelError{}}, + ) + } +}