diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index e631dc8477..f3fefcc562 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -15192,7 +15192,7 @@ func NewUnmeteredCompositeField(name string, value Value) CompositeField { // Create a CompositeValue with the provided StaticType. // Useful when we wish to utilize CompositeValue as the value // for a type which isn't CompositeType. -// For e.g. RangeType +// For e.g. InclusiveRangeType func NewCompositeValueWithStaticType( interpreter *Interpreter, locationRange LocationRange, diff --git a/runtime/interpreter/value_range.go b/runtime/interpreter/value_range.go index 4b65cc9ca7..df14ed4886 100644 --- a/runtime/interpreter/value_range.go +++ b/runtime/interpreter/value_range.go @@ -90,7 +90,7 @@ func NewInclusiveRangeValueWithStep( Value: start, }, { - Name: sema.InclusiveRangeTypeEndInclusiveFieldName, + Name: sema.InclusiveRangeTypeEndFieldName, Value: end, }, { @@ -112,19 +112,6 @@ func NewInclusiveRangeValueWithStep( rangeType, ) - rangeValue.ComputedFields = map[string]ComputedField{ - sema.InclusiveRangeTypeCountFieldName: func(interpreter *Interpreter, locationRange LocationRange) Value { - start := getFieldAsIntegerValue(rangeValue, interpreter, locationRange, sema.InclusiveRangeTypeStartFieldName) - endInclusive := getFieldAsIntegerValue(rangeValue, interpreter, locationRange, sema.InclusiveRangeTypeEndInclusiveFieldName) - step := getFieldAsIntegerValue(rangeValue, interpreter, locationRange, sema.InclusiveRangeTypeStepFieldName) - - diff := convertAndAssertIntegerValue(endInclusive.Minus(interpreter, start, locationRange)) - - // Perform integer division & drop the decimal part. - // Note that step is guaranteed to be non-zero. - return diff.Div(interpreter, step, locationRange) - }, - } rangeValue.Functions = map[string]FunctionValue{ sema.InclusiveRangeTypeContainsFunctionName: NewHostFunctionValue( interpreter, @@ -158,7 +145,7 @@ func rangeContains( needleValue Value, ) BoolValue { start := getFieldAsIntegerValue(rangeValue, interpreter, locationRange, sema.InclusiveRangeTypeStartFieldName) - endInclusive := getFieldAsIntegerValue(rangeValue, interpreter, locationRange, sema.InclusiveRangeTypeEndInclusiveFieldName) + endInclusive := getFieldAsIntegerValue(rangeValue, interpreter, locationRange, sema.InclusiveRangeTypeEndFieldName) step := getFieldAsIntegerValue(rangeValue, interpreter, locationRange, sema.InclusiveRangeTypeStepFieldName) needleInteger := convertAndAssertIntegerValue(needleValue) diff --git a/runtime/sema/type.go b/runtime/sema/type.go index 069f8c002e..c4cf834680 100644 --- a/runtime/sema/type.go +++ b/runtime/sema/type.go @@ -5089,12 +5089,17 @@ func (r *InclusiveRangeType) RewriteWithRestrictedTypes() (Type, bool) { } const InclusiveRangeTypeStartFieldName = "start" -const InclusiveRangeTypeEndInclusiveFieldName = "endInclusive" -const InclusiveRangeTypeStepFieldName = "step" -const InclusiveRangeTypeCountFieldName = "count" +const inclusiveRangeTypeStartFieldDocString = ` +The start of the InclusiveRange sequence +` +const InclusiveRangeTypeEndFieldName = "end" +const inclusiveRangeTypeEndFieldDocString = ` +The end of the InclusiveRange sequence +` -const inclusiveRangeTypeCountFieldDocString = ` -The number of entries in the Range sequence +const InclusiveRangeTypeStepFieldName = "step" +const inclusiveRangeTypeStepFieldDocString = ` +The step size of the InclusiveRange sequence ` const InclusiveRangeTypeContainsFunctionName = "contains" @@ -5126,7 +5131,31 @@ func InclusiveRangeContainsFunctionType(elementType Type) *FunctionType { func (r *InclusiveRangeType) initializeMemberResolvers() { r.memberResolversOnce.Do(func() { r.memberResolvers = withBuiltinMembers(r, map[string]MemberResolver{ - InclusiveRangeTypeCountFieldName: { + InclusiveRangeTypeStartFieldName: { + Kind: common.DeclarationKindField, + Resolve: func(memoryGauge common.MemoryGauge, identifier string, _ ast.Range, _ func(error)) *Member { + return NewPublicConstantFieldMember( + memoryGauge, + r, + identifier, + r.ElementType(false), + inclusiveRangeTypeStartFieldDocString, + ) + }, + }, + InclusiveRangeTypeEndFieldName: { + Kind: common.DeclarationKindField, + Resolve: func(memoryGauge common.MemoryGauge, identifier string, _ ast.Range, _ func(error)) *Member { + return NewPublicConstantFieldMember( + memoryGauge, + r, + identifier, + r.ElementType(false), + inclusiveRangeTypeEndFieldDocString, + ) + }, + }, + InclusiveRangeTypeStepFieldName: { Kind: common.DeclarationKindField, Resolve: func(memoryGauge common.MemoryGauge, identifier string, _ ast.Range, _ func(error)) *Member { return NewPublicConstantFieldMember( @@ -5134,7 +5163,7 @@ func (r *InclusiveRangeType) initializeMemberResolvers() { r, identifier, r.ElementType(false), - inclusiveRangeTypeCountFieldDocString, + inclusiveRangeTypeStepFieldDocString, ) }, }, diff --git a/runtime/tests/checker/range_value_test.go b/runtime/tests/checker/range_value_test.go index 863be95685..b6af07f571 100644 --- a/runtime/tests/checker/range_value_test.go +++ b/runtime/tests/checker/range_value_test.go @@ -22,40 +22,226 @@ import ( "fmt" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/onflow/cadence/runtime/sema" "github.com/onflow/cadence/runtime/stdlib" ) -func TestInclusiveRange(t *testing.T) { +type inclusiveRangeConstructionTest struct { + ty sema.Type + s, e, step int64 +} + +func TestInclusiveRangeConstruction(t *testing.T) { t.Parallel() baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) baseValueActivation.DeclareValue(stdlib.InclusiveRangeConstructorFunction) - runValidCase := func(t *testing.T, memberType sema.Type, withStep bool) { - t.Run(memberType.String(), func(t *testing.T) { + validTestCases := []inclusiveRangeConstructionTest{ + // Int* + { + ty: sema.IntType, + s: 0, + e: 10, + step: 2, + }, + { + ty: sema.IntType, + s: 10, + e: -10, + step: -2, + }, + { + ty: sema.Int8Type, + s: 0, + e: 10, + step: 2, + }, + { + ty: sema.Int8Type, + s: 10, + e: -10, + step: -2, + }, + { + ty: sema.Int16Type, + s: 0, + e: 10, + step: 2, + }, + { + ty: sema.Int16Type, + s: 10, + e: -10, + step: -2, + }, + { + ty: sema.Int32Type, + s: 0, + e: 10, + step: 2, + }, + { + ty: sema.Int32Type, + s: 10, + e: -10, + step: -2, + }, + { + ty: sema.Int64Type, + s: 0, + e: 10, + step: 2, + }, + { + ty: sema.Int64Type, + s: 10, + e: -10, + step: -2, + }, + { + ty: sema.Int128Type, + s: 0, + e: 10, + step: 2, + }, + { + ty: sema.Int128Type, + s: 10, + e: -10, + step: -2, + }, + { + ty: sema.Int256Type, + s: 0, + e: 10, + step: 2, + }, + { + ty: sema.Int256Type, + s: 10, + e: -10, + step: -2, + }, + + // UInt* + { + ty: sema.UIntType, + s: 0, + e: 10, + step: 2, + }, + { + ty: sema.UInt8Type, + s: 0, + e: 10, + step: 2, + }, + { + ty: sema.UInt16Type, + s: 0, + e: 10, + step: 2, + }, + { + ty: sema.UInt32Type, + s: 0, + e: 10, + step: 2, + }, + { + ty: sema.UInt64Type, + s: 0, + e: 10, + step: 2, + }, + { + ty: sema.UInt128Type, + s: 0, + e: 10, + step: 2, + }, + { + ty: sema.UInt256Type, + s: 0, + e: 10, + step: 2, + }, + + // Word* + { + ty: sema.Word8Type, + s: 0, + e: 10, + step: 2, + }, + { + ty: sema.Word16Type, + s: 0, + e: 10, + step: 2, + }, + { + ty: sema.Word32Type, + s: 0, + e: 10, + step: 2, + }, + { + ty: sema.Word64Type, + s: 0, + e: 10, + step: 2, + }, + { + ty: sema.Word128Type, + s: 0, + e: 10, + step: 2, + }, + { + ty: sema.Word256Type, + s: 0, + e: 10, + step: 2, + }, + } + + runValidCase := func(t *testing.T, testCase inclusiveRangeConstructionTest, withStep bool) { + t.Run(testCase.ty.String(), func(t *testing.T) { t.Parallel() var code string if withStep { code = fmt.Sprintf( ` - let s : %s = 10 - let e : %s = 20 - let step : %s = 2 + let s : %s = %d + let e : %s = %d + let step : %s = %d let r = InclusiveRange(s, e, step: step) + + let rs = r.start + let re = r.end + let rstep = r.step + let contains_res = r.contains(s) `, - memberType.String(), memberType.String(), memberType.String()) + testCase.ty.String(), testCase.s, testCase.ty.String(), testCase.e, testCase.ty.String(), testCase.step) } else { code = fmt.Sprintf( ` - let s : %s = 10 - let e : %s = 20 + let s : %s = %d + let e : %s = %d let r = InclusiveRange(s, e) + + let rs = r.start + let re = r.end + let rstep = r.step + let contains_res = r.contains(s) `, - memberType.String(), memberType.String()) + testCase.ty.String(), testCase.s, testCase.ty.String(), testCase.e) } checker, err := ParseAndCheckWithOptions(t, code, @@ -67,30 +253,25 @@ func TestInclusiveRange(t *testing.T) { ) require.NoError(t, err) - resType := RequireGlobalValue(t, checker.Elaboration, "r") - require.Equal(t, - &sema.InclusiveRangeType{ - MemberType: memberType, - }, - resType, - ) - }) - } - runValidCaseWithoutStep := func(t *testing.T, memberType sema.Type) { - runValidCase(t, memberType, false) - } - runValidCaseWithStep := func(t *testing.T, memberType sema.Type) { - runValidCase(t, memberType, true) - } + checkType := func(t *testing.T, name string, expectedType sema.Type) { + resType := RequireGlobalValue(t, checker.Elaboration, name) + assert.IsType(t, expectedType, resType) + } - for _, integerType := range sema.AllIntegerTypes { - switch integerType { - case sema.IntegerType, sema.SignedIntegerType: - continue - } + checkType(t, "r", &sema.InclusiveRangeType{ + MemberType: testCase.ty, + }) + checkType(t, "rs", testCase.ty) + checkType(t, "re", testCase.ty) + checkType(t, "rstep", testCase.ty) + checkType(t, "contains_res", sema.BoolType) + }) + } - runValidCaseWithStep(t, integerType) - runValidCaseWithoutStep(t, integerType) + // Run each test case with and without step. + for _, testCase := range validTestCases { + runValidCase(t, testCase, true) + runValidCase(t, testCase, false) } }