diff --git a/runtime/interpreter/interpreter_expression.go b/runtime/interpreter/interpreter_expression.go index b05de547a2..9477d0d2c8 100644 --- a/runtime/interpreter/interpreter_expression.go +++ b/runtime/interpreter/interpreter_expression.go @@ -553,39 +553,6 @@ func (interpreter *Interpreter) testComparison(left, right Value, expression *as } } -func (interpreter *Interpreter) createRange(left, right IntegerValue, expression *ast.BinaryExpression) *RangeValue { - locationRange := LocationRange{ - Location: interpreter.Location, - HasPosition: expression, - } - - leftComparable, leftOk := left.(ComparableValue) - rightComparable, rightOk := right.(ComparableValue) - - if !leftOk || !rightOk { - panic(errors.NewUnreachableError()) - } - - leftStaticType := left.StaticType(interpreter) - rightStaticType := right.StaticType(interpreter) - if leftStaticType != rightStaticType { - // Checker would only allow same type on both sides of the create range expression. - panic(errors.NewUnreachableError()) - } - - if leftComparable.Greater(interpreter, rightComparable, locationRange) { - panic(InvalidOperandsError{ - Operation: expression.Operation, - LeftType: leftStaticType, - RightType: rightStaticType, - LocationRange: locationRange, - }) - } - - rangeStaticType := RangeStaticType{ElementType: leftStaticType} - return NewRangeValue(interpreter, locationRange, left, right, rangeStaticType) -} - func (interpreter *Interpreter) VisitUnaryExpression(expression *ast.UnaryExpression) Value { value := interpreter.evalExpression(expression.Expression) diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index e5efa891b9..d942c0e71b 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -17817,6 +17817,7 @@ type RangeValue struct { semaType *sema.RangeType start IntegerValue endInclusive IntegerValue + step IntegerValue } func NewRangeValue( @@ -17825,10 +17826,38 @@ func NewRangeValue( start IntegerValue, endInclusive IntegerValue, rangeType RangeStaticType, +) *RangeValue { + startComparable, startOk := start.(ComparableValue) + endInclusiveComparable, endInclusiveOk := endInclusive.(ComparableValue) + if !startOk || !endInclusiveOk { + panic(errors.NewUnreachableError()) + } + + if startComparable.GreaterEqual(interpreter, endInclusiveComparable, locationRange) { + // stepValue should be 1 + } else { + // stepValue should be -1 + } + + return &RangeValue{ + start: start, + endInclusive: endInclusive, + Type: rangeType, + } +} + +func NewRangeValueWithStep( + interpreter *Interpreter, + locationRange LocationRange, + start IntegerValue, + endInclusive IntegerValue, + step IntegerValue, + rangeType RangeStaticType, ) *RangeValue { return &RangeValue{ start: start, endInclusive: endInclusive, + step: step, Type: rangeType, } } diff --git a/runtime/stdlib/builtin.go b/runtime/stdlib/builtin.go index 60e0111fc2..ed03050174 100644 --- a/runtime/stdlib/builtin.go +++ b/runtime/stdlib/builtin.go @@ -39,6 +39,7 @@ func DefaultStandardLibraryValues(handler StandardLibraryHandler) []StandardLibr PanicFunction, SignatureAlgorithmConstructor, RLPContract, + RangeConstructorFunction, NewLogFunction(handler), NewUnsafeRandomFunction(handler), NewGetBlockFunction(handler), diff --git a/runtime/stdlib/range.go b/runtime/stdlib/range.go index 7c4ea7d589..ba25358a60 100644 --- a/runtime/stdlib/range.go +++ b/runtime/stdlib/range.go @@ -81,7 +81,35 @@ var RangeConstructorFunction = NewStandardLibraryFunction( rangeConstructorFunctionType, rangeConstructorFunctionDocString, func(invocation interpreter.Invocation) interpreter.Value { - panic("TODO") + start, startOk := invocation.Arguments[0].(interpreter.IntegerValue) + endInclusive, endInclusiveOk := invocation.Arguments[1].(interpreter.IntegerValue) + + if !startOk || !endInclusiveOk { + panic(errors.NewUnreachableError()) + } + + inter := invocation.Interpreter + locationRange := invocation.LocationRange + + leftStaticType := start.StaticType(inter) + rightStaticType := endInclusive.StaticType(inter) + if leftStaticType != rightStaticType { + // Checker would only allow same type for both start & endInclusive. + panic(errors.NewUnreachableError()) + } + + rangeStaticType := interpreter.RangeStaticType{ElementType: leftStaticType} + + if len(invocation.Arguments) > 2 { + step, ok := invocation.Arguments[2].(interpreter.IntegerValue) + if !ok { + panic(errors.NewUnreachableError()) + } + + return interpreter.NewRangeValueWithStep(inter, locationRange, start, endInclusive, step, rangeStaticType) + } else { + return interpreter.NewRangeValue(inter, locationRange, start, endInclusive, rangeStaticType) + } }, ) diff --git a/runtime/tests/checker/range_value_test.go b/runtime/tests/checker/range_value_test.go index 7e1b0db60c..6d4acc300e 100644 --- a/runtime/tests/checker/range_value_test.go +++ b/runtime/tests/checker/range_value_test.go @@ -19,18 +19,77 @@ package checker import ( + "fmt" "testing" + "github.com/onflow/cadence/runtime/sema" + "github.com/onflow/cadence/runtime/stdlib" "github.com/stretchr/testify/require" ) -func TestRangeDeclaration(t *testing.T) { - +func TestRange(t *testing.T) { t.Parallel() - _, err := ParseAndCheck(t, ` - let a = 1 .. 10 - `) + baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) + baseValueActivation.DeclareValue(stdlib.RangeConstructorFunction) + + runValidCase := func(t *testing.T, memberType sema.Type, withStep bool) { + t.Run(memberType.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 r = Range(s, e, step: step) + `, + memberType.String(), memberType.String(), memberType.String()) + } else { + code = fmt.Sprintf( + ` + let s : %s = 10 + let e : %s = 20 + let r = Range(s, e) + `, + memberType.String(), memberType.String()) + } + + checker, err := ParseAndCheckWithOptions(t, code, + ParseAndCheckOptions{ + Config: &sema.Config{ + BaseValueActivation: baseValueActivation, + }, + }, + ) + + require.NoError(t, err) + resType := RequireGlobalValue(t, checker.Elaboration, "r") + require.Equal(t, + &sema.RangeType{ + 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) + } + + for _, integerType := range sema.AllIntegerTypes { + switch integerType { + case sema.IntegerType, sema.SignedIntegerType: + continue + } - require.NoError(t, err) + runValidCaseWithStep(t, integerType) + runValidCaseWithoutStep(t, integerType) + } } diff --git a/runtime/tests/interpreter/range_value_test.go b/runtime/tests/interpreter/range_value_test.go index b9d077ccbd..c89165579c 100644 --- a/runtime/tests/interpreter/range_value_test.go +++ b/runtime/tests/interpreter/range_value_test.go @@ -19,14 +19,78 @@ package interpreter_test import ( + "fmt" "testing" -) -func TestRangeDeclaration(t *testing.T) { + "github.com/onflow/cadence/runtime/activations" + "github.com/onflow/cadence/runtime/interpreter" + "github.com/onflow/cadence/runtime/sema" + "github.com/onflow/cadence/runtime/stdlib" + "github.com/stretchr/testify/require" +) +func TestRange(t *testing.T) { t.Parallel() - _ = parseCheckAndInterpret(t, ` - let a = 1 .. 10 - `) + baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) + baseValueActivation.DeclareValue(stdlib.RangeConstructorFunction) + + baseActivation := activations.NewActivation(nil, interpreter.BaseActivation) + interpreter.Declare(baseActivation, stdlib.RangeConstructorFunction) + + runValidCase := func(t *testing.T, memberType sema.Type, withStep bool) { + t.Run(memberType.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 r = Range(s, e, step: step) + `, + memberType.String(), memberType.String(), memberType.String()) + } else { + code = fmt.Sprintf( + ` + let s : %s = 10 + let e : %s = 20 + let r = Range(s, e) + `, + memberType.String(), memberType.String()) + } + + _, err := parseCheckAndInterpretWithOptions(t, code, + ParseCheckAndInterpretOptions{ + CheckerConfig: &sema.Config{ + BaseValueActivation: baseValueActivation, + }, + Config: &interpreter.Config{ + BaseActivation: baseActivation, + }, + }, + ) + + require.NoError(t, err) + }) + } + + runValidCaseWithoutStep := func(t *testing.T, memberType sema.Type) { + runValidCase(t, memberType, false) + } + runValidCaseWithStep := func(t *testing.T, memberType sema.Type) { + runValidCase(t, memberType, true) + } + + for _, integerType := range sema.AllIntegerTypes { + switch integerType { + case sema.IntegerType, sema.SignedIntegerType: + continue + } + + runValidCaseWithStep(t, integerType) + runValidCaseWithoutStep(t, integerType) + } }