From aece6f240d99dc6884e32028459e67ace0e33064 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Fri, 17 Apr 2026 17:08:35 -0700 Subject: [PATCH 1/2] add RoundingMode enum and optional rounding parameter to Fix64/UFix64 conversions --- bbq/vm/builtin_globals.go | 10 +- bbq/vm/value_function.go | 7 + interpreter/fixedpoint_test.go | 544 ++++++++++++++++++++++ interpreter/interpreter.go | 46 +- interpreter/value_fix64.go | 36 ++ interpreter/value_fixedpoint.go | 60 +++ interpreter/value_ufix64.go | 38 ++ runtime/account_test.go | 1 + runtime/convertValues.go | 56 +++ runtime/crypto_test.go | 8 +- runtime/program_params_validation_test.go | 14 + runtime/rounding_mode_test.go | 242 ++++++++++ sema/crypto_test.go | 4 +- sema/rounding_mode_test.go | 230 +++++++++ sema/rounding_mode_type.go | 143 ++++++ sema/rounding_mode_type_test.go | 56 +++ sema/type.go | 34 +- stdlib/builtin.go | 3 + stdlib/crypto.go | 63 --- stdlib/enum.go | 85 ++++ stdlib/hashalgorithm.go | 4 +- stdlib/roundingmode.go | 112 +++++ stdlib/signaturealgorithm.go | 4 +- test_utils/test_utils.go | 50 +- 24 files changed, 1768 insertions(+), 82 deletions(-) create mode 100644 interpreter/value_fixedpoint.go create mode 100644 runtime/rounding_mode_test.go create mode 100644 sema/rounding_mode_test.go create mode 100644 sema/rounding_mode_type.go create mode 100644 sema/rounding_mode_type_test.go create mode 100644 stdlib/enum.go create mode 100644 stdlib/roundingmode.go diff --git a/bbq/vm/builtin_globals.go b/bbq/vm/builtin_globals.go index 81e2905bd8..dde59dfd79 100644 --- a/bbq/vm/builtin_globals.go +++ b/bbq/vm/builtin_globals.go @@ -228,13 +228,21 @@ func init() { for _, declaration := range interpreter.ConverterDeclarations { // NOTE: declare in loop, as captured in closure below convert := declaration.Convert + convertWithRounding := declaration.ConvertWithRounding functionType := sema.BaseValueActivation.Find(declaration.Name).Type.(*sema.FunctionType) + var nativeFn interpreter.NativeFunction + if convertWithRounding != nil { + nativeFn = interpreter.NativeConverterFunctionWithRounding(convert, convertWithRounding) + } else { + nativeFn = interpreter.NativeConverterFunction(convert) + } + function := NewNativeFunctionValue( declaration.Name, functionType, - interpreter.NativeConverterFunction(convert), + nativeFn, ) registerBuiltinFunction(function) diff --git a/bbq/vm/value_function.go b/bbq/vm/value_function.go index 1cdc37738b..1270705b15 100644 --- a/bbq/vm/value_function.go +++ b/bbq/vm/value_function.go @@ -315,6 +315,13 @@ func (v *NativeFunctionValue) GetMember( ) } +func (v *NativeFunctionValue) SetField(name string, value interpreter.Value) { + if v.fields == nil { + v.fields = make(map[string]interpreter.Value) + } + v.fields[name] = value +} + func (*NativeFunctionValue) RemoveMember(_ interpreter.ValueTransferContext, _ string) interpreter.Value { panic(errors.NewUnreachableError()) } diff --git a/interpreter/fixedpoint_test.go b/interpreter/fixedpoint_test.go index 1534cff978..43de33ed64 100644 --- a/interpreter/fixedpoint_test.go +++ b/interpreter/fixedpoint_test.go @@ -28,10 +28,13 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/onflow/cadence/activations" "github.com/onflow/cadence/ast" + "github.com/onflow/cadence/common" "github.com/onflow/cadence/fixedpoint" "github.com/onflow/cadence/interpreter" "github.com/onflow/cadence/sema" + "github.com/onflow/cadence/stdlib" . "github.com/onflow/cadence/test_utils/common_utils" . "github.com/onflow/cadence/test_utils/interpreter_utils" . "github.com/onflow/cadence/test_utils/sema_utils" @@ -1350,3 +1353,544 @@ func TestInterpretFixedPointLeastSignificantDecimalHandling(t *testing.T) { } }) } + +func parseCheckAndPrepareWithRoundingMode(t *testing.T, code string) Invokable { + t.Helper() + + baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) + baseActivation := activations.NewActivation(nil, interpreter.BaseActivation) + + valueDeclaration := stdlib.InterpreterRoundingModeConstructor + baseValueActivation.DeclareValue(valueDeclaration) + interpreter.Declare(baseActivation, valueDeclaration) + + invokable, err := parseCheckAndPrepareWithOptions( + t, + code, + ParseCheckAndInterpretOptions{ + ParseAndCheckOptions: &ParseAndCheckOptions{ + CheckerConfig: &sema.Config{ + BaseValueActivationHandler: func(_ common.Location) *sema.VariableActivation { + return baseValueActivation + }, + }, + }, + InterpreterConfig: &interpreter.Config{ + BaseActivationHandler: func(_ common.Location) *interpreter.VariableActivation { + return baseActivation + }, + }, + }, + ) + require.NoError(t, err) + + return invokable +} + +func TestInterpretFix64WithRoundingMode(t *testing.T) { + t.Parallel() + + // Fix128 has 24 decimal places, Fix64 has 8. + // Rounding applies to the 9th+ decimal places when converting Fix128 → Fix64. + // + // Test values: + // 1.000000003... (9th digit < 5): non-halfway, fractional part below midpoint + // 1.000000005... (9th digit = 5): exact halfway between 1.00000000 and 1.00000001 + // 1.000000007... (9th digit > 5): non-halfway, fractional part above midpoint + // + // Expected results per rounding mode: + // towardZero: always truncate → 1.00000000 (positive), -1.00000000 (negative) + // awayFromZero: always round up magnitude → 1.00000001 (positive), -1.00000001 (negative) + // nearestHalfAway: < 5 → 1.00000000, = 5 → 1.00000001 (away), > 5 → 1.00000001 + // nearestHalfEven: < 5 → 1.00000000, = 5 → 1.00000000 (even), > 5 → 1.00000001 + + type testCase struct { + name string + code string + expected interpreter.Fix64Value + } + + tests := []testCase{ + // towardZero: truncates fractional part beyond 8 decimals + { + name: "towardZero, positive, non-halfway below", + code: `fun main(): Fix64 { + let x: Fix128 = 1.000000003000000000000000 + return Fix64(x, rounding: RoundingMode.towardZero) + }`, + expected: interpreter.NewUnmeteredFix64Value(100000000), // 1.00000000 + }, + { + name: "towardZero, positive, exact halfway", + code: `fun main(): Fix64 { + let x: Fix128 = 1.000000005000000000000000 + return Fix64(x, rounding: RoundingMode.towardZero) + }`, + expected: interpreter.NewUnmeteredFix64Value(100000000), // 1.00000000 + }, + { + name: "towardZero, positive, non-halfway above", + code: `fun main(): Fix64 { + let x: Fix128 = 1.000000007000000000000000 + return Fix64(x, rounding: RoundingMode.towardZero) + }`, + expected: interpreter.NewUnmeteredFix64Value(100000000), // 1.00000000 + }, + { + name: "towardZero, negative, non-halfway below", + code: `fun main(): Fix64 { + let x: Fix128 = -1.000000003000000000000000 + return Fix64(x, rounding: RoundingMode.towardZero) + }`, + expected: interpreter.NewUnmeteredFix64Value(-100000000), // -1.00000000 + }, + { + name: "towardZero, negative, exact halfway", + code: `fun main(): Fix64 { + let x: Fix128 = -1.000000005000000000000000 + return Fix64(x, rounding: RoundingMode.towardZero) + }`, + expected: interpreter.NewUnmeteredFix64Value(-100000000), // -1.00000000 + }, + { + name: "towardZero, negative, non-halfway above", + code: `fun main(): Fix64 { + let x: Fix128 = -1.000000007000000000000000 + return Fix64(x, rounding: RoundingMode.towardZero) + }`, + expected: interpreter.NewUnmeteredFix64Value(-100000000), // -1.00000000 + }, + + // awayFromZero: rounds up magnitude for any nonzero fractional part + { + name: "awayFromZero, positive, non-halfway below", + code: `fun main(): Fix64 { + let x: Fix128 = 1.000000003000000000000000 + return Fix64(x, rounding: RoundingMode.awayFromZero) + }`, + expected: interpreter.NewUnmeteredFix64Value(100000001), // 1.00000001 + }, + { + name: "awayFromZero, positive, exact halfway", + code: `fun main(): Fix64 { + let x: Fix128 = 1.000000005000000000000000 + return Fix64(x, rounding: RoundingMode.awayFromZero) + }`, + expected: interpreter.NewUnmeteredFix64Value(100000001), // 1.00000001 + }, + { + name: "awayFromZero, positive, non-halfway above", + code: `fun main(): Fix64 { + let x: Fix128 = 1.000000007000000000000000 + return Fix64(x, rounding: RoundingMode.awayFromZero) + }`, + expected: interpreter.NewUnmeteredFix64Value(100000001), // 1.00000001 + }, + { + name: "awayFromZero, negative, non-halfway below", + code: `fun main(): Fix64 { + let x: Fix128 = -1.000000003000000000000000 + return Fix64(x, rounding: RoundingMode.awayFromZero) + }`, + expected: interpreter.NewUnmeteredFix64Value(-100000001), // -1.00000001 + }, + { + name: "awayFromZero, negative, exact halfway", + code: `fun main(): Fix64 { + let x: Fix128 = -1.000000005000000000000000 + return Fix64(x, rounding: RoundingMode.awayFromZero) + }`, + expected: interpreter.NewUnmeteredFix64Value(-100000001), // -1.00000001 + }, + { + name: "awayFromZero, negative, non-halfway above", + code: `fun main(): Fix64 { + let x: Fix128 = -1.000000007000000000000000 + return Fix64(x, rounding: RoundingMode.awayFromZero) + }`, + expected: interpreter.NewUnmeteredFix64Value(-100000001), // -1.00000001 + }, + + // nearestHalfAway: nearest, tie breaks away from zero + { + name: "nearestHalfAway, positive, non-halfway below", + code: `fun main(): Fix64 { + let x: Fix128 = 1.000000003000000000000000 + return Fix64(x, rounding: RoundingMode.nearestHalfAway) + }`, + expected: interpreter.NewUnmeteredFix64Value(100000000), // 1.00000000 + }, + { + name: "nearestHalfAway, positive, exact halfway", + code: `fun main(): Fix64 { + let x: Fix128 = 1.000000005000000000000000 + return Fix64(x, rounding: RoundingMode.nearestHalfAway) + }`, + expected: interpreter.NewUnmeteredFix64Value(100000001), // 1.00000001 (tie → away) + }, + { + name: "nearestHalfAway, positive, non-halfway above", + code: `fun main(): Fix64 { + let x: Fix128 = 1.000000007000000000000000 + return Fix64(x, rounding: RoundingMode.nearestHalfAway) + }`, + expected: interpreter.NewUnmeteredFix64Value(100000001), // 1.00000001 + }, + { + name: "nearestHalfAway, negative, exact halfway", + code: `fun main(): Fix64 { + let x: Fix128 = -1.000000005000000000000000 + return Fix64(x, rounding: RoundingMode.nearestHalfAway) + }`, + expected: interpreter.NewUnmeteredFix64Value(-100000001), // -1.00000001 (tie → away) + }, + + // nearestHalfEven: nearest, tie breaks to even + { + name: "nearestHalfEven, positive, non-halfway below", + code: `fun main(): Fix64 { + let x: Fix128 = 1.000000003000000000000000 + return Fix64(x, rounding: RoundingMode.nearestHalfEven) + }`, + expected: interpreter.NewUnmeteredFix64Value(100000000), // 1.00000000 + }, + { + name: "nearestHalfEven, positive, exact halfway (last digit 0 is even)", + code: `fun main(): Fix64 { + let x: Fix128 = 1.000000005000000000000000 + return Fix64(x, rounding: RoundingMode.nearestHalfEven) + }`, + expected: interpreter.NewUnmeteredFix64Value(100000000), // 1.00000000 (tie → even, 0 is even) + }, + { + name: "nearestHalfEven, positive, non-halfway above", + code: `fun main(): Fix64 { + let x: Fix128 = 1.000000007000000000000000 + return Fix64(x, rounding: RoundingMode.nearestHalfEven) + }`, + expected: interpreter.NewUnmeteredFix64Value(100000001), // 1.00000001 + }, + { + name: "nearestHalfEven, positive, exact halfway (last digit 1 is odd)", + code: `fun main(): Fix64 { + let x: Fix128 = 1.000000015000000000000000 + return Fix64(x, rounding: RoundingMode.nearestHalfEven) + }`, + expected: interpreter.NewUnmeteredFix64Value(100000002), // 1.00000002 (tie → even, 2 is even) + }, + { + name: "nearestHalfEven, negative, exact halfway", + code: `fun main(): Fix64 { + let x: Fix128 = -1.000000005000000000000000 + return Fix64(x, rounding: RoundingMode.nearestHalfEven) + }`, + expected: interpreter.NewUnmeteredFix64Value(-100000000), // -1.00000000 (tie → even) + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + invokable := parseCheckAndPrepareWithRoundingMode(t, tc.code) + result, err := invokable.Invoke("main") + require.NoError(t, err) + assert.Equal(t, tc.expected, result) + }) + } + + t.Run("backward compat, no rounding truncates", func(t *testing.T) { + t.Parallel() + + invokable := parseCheckAndPrepare(t, ` + fun main(): Fix64 { + let x: Fix128 = 1.000000005000000000000000 + return Fix64(x) + } + `) + + result, err := invokable.Invoke("main") + require.NoError(t, err) + assert.Equal(t, interpreter.NewUnmeteredFix64Value(100000000), result) + }) + + t.Run("integer conversion ignores rounding", func(t *testing.T) { + t.Parallel() + + invokable := parseCheckAndPrepareWithRoundingMode(t, ` + fun main(): Fix64 { + return Fix64(42, rounding: RoundingMode.awayFromZero) + } + `) + + result, err := invokable.Invoke("main") + require.NoError(t, err) + assert.Equal(t, interpreter.NewUnmeteredFix64ValueWithInteger(42), result) + }) +} + +func TestInterpretUFix64WithRoundingMode(t *testing.T) { + t.Parallel() + + type testCase struct { + name string + code string + expected interpreter.UFix64Value + } + + tests := []testCase{ + // towardZero + { + name: "towardZero, non-halfway below", + code: `fun main(): UFix64 { + let x: UFix128 = 1.000000003000000000000000 + return UFix64(x, rounding: RoundingMode.towardZero) + }`, + expected: interpreter.NewUnmeteredUFix64Value(100000000), + }, + { + name: "towardZero, exact halfway", + code: `fun main(): UFix64 { + let x: UFix128 = 1.000000005000000000000000 + return UFix64(x, rounding: RoundingMode.towardZero) + }`, + expected: interpreter.NewUnmeteredUFix64Value(100000000), + }, + { + name: "towardZero, non-halfway above", + code: `fun main(): UFix64 { + let x: UFix128 = 1.000000007000000000000000 + return UFix64(x, rounding: RoundingMode.towardZero) + }`, + expected: interpreter.NewUnmeteredUFix64Value(100000000), + }, + + // awayFromZero + { + name: "awayFromZero, non-halfway below", + code: `fun main(): UFix64 { + let x: UFix128 = 1.000000003000000000000000 + return UFix64(x, rounding: RoundingMode.awayFromZero) + }`, + expected: interpreter.NewUnmeteredUFix64Value(100000001), + }, + { + name: "awayFromZero, exact halfway", + code: `fun main(): UFix64 { + let x: UFix128 = 1.000000005000000000000000 + return UFix64(x, rounding: RoundingMode.awayFromZero) + }`, + expected: interpreter.NewUnmeteredUFix64Value(100000001), + }, + { + name: "awayFromZero, non-halfway above", + code: `fun main(): UFix64 { + let x: UFix128 = 1.000000007000000000000000 + return UFix64(x, rounding: RoundingMode.awayFromZero) + }`, + expected: interpreter.NewUnmeteredUFix64Value(100000001), + }, + + // nearestHalfAway + { + name: "nearestHalfAway, non-halfway below", + code: `fun main(): UFix64 { + let x: UFix128 = 1.000000003000000000000000 + return UFix64(x, rounding: RoundingMode.nearestHalfAway) + }`, + expected: interpreter.NewUnmeteredUFix64Value(100000000), + }, + { + name: "nearestHalfAway, exact halfway", + code: `fun main(): UFix64 { + let x: UFix128 = 1.000000005000000000000000 + return UFix64(x, rounding: RoundingMode.nearestHalfAway) + }`, + expected: interpreter.NewUnmeteredUFix64Value(100000001), // tie → away + }, + { + name: "nearestHalfAway, non-halfway above", + code: `fun main(): UFix64 { + let x: UFix128 = 1.000000007000000000000000 + return UFix64(x, rounding: RoundingMode.nearestHalfAway) + }`, + expected: interpreter.NewUnmeteredUFix64Value(100000001), + }, + + // nearestHalfEven + { + name: "nearestHalfEven, non-halfway below", + code: `fun main(): UFix64 { + let x: UFix128 = 1.000000003000000000000000 + return UFix64(x, rounding: RoundingMode.nearestHalfEven) + }`, + expected: interpreter.NewUnmeteredUFix64Value(100000000), + }, + { + name: "nearestHalfEven, exact halfway (last digit 0 is even)", + code: `fun main(): UFix64 { + let x: UFix128 = 1.000000005000000000000000 + return UFix64(x, rounding: RoundingMode.nearestHalfEven) + }`, + expected: interpreter.NewUnmeteredUFix64Value(100000000), // tie → even, 0 is even + }, + { + name: "nearestHalfEven, non-halfway above", + code: `fun main(): UFix64 { + let x: UFix128 = 1.000000007000000000000000 + return UFix64(x, rounding: RoundingMode.nearestHalfEven) + }`, + expected: interpreter.NewUnmeteredUFix64Value(100000001), + }, + { + name: "nearestHalfEven, exact halfway (last digit 1 is odd)", + code: `fun main(): UFix64 { + let x: UFix128 = 1.000000015000000000000000 + return UFix64(x, rounding: RoundingMode.nearestHalfEven) + }`, + expected: interpreter.NewUnmeteredUFix64Value(100000002), // tie → even, 2 is even + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + invokable := parseCheckAndPrepareWithRoundingMode(t, tc.code) + result, err := invokable.Invoke("main") + require.NoError(t, err) + assert.Equal(t, tc.expected, result) + }) + } + + t.Run("backward compat, no rounding truncates", func(t *testing.T) { + t.Parallel() + + invokable := parseCheckAndPrepare(t, ` + fun main(): UFix64 { + let x: UFix128 = 1.000000005000000000000000 + return UFix64(x) + } + `) + + result, err := invokable.Invoke("main") + require.NoError(t, err) + assert.Equal(t, interpreter.NewUnmeteredUFix64Value(100000000), result) + }) +} + +func TestInterpretFix64WithRoundingModeOverflow(t *testing.T) { + t.Parallel() + + t.Run("Fix128 overflow", func(t *testing.T) { + t.Parallel() + + invokable := parseCheckAndPrepareWithRoundingMode(t, ` + fun main(): Fix64 { + // Fix128 max is much larger than Fix64 max + let x: Fix128 = Fix128.max + return Fix64(x, rounding: RoundingMode.towardZero) + } + `) + + _, err := invokable.Invoke("main") + RequireError(t, err) + var expectedError *interpreter.OverflowError + require.ErrorAs(t, err, &expectedError) + }) + + t.Run("Fix128 negative overflow", func(t *testing.T) { + t.Parallel() + + invokable := parseCheckAndPrepareWithRoundingMode(t, ` + fun main(): Fix64 { + let x: Fix128 = Fix128.min + return Fix64(x, rounding: RoundingMode.towardZero) + } + `) + + _, err := invokable.Invoke("main") + RequireError(t, err) + var expectedError *interpreter.UnderflowError + require.ErrorAs(t, err, &expectedError) + }) + + t.Run("rounding causes overflow", func(t *testing.T) { + t.Parallel() + + invokable := parseCheckAndPrepareWithRoundingMode(t, ` + fun main(): Fix64 { + // Fix64.max as Fix128, plus a fraction that would round up + let x: Fix128 = 92233720368.547758079999999999999999 + return Fix64(x, rounding: RoundingMode.awayFromZero) + } + `) + + _, err := invokable.Invoke("main") + RequireError(t, err) + var expectedError *interpreter.OverflowError + require.ErrorAs(t, err, &expectedError) + }) +} + +func TestInterpretUFix64WithRoundingModeOverflow(t *testing.T) { + t.Parallel() + + t.Run("UFix128 overflow", func(t *testing.T) { + t.Parallel() + + invokable := parseCheckAndPrepareWithRoundingMode(t, ` + fun main(): UFix64 { + let x: UFix128 = UFix128.max + return UFix64(x, rounding: RoundingMode.towardZero) + } + `) + + _, err := invokable.Invoke("main") + RequireError(t, err) + var expectedError *interpreter.OverflowError + require.ErrorAs(t, err, &expectedError) + }) + + t.Run("Fix128 negative to UFix64", func(t *testing.T) { + t.Parallel() + + invokable := parseCheckAndPrepareWithRoundingMode(t, ` + fun main(): UFix64 { + let x: Fix128 = -1.0 + return UFix64(x, rounding: RoundingMode.towardZero) + } + `) + + _, err := invokable.Invoke("main") + RequireError(t, err) + var expectedError *interpreter.UnderflowError + require.ErrorAs(t, err, &expectedError) + }) +} + +func TestInterpretRoundingModeEnum(t *testing.T) { + t.Parallel() + + invokable := parseCheckAndPrepareWithRoundingMode(t, ` + fun main(): [UInt8] { + return [ + RoundingMode.towardZero.rawValue, + RoundingMode.awayFromZero.rawValue, + RoundingMode.nearestHalfAway.rawValue, + RoundingMode.nearestHalfEven.rawValue + ] + } + `) + + result, err := invokable.Invoke("main") + require.NoError(t, err) + + arrayValue := result.(*interpreter.ArrayValue) + require.Equal(t, 4, arrayValue.Count()) + + assert.Equal(t, interpreter.UInt8Value(0), arrayValue.Get(nil, 0)) + assert.Equal(t, interpreter.UInt8Value(1), arrayValue.Get(nil, 1)) + assert.Equal(t, interpreter.UInt8Value(2), arrayValue.Get(nil, 2)) + assert.Equal(t, interpreter.UInt8Value(3), arrayValue.Get(nil, 3)) +} diff --git a/interpreter/interpreter.go b/interpreter/interpreter.go index 20fedbb73d..95ee7707f3 100644 --- a/interpreter/interpreter.go +++ b/interpreter/interpreter.go @@ -34,6 +34,8 @@ import ( "github.com/onflow/atree" "go.opentelemetry.io/otel/attribute" + fix "github.com/onflow/fixed-point" + "github.com/onflow/cadence/activations" "github.com/onflow/cadence/ast" "github.com/onflow/cadence/common" @@ -3510,10 +3512,11 @@ var BigEndianBytesConverters = func() map[string]TypedBigEndianBytesConverter { }() type ValueConverterDeclaration struct { - Min Value - Max Value - Convert func(common.MemoryGauge, Value) Value - nestedVariables []struct { + Min Value + Max Value + Convert func(common.MemoryGauge, Value) Value + ConvertWithRounding func(common.MemoryGauge, Value, fix.RoundingMode) Value + nestedVariables []struct { Name string Value Value } @@ -3678,6 +3681,9 @@ var ConverterDeclarations = []ValueConverterDeclaration{ Convert: func(gauge common.MemoryGauge, value Value) Value { return ConvertFix64(gauge, value) }, + ConvertWithRounding: func(gauge common.MemoryGauge, value Value, round fix.RoundingMode) Value { + return ConvertFix64WithRounding(gauge, value, round) + }, Min: NewUnmeteredFix64Value(math.MinInt64), Max: NewUnmeteredFix64Value(math.MaxInt64), }, @@ -3694,6 +3700,9 @@ var ConverterDeclarations = []ValueConverterDeclaration{ Convert: func(gauge common.MemoryGauge, value Value) Value { return ConvertUFix64(gauge, value) }, + ConvertWithRounding: func(gauge common.MemoryGauge, value Value, round fix.RoundingMode) Value { + return ConvertUFix64WithRounding(gauge, value, round) + }, Min: NewUnmeteredUFix64Value(0), Max: NewUnmeteredUFix64Value(math.MaxUint64), }, @@ -4158,12 +4167,20 @@ var converterFunctionValues = func() []converterFunction { for index, declaration := range ConverterDeclarations { // NOTE: declare in loop, as captured in closure below convert := declaration.Convert + convertWithRounding := declaration.ConvertWithRounding converterFunctionType := sema.BaseValueActivation.Find(declaration.Name).Type.(*sema.FunctionType) + var nativeFn NativeFunction + if convertWithRounding != nil { + nativeFn = NativeConverterFunctionWithRounding(convert, convertWithRounding) + } else { + nativeFn = NativeConverterFunction(convert) + } + converterFunctionValue := NewUnmeteredStaticHostFunctionValueFromNativeFunction( converterFunctionType, - NativeConverterFunction(convert), + nativeFn, ) addMember := func(name string, value Value) { @@ -4434,6 +4451,25 @@ func NativeConverterFunction(convert func(memoryGauge common.MemoryGauge, value } } +func NativeConverterFunctionWithRounding( + convert func(memoryGauge common.MemoryGauge, value Value) Value, + convertWithRounding func(memoryGauge common.MemoryGauge, value Value, round fix.RoundingMode) Value, +) NativeFunction { + return func( + context NativeFunctionContext, + _ TypeArgumentsIterator, + _ ArgumentTypesIterator, + _ Value, + args []Value, + ) Value { + if len(args) > 1 { + roundingMode := extractRoundingMode(args[1]) + return convertWithRounding(context, args[0], roundingMode) + } + return convert(context, args[0]) + } +} + func NativeFromStringFunction(parser StringValueParser) NativeFunction { return func( context NativeFunctionContext, diff --git a/interpreter/value_fix64.go b/interpreter/value_fix64.go index 3e4842467d..98f692db7a 100644 --- a/interpreter/value_fix64.go +++ b/interpreter/value_fix64.go @@ -27,6 +27,8 @@ import ( "github.com/onflow/atree" + fix "github.com/onflow/fixed-point" + "github.com/onflow/cadence/ast" "github.com/onflow/cadence/common" "github.com/onflow/cadence/errors" @@ -514,6 +516,40 @@ func ConvertFix64(memoryGauge common.MemoryGauge, value Value) Fix64Value { } } +func ConvertFix64WithRounding(memoryGauge common.MemoryGauge, value Value, roundingMode fix.RoundingMode) Fix64Value { + switch value := value.(type) { + case Fix128Value: + return NewFix64Value( + memoryGauge, + func() int64 { + result, err := fix.Fix128(value).ToFix64(roundingMode) + if err != nil { + handleFixedPointConversionError(err) + } + return int64(result) + }, + ) + + case UFix128Value: + return NewFix64Value( + memoryGauge, + func() int64 { + result, err := fix.UFix128(value).ToUFix64(roundingMode) + if err != nil { + handleFixedPointConversionError(err) + } + if uint64(result) > Fix64MaxValue { + panic(&OverflowError{}) + } + return int64(result) + }, + ) + + default: + return ConvertFix64(memoryGauge, value) + } +} + func (v Fix64Value) GetMember(context MemberAccessibleContext, name string, memberKind common.DeclarationKind) Value { return GetMember( context, diff --git a/interpreter/value_fixedpoint.go b/interpreter/value_fixedpoint.go new file mode 100644 index 0000000000..be97dcd3b1 --- /dev/null +++ b/interpreter/value_fixedpoint.go @@ -0,0 +1,60 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package interpreter + +import ( + fix "github.com/onflow/fixed-point" + + "github.com/onflow/cadence/errors" + "github.com/onflow/cadence/sema" +) + +func extractRoundingMode(value Value) fix.RoundingMode { + composite, ok := value.(*SimpleCompositeValue) + if !ok { + panic(errors.NewUnreachableError()) + } + rawValue, ok := composite.Fields[sema.EnumRawValueFieldName].(UInt8Value) + if !ok { + panic(errors.NewUnreachableError()) + } + return fix.RoundingMode(rawValue) +} + +// handleFixedPointConversionError handles errors from the fixed-point library +// during narrowing conversions (e.g. Fix128 → Fix64). +// +// Unlike handleFixedpointError (used for Fix128 arithmetic), +// this function does NOT ignore UnderflowError: +// for narrowing conversions, a nonzero value that rounds to zero +// is a loss of the entire value, not just precision. +func handleFixedPointConversionError(err error) { + switch err.(type) { + case nil: + return + case fix.PositiveOverflowError: + panic(&OverflowError{}) + case fix.NegativeOverflowError: + panic(&UnderflowError{}) + case fix.UnderflowError: + panic(&UnderflowError{}) + default: + panic(err) + } +} diff --git a/interpreter/value_ufix64.go b/interpreter/value_ufix64.go index 46979bf771..d739ffe39a 100644 --- a/interpreter/value_ufix64.go +++ b/interpreter/value_ufix64.go @@ -26,6 +26,8 @@ import ( "github.com/onflow/atree" + fix "github.com/onflow/fixed-point" + "github.com/onflow/cadence/ast" "github.com/onflow/cadence/common" "github.com/onflow/cadence/errors" @@ -167,6 +169,42 @@ func ConvertUFix64(memoryGauge common.MemoryGauge, value Value) UFix64Value { } } +func ConvertUFix64WithRounding(memoryGauge common.MemoryGauge, value Value, roundingMode fix.RoundingMode) UFix64Value { + switch value := value.(type) { + case UFix128Value: + return NewUFix64Value( + memoryGauge, + func() uint64 { + result, err := fix.UFix128(value).ToUFix64(roundingMode) + if err != nil { + handleFixedPointConversionError(err) + } + return uint64(result) + }, + ) + + case Fix128Value: + return NewUFix64Value( + memoryGauge, + func() uint64 { + fix128 := fix.Fix128(value) + if fix128.IsNeg() { + panic(&UnderflowError{}) + } + // A non-negative Fix128 has the same bit representation as UFix128 + result, err := fix.UFix128(fix128).ToUFix64(roundingMode) + if err != nil { + handleFixedPointConversionError(err) + } + return uint64(result) + }, + ) + + default: + return ConvertUFix64(memoryGauge, value) + } +} + var _ Value = UFix64Value{} var _ atree.Storable = UFix64Value{} var _ NumberValue = UFix64Value{} diff --git a/runtime/account_test.go b/runtime/account_test.go index 3ad2141ca6..22a3a28b2a 100644 --- a/runtime/account_test.go +++ b/runtime/account_test.go @@ -1223,6 +1223,7 @@ var AccountKeyType = ExportedBuiltinType(sema.AccountKeyType).(*cadence.StructTy var PublicKeyType = ExportedBuiltinType(sema.PublicKeyType).(*cadence.StructType) var SignAlgoType = ExportedBuiltinType(sema.SignatureAlgorithmType).(*cadence.EnumType) var HashAlgoType = ExportedBuiltinType(sema.HashAlgorithmType).(*cadence.EnumType) +var RoundingModeEnumType = ExportedBuiltinType(sema.RoundingModeType).(*cadence.EnumType) func ExportedBuiltinType(internalType sema.Type) cadence.Type { return ExportType(internalType, map[sema.TypeID]cadence.Type{}) diff --git a/runtime/convertValues.go b/runtime/convertValues.go index 7a5465b172..e12ed867e5 100644 --- a/runtime/convertValues.go +++ b/runtime/convertValues.go @@ -1582,6 +1582,9 @@ func (i valueImporter) importCompositeValue( // (e.g. it has host functions) return i.importSignatureAlgorithm(fields) + case sema.RoundingModeType: + return i.importRoundingMode(fields) + default: return nil, errors.NewDefaultUserError( "cannot import value of type %s", @@ -1771,3 +1774,56 @@ func (valueImporter) importSignatureAlgorithm( return caseValue, nil } + +func (valueImporter) importRoundingMode( + fields []interpreter.CompositeField, +) ( + interpreter.MemberAccessibleValue, + error, +) { + + var foundRawValue bool + var rawValue interpreter.UInt8Value + + ty := sema.RoundingModeType + + for _, field := range fields { + switch field.Name { + case sema.EnumRawValueFieldName: + rawValue, foundRawValue = field.Value.(interpreter.UInt8Value) + if !foundRawValue { + return nil, errors.NewDefaultUserError( + "cannot import value of type '%s'. invalid value for field '%s': %v", + ty, + field.Name, + field.Value, + ) + } + + default: + return nil, errors.NewDefaultUserError( + "cannot import value of type '%s'. invalid field '%s'", + ty, + field.Name, + ) + } + } + + if !foundRawValue { + return nil, errors.NewDefaultUserError( + "cannot import value of type '%s'. missing field '%s'", + ty, + sema.EnumRawValueFieldName, + ) + } + + caseValue, ok := stdlib.RoundingModeCaseValues[rawValue] + if !ok { + return nil, errors.NewDefaultUserError( + "unknown RoundingMode with rawValue %d", + rawValue, + ) + } + + return caseValue, nil +} diff --git a/runtime/crypto_test.go b/runtime/crypto_test.go index c4db701b95..271c0b24fd 100644 --- a/runtime/crypto_test.go +++ b/runtime/crypto_test.go @@ -185,7 +185,7 @@ func TestRuntimeHashingAlgorithmExport(t *testing.T) { runtimeInterface := &TestRuntimeInterface{} nextScriptLocation := NewScriptLocationGenerator() - testHashAlgorithm := func(algo sema.CryptoAlgorithm) { + testHashAlgorithm := func(algo sema.NativeEnumCase) { script := fmt.Sprintf(` access(all) fun main(): HashAlgorithm { return HashAlgorithm.%s @@ -231,7 +231,7 @@ func TestRuntimeSignatureAlgorithmExport(t *testing.T) { runtimeInterface := &TestRuntimeInterface{} nextScriptLocation := NewScriptLocationGenerator() - testSignatureAlgorithm := func(algo sema.CryptoAlgorithm) { + testSignatureAlgorithm := func(algo sema.NativeEnumCase) { script := fmt.Sprintf(` access(all) fun main(): SignatureAlgorithm { return SignatureAlgorithm.%s @@ -288,7 +288,7 @@ func TestRuntimeSignatureAlgorithmImport(t *testing.T) { nextScriptLocation := NewScriptLocationGenerator() - testSignatureAlgorithm := func(algo sema.CryptoAlgorithm) { + testSignatureAlgorithm := func(algo sema.NativeEnumCase) { value, err := runtime.ExecuteScript( Script{ @@ -344,7 +344,7 @@ func TestRuntimeHashAlgorithmImport(t *testing.T) { } ` - testHashAlgorithm := func(algo sema.CryptoAlgorithm) { + testHashAlgorithm := func(algo sema.NativeEnumCase) { var logs []string var hashCalls int diff --git a/runtime/program_params_validation_test.go b/runtime/program_params_validation_test.go index eb2216297f..d8ec5faa5c 100644 --- a/runtime/program_params_validation_test.go +++ b/runtime/program_params_validation_test.go @@ -474,6 +474,13 @@ func TestRuntimeScriptParameterTypeValidation(t *testing.T) { }, ).WithType(SignAlgoType) + case sema.RoundingModeType: + value = cadence.NewEnum( + []cadence.Value{ + cadence.NewUInt8(0), + }, + ).WithType(RoundingModeEnumType) + case sema.PublicKeyType: value = cadence.NewStruct( []cadence.Value{ @@ -1066,6 +1073,13 @@ func TestRuntimeTransactionParameterTypeValidation(t *testing.T) { }, ).WithType(SignAlgoType) + case sema.RoundingModeType: + value = cadence.NewEnum( + []cadence.Value{ + cadence.NewUInt8(0), + }, + ).WithType(RoundingModeEnumType) + case sema.PublicKeyType: value = cadence.NewStruct( []cadence.Value{ diff --git a/runtime/rounding_mode_test.go b/runtime/rounding_mode_test.go new file mode 100644 index 0000000000..3b32a9ac37 --- /dev/null +++ b/runtime/rounding_mode_test.go @@ -0,0 +1,242 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package runtime_test + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/onflow/cadence" + "github.com/onflow/cadence/encoding/json" + . "github.com/onflow/cadence/runtime" + "github.com/onflow/cadence/sema" + . "github.com/onflow/cadence/test_utils/runtime_utils" +) + +func newRoundingModeArgument(rawValue uint8) cadence.Value { + return cadence.NewEnum([]cadence.Value{ + cadence.UInt8(rawValue), + }).WithType(cadence.NewEnumType( + nil, + sema.RoundingModeTypeName, + cadence.UInt8Type, + []cadence.Field{ + { + Identifier: sema.EnumRawValueFieldName, + Type: cadence.UInt8Type, + }, + }, + nil, + )) +} + +func TestRuntimeRoundingModeExport(t *testing.T) { + + t.Parallel() + + runtime := NewTestRuntime() + runtimeInterface := &TestRuntimeInterface{} + nextScriptLocation := NewScriptLocationGenerator() + + testRoundingMode := func(mode sema.NativeEnumCase) { + script := fmt.Sprintf(` + access(all) fun main(): RoundingMode { + return RoundingMode.%s + } + `, + mode.Name(), + ) + + value, err := runtime.ExecuteScript( + Script{ + Source: []byte(script), + }, + Context{ + Interface: runtimeInterface, + Location: nextScriptLocation(), + UseVM: *compile, + }, + ) + + require.NoError(t, err) + + require.IsType(t, cadence.Enum{}, value) + enumValue := value.(cadence.Enum) + + fields := cadence.FieldsMappedByName(enumValue) + require.Len(t, fields, 1) + assert.Equal(t, + cadence.NewUInt8(mode.RawValue()), + fields[sema.EnumRawValueFieldName], + ) + } + + for _, mode := range sema.RoundingModes { + testRoundingMode(mode) + } +} + +func TestRuntimeRoundingModeImport(t *testing.T) { + + t.Parallel() + + runtime := NewTestRuntime() + runtimeInterface := &TestRuntimeInterface{ + OnDecodeArgument: func(b []byte, t cadence.Type) (cadence.Value, error) { + return json.Decode(nil, b) + }, + } + nextScriptLocation := NewScriptLocationGenerator() + + const script = ` + access(all) fun main(mode: RoundingMode): UInt8 { + return mode.rawValue + } + ` + + testRoundingMode := func(mode sema.NativeEnumCase) { + + value, err := runtime.ExecuteScript( + Script{ + Source: []byte(script), + Arguments: encodeArgs([]cadence.Value{ + newRoundingModeArgument(mode.RawValue()), + }), + }, + Context{ + Interface: runtimeInterface, + Location: nextScriptLocation(), + UseVM: *compile, + }, + ) + + require.NoError(t, err) + assert.Equal(t, cadence.UInt8(mode.RawValue()), value) + } + + for _, mode := range sema.RoundingModes { + testRoundingMode(mode) + } +} + +func TestRuntimeRoundingModeImportInvalid(t *testing.T) { + + t.Parallel() + + runtime := NewTestRuntime() + runtimeInterface := &TestRuntimeInterface{ + OnDecodeArgument: func(b []byte, t cadence.Type) (cadence.Value, error) { + return json.Decode(nil, b) + }, + } + nextScriptLocation := NewScriptLocationGenerator() + + const script = ` + access(all) fun main(mode: RoundingMode): UInt8 { + return mode.rawValue + } + ` + + _, err := runtime.ExecuteScript( + Script{ + Source: []byte(script), + Arguments: encodeArgs([]cadence.Value{ + newRoundingModeArgument(99), // invalid raw value + }), + }, + Context{ + Interface: runtimeInterface, + Location: nextScriptLocation(), + UseVM: *compile, + }, + ) + + require.Error(t, err) + assert.Contains(t, err.Error(), "unknown RoundingMode") +} + +func TestRuntimeFix64ConversionWithRoundingModeArgument(t *testing.T) { + + t.Parallel() + + runtime := NewTestRuntime() + runtimeInterface := &TestRuntimeInterface{ + OnDecodeArgument: func(b []byte, t cadence.Type) (cadence.Value, error) { + return json.Decode(nil, b) + }, + } + nextScriptLocation := NewScriptLocationGenerator() + + t.Run("Fix128 to Fix64 with rounding", func(t *testing.T) { + t.Parallel() + + const script = ` + access(all) fun main(mode: RoundingMode): Fix64 { + let x: Fix128 = 1.000000005000000000000000 + return Fix64(x, rounding: mode) + } + ` + + // towardZero should truncate + value, err := runtime.ExecuteScript( + Script{ + Source: []byte(script), + Arguments: encodeArgs([]cadence.Value{newRoundingModeArgument(0)}), + }, + Context{ + Interface: runtimeInterface, + Location: nextScriptLocation(), + UseVM: *compile, + }, + ) + + require.NoError(t, err) + assert.Equal(t, cadence.Fix64(100000000), value) // 1.00000000 + }) + + t.Run("UFix128 to UFix64 with rounding", func(t *testing.T) { + t.Parallel() + + const script = ` + access(all) fun main(mode: RoundingMode): UFix64 { + let x: UFix128 = 1.000000005000000000000000 + return UFix64(x, rounding: mode) + } + ` + + // awayFromZero should round up + value, err := runtime.ExecuteScript( + Script{ + Source: []byte(script), + Arguments: encodeArgs([]cadence.Value{newRoundingModeArgument(1)}), + }, + Context{ + Interface: runtimeInterface, + Location: nextScriptLocation(), + UseVM: *compile, + }, + ) + + require.NoError(t, err) + assert.Equal(t, cadence.UFix64(100000001), value) // 1.00000001 + }) +} diff --git a/sema/crypto_test.go b/sema/crypto_test.go index 6c19362ddf..43be41ef11 100644 --- a/sema/crypto_test.go +++ b/sema/crypto_test.go @@ -39,7 +39,7 @@ func TestCheckHashAlgorithmCases(t *testing.T) { baseValueActivation.DeclareValue(value) } - test := func(algorithm sema.CryptoAlgorithm) { + test := func(algorithm sema.NativeEnumCase) { _, err := ParseAndCheckWithOptions(t, fmt.Sprintf( @@ -120,7 +120,7 @@ func TestCheckSignatureAlgorithmCases(t *testing.T) { baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) baseValueActivation.DeclareValue(stdlib.InterpreterSignatureAlgorithmConstructor) - test := func(algorithm sema.CryptoAlgorithm) { + test := func(algorithm sema.NativeEnumCase) { _, err := ParseAndCheckWithOptions(t, fmt.Sprintf( diff --git a/sema/rounding_mode_test.go b/sema/rounding_mode_test.go new file mode 100644 index 0000000000..39dab7c8fe --- /dev/null +++ b/sema/rounding_mode_test.go @@ -0,0 +1,230 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sema_test + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/onflow/cadence/common" + "github.com/onflow/cadence/sema" + "github.com/onflow/cadence/stdlib" + . "github.com/onflow/cadence/test_utils/sema_utils" +) + +func TestCheckRoundingModeCases(t *testing.T) { + + t.Parallel() + + baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) + for _, value := range stdlib.InterpreterDefaultScriptStandardLibraryValues(nil) { + baseValueActivation.DeclareValue(value) + } + + test := func(mode sema.NativeEnumCase) { + + _, err := ParseAndCheckWithOptions(t, + fmt.Sprintf( + ` + let mode: RoundingMode = RoundingMode.%s + `, + mode.Name(), + ), + ParseAndCheckOptions{ + CheckerConfig: &sema.Config{ + BaseValueActivationHandler: func(_ common.Location) *sema.VariableActivation { + return baseValueActivation + }, + }, + }, + ) + + require.NoError(t, err) + } + + for _, mode := range sema.RoundingModes { + test(mode) + } +} + +func TestCheckRoundingModeConstructor(t *testing.T) { + + t.Parallel() + + baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) + baseValueActivation.DeclareValue(stdlib.InterpreterRoundingModeConstructor) + + _, err := ParseAndCheckWithOptions(t, + ` + let mode = RoundingMode(rawValue: 0) + `, + ParseAndCheckOptions{ + CheckerConfig: &sema.Config{ + BaseValueActivationHandler: func(_ common.Location) *sema.VariableActivation { + return baseValueActivation + }, + }, + }, + ) + + require.NoError(t, err) +} + +func TestCheckRoundingModeRawValue(t *testing.T) { + + t.Parallel() + + baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) + for _, value := range stdlib.InterpreterDefaultScriptStandardLibraryValues(nil) { + baseValueActivation.DeclareValue(value) + } + + _, err := ParseAndCheckWithOptions(t, + ` + let mode = RoundingMode.towardZero + let rawValue: UInt8 = mode.rawValue + `, + ParseAndCheckOptions{ + CheckerConfig: &sema.Config{ + BaseValueActivationHandler: func(_ common.Location) *sema.VariableActivation { + return baseValueActivation + }, + }, + }, + ) + + require.NoError(t, err) +} + +func TestCheckFix64WithRoundingMode(t *testing.T) { + + t.Parallel() + + baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) + for _, value := range stdlib.InterpreterDefaultScriptStandardLibraryValues(nil) { + baseValueActivation.DeclareValue(value) + } + + t.Run("with rounding", func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheckWithOptions(t, + ` + let x: Fix64 = Fix64(1, rounding: RoundingMode.towardZero) + `, + ParseAndCheckOptions{ + CheckerConfig: &sema.Config{ + BaseValueActivationHandler: func(_ common.Location) *sema.VariableActivation { + return baseValueActivation + }, + }, + }, + ) + + require.NoError(t, err) + }) + + t.Run("without rounding", func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheckWithOptions(t, + ` + let x: Fix64 = Fix64(1) + `, + ParseAndCheckOptions{ + CheckerConfig: &sema.Config{ + BaseValueActivationHandler: func(_ common.Location) *sema.VariableActivation { + return baseValueActivation + }, + }, + }, + ) + + require.NoError(t, err) + }) + + t.Run("invalid rounding type", func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheckWithOptions(t, + ` + let x: Fix64 = Fix64(1, rounding: 42) + `, + ParseAndCheckOptions{ + CheckerConfig: &sema.Config{ + BaseValueActivationHandler: func(_ common.Location) *sema.VariableActivation { + return baseValueActivation + }, + }, + }, + ) + + require.Error(t, err) + }) +} + +func TestCheckUFix64WithRoundingMode(t *testing.T) { + + t.Parallel() + + baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) + for _, value := range stdlib.InterpreterDefaultScriptStandardLibraryValues(nil) { + baseValueActivation.DeclareValue(value) + } + + t.Run("with rounding", func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheckWithOptions(t, + ` + let x: UFix64 = UFix64(1, rounding: RoundingMode.nearestHalfEven) + `, + ParseAndCheckOptions{ + CheckerConfig: &sema.Config{ + BaseValueActivationHandler: func(_ common.Location) *sema.VariableActivation { + return baseValueActivation + }, + }, + }, + ) + + require.NoError(t, err) + }) + + t.Run("without rounding", func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheckWithOptions(t, + ` + let x: UFix64 = UFix64(1) + `, + ParseAndCheckOptions{ + CheckerConfig: &sema.Config{ + BaseValueActivationHandler: func(_ common.Location) *sema.VariableActivation { + return baseValueActivation + }, + }, + }, + ) + + require.NoError(t, err) + }) +} diff --git a/sema/rounding_mode_type.go b/sema/rounding_mode_type.go new file mode 100644 index 0000000000..294e8c54f8 --- /dev/null +++ b/sema/rounding_mode_type.go @@ -0,0 +1,143 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sema + +import "github.com/onflow/cadence/errors" + +const RoundingModeTypeName = "RoundingMode" + +var RoundingModeType = newNativeEnumType( + RoundingModeTypeName, + UInt8Type, + nil, +) + +var RoundingModeTypeAnnotation = NewTypeAnnotation(RoundingModeType) + +type RoundingMode uint8 + +// NOTE: only add new modes, do *NOT* change existing items, +// reuse raw values for other items, swap the order, etc. +// +// # Existing stored values use these raw values and should not change +// +// IMPORTANT: update RoundingModes +const ( + RoundingModeTowardZero RoundingMode = iota + RoundingModeAwayFromZero + RoundingModeNearestHalfAway + RoundingModeNearestHalfEven + + // !!! *WARNING* !!! + // ADD NEW MODES *BEFORE* THIS WARNING. + // DO *NOT* ADD NEW MODES AFTER THIS LINE! + RoundingMode_Count +) + +var RoundingModes = []RoundingMode{ + RoundingModeTowardZero, + RoundingModeAwayFromZero, + RoundingModeNearestHalfAway, + RoundingModeNearestHalfEven, +} + +func (mode RoundingMode) Name() string { + switch mode { + case RoundingModeTowardZero: + return "towardZero" + case RoundingModeAwayFromZero: + return "awayFromZero" + case RoundingModeNearestHalfAway: + return "nearestHalfAway" + case RoundingModeNearestHalfEven: + return "nearestHalfEven" + } + + panic(errors.NewUnreachableError()) +} + +func (mode RoundingMode) RawValue() uint8 { + switch mode { + case RoundingModeTowardZero: + return 0 + case RoundingModeAwayFromZero: + return 1 + case RoundingModeNearestHalfAway: + return 2 + case RoundingModeNearestHalfEven: + return 3 + } + + panic(errors.NewUnreachableError()) +} + +func (mode RoundingMode) DocString() string { + switch mode { + case RoundingModeTowardZero: + return RoundingModeTowardZeroDocString + case RoundingModeAwayFromZero: + return RoundingModeAwayFromZeroDocString + case RoundingModeNearestHalfAway: + return RoundingModeNearestHalfAwayDocString + case RoundingModeNearestHalfEven: + return RoundingModeNearestHalfEvenDocString + } + + panic(errors.NewUnreachableError()) +} + +const RoundingModeTowardZeroDocString = ` +Round to the closest representable fixed-point value that has +a magnitude less than or equal to the magnitude of the real result, +effectively truncating the fractional part. + +e.g. 5e-8 / 2 = 2e-8, -5e-8 / 2 = -2e-8 +` + +const RoundingModeAwayFromZeroDocString = ` +Round to the closest representable fixed-point value that has +a magnitude greater than or equal to the magnitude of the real result, +effectively rounding up any fractional part. + +e.g. 5e-8 / 2 = 3e-8, -5e-8 / 2 = -3e-8 +` + +const RoundingModeNearestHalfAwayDocString = ` +Round to the closest representable fixed-point value to the real result, +which could be larger (rounded up) or smaller (rounded down) depending on +if the unrepresentable portion is greater than or less than one half +the difference between two available values. + +If two representable values are equally close, +the value will be rounded away from zero. + +e.g. 7e-8 / 2 = 4e-8, 5e-8 / 2 = 3e-8 +` + +const RoundingModeNearestHalfEvenDocString = ` +Round to the closest representable fixed-point value to the real result, +which could be larger (rounded up) or smaller (rounded down) depending on +if the unrepresentable portion is greater than or less than one half +the difference between two available values. + +If two representable values are equally close, +the value with an even digit in the smallest decimal place will be chosen. + +e.g. 7e-8 / 2 = 4e-8, 5e-8 / 2 = 2e-8 +` diff --git a/sema/rounding_mode_type_test.go b/sema/rounding_mode_type_test.go new file mode 100644 index 0000000000..8a517e3719 --- /dev/null +++ b/sema/rounding_mode_type_test.go @@ -0,0 +1,56 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sema + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestRoundingModeValues(t *testing.T) { + t.Parallel() + + // Ensure that the values of the RoundingMode enum are not accidentally changed, + // e.g. by adding a new value in between or by changing an existing value. + + expectedValues := map[RoundingMode]uint8{ + RoundingModeTowardZero: 0, + RoundingModeAwayFromZero: 1, + RoundingModeNearestHalfAway: 2, + RoundingModeNearestHalfEven: 3, + RoundingMode_Count: 4, + } + + // Check all expected values. + for mode, expectedValue := range expectedValues { + require.Equal(t, expectedValue, uint8(mode), "value mismatch for %d", mode) + } + + // Check that no new values have been added + // without updating the expected values above. + for i := uint8(0); i < uint8(RoundingMode_Count); i++ { + mode := RoundingMode(i) + _, ok := expectedValues[mode] + require.True(t, ok, + fmt.Sprintf("unexpected RoundingMode value %d: update expectedValues", i), + ) + } +} diff --git a/sema/type.go b/sema/type.go index 45bf8a58a0..fb82dd3bb4 100644 --- a/sema/type.go +++ b/sema/type.go @@ -4570,6 +4570,7 @@ var AllBuiltinTypes = common.Concat( PublicKeyType, SignatureAlgorithmType, HashAlgorithmType, + RoundingModeType, StorageCapabilityControllerType, AccountCapabilityControllerType, DeploymentResultType, @@ -4752,7 +4753,13 @@ func init() { panic(errors.NewUnreachableError()) } - functionType := NumberConversionFunctionType(numberType) + var functionType *FunctionType + switch numberType { + case Fix64Type, UFix64Type: + functionType = FixedPoint64ConversionFunctionType(numberType) + default: + functionType = NumberConversionFunctionType(numberType) + } addMember := func(member *Member) { if functionType.Members == nil { @@ -4865,6 +4872,28 @@ func NumberConversionFunctionType(numberType Type) *FunctionType { } } +func FixedPoint64ConversionFunctionType(numberType Type) *FunctionType { + return &FunctionType{ + Purity: FunctionPurityView, + TypeFunctionType: numberType, + Parameters: []Parameter{ + { + Label: ArgumentLabelNotRequired, + Identifier: "value", + TypeAnnotation: NumberTypeAnnotation, + }, + { + Label: "rounding", + Identifier: "rounding", + TypeAnnotation: RoundingModeTypeAnnotation, + }, + }, + Arity: &Arity{Min: 1, Max: 2}, + ReturnTypeAnnotation: NewTypeAnnotation(numberType), + ArgumentExpressionsCheck: numberFunctionArgumentExpressionsChecker(numberType), + } +} + func numberConversionDocString(targetDescription string) string { return fmt.Sprintf( "Converts the given number to %s. %s", @@ -9671,7 +9700,7 @@ var PublicKeyTypeVerifyPoPFunctionType = NewSimpleFunctionType( BoolTypeAnnotation, ) -type CryptoAlgorithm interface { +type NativeEnumCase interface { RawValue() uint8 Name() string DocString() string @@ -10176,6 +10205,7 @@ func init() { PublicKeyType, HashAlgorithmType, SignatureAlgorithmType, + RoundingModeType, AccountType, DeploymentResultType, } diff --git a/stdlib/builtin.go b/stdlib/builtin.go index 76d39f9ef5..30cecd0b36 100644 --- a/stdlib/builtin.go +++ b/stdlib/builtin.go @@ -57,6 +57,7 @@ func InterpreterDefaultStandardLibraryValues(handler StandardLibraryHandler) []S InterpreterAssertFunction, InterpreterPanicFunction, InterpreterSignatureAlgorithmConstructor, + InterpreterRoundingModeConstructor, InterpreterInclusiveRangeConstructor, NewInterpreterLogFunction(handler), NewInterpreterRevertibleRandomFunction(handler), @@ -76,6 +77,7 @@ func VMDefaultStandardLibraryValues(handler StandardLibraryHandler) []StandardLi VMAssertFunction, VMPanicFunction, VMSignatureAlgorithmConstructor, + VMRoundingModeConstructor, VMInclusiveRangeConstructor, NewVMLogFunction(handler), NewVMRevertibleRandomFunction(handler), @@ -155,6 +157,7 @@ func VMValues(handler StandardLibraryHandler) []VMValue { return common.Concat( VMSignatureAlgorithmCaseValues, NewVMHashAlgorithmCaseValues(handler), + VMRoundingModeCaseValues, ) } diff --git a/stdlib/crypto.go b/stdlib/crypto.go index 8f1da92778..9bfbf4c597 100644 --- a/stdlib/crypto.go +++ b/stdlib/crypto.go @@ -20,69 +20,6 @@ package stdlib import ( "github.com/onflow/cadence/common" - "github.com/onflow/cadence/interpreter" - "github.com/onflow/cadence/sema" ) const CryptoContractLocation = common.IdentifierLocation("Crypto") - -func cryptoAlgorithmEnumLookupType[T sema.CryptoAlgorithm]( - enumType *sema.CompositeType, - enumCases []T, -) *sema.FunctionType { - - functionType := sema.EnumLookupFunctionType(enumType) - - for _, algo := range enumCases { - name := algo.Name() - functionType.Members.Set( - name, - sema.NewUnmeteredPublicConstantFieldMember( - enumType, - name, - enumType, - algo.DocString(), - ), - ) - } - - return functionType -} - -type enumCaseConstructor func(rawValue interpreter.UInt8Value) interpreter.MemberAccessibleValue - -func interpreterCryptoAlgorithmEnumValueAndCaseValues[T sema.CryptoAlgorithm]( - functionType *sema.FunctionType, - enumCases []T, - caseConstructor enumCaseConstructor, -) ( - functionValue interpreter.FunctionValue, - cases map[interpreter.UInt8Value]interpreter.MemberAccessibleValue, -) { - - caseCount := len(enumCases) - caseValues := make([]interpreter.EnumCase, caseCount) - constructorNestedVariables := make(map[string]interpreter.Variable, caseCount) - cases = make(map[interpreter.UInt8Value]interpreter.MemberAccessibleValue, caseCount) - - for i, enumCase := range enumCases { - rawValue := interpreter.UInt8Value(enumCase.RawValue()) - caseValue := caseConstructor(rawValue) - cases[rawValue] = caseValue - caseValues[i] = interpreter.EnumCase{ - Value: caseValue, - RawValue: rawValue, - } - constructorNestedVariables[enumCase.Name()] = - interpreter.NewVariableWithValue(nil, caseValue) - } - - functionValue = interpreter.EnumLookupFunction( - nil, - functionType, - caseValues, - constructorNestedVariables, - ) - - return -} diff --git a/stdlib/enum.go b/stdlib/enum.go new file mode 100644 index 0000000000..0b18ffe7ca --- /dev/null +++ b/stdlib/enum.go @@ -0,0 +1,85 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package stdlib + +import ( + "github.com/onflow/cadence/interpreter" + "github.com/onflow/cadence/sema" +) + +func nativeEnumLookupType[T sema.NativeEnumCase]( + enumType *sema.CompositeType, + enumCases []T, +) *sema.FunctionType { + + functionType := sema.EnumLookupFunctionType(enumType) + + for _, algo := range enumCases { + name := algo.Name() + functionType.Members.Set( + name, + sema.NewUnmeteredPublicConstantFieldMember( + enumType, + name, + enumType, + algo.DocString(), + ), + ) + } + + return functionType +} + +type enumCaseConstructor func(rawValue interpreter.UInt8Value) interpreter.MemberAccessibleValue + +func interpreterNativeEnumValueAndCaseValues[T sema.NativeEnumCase]( + functionType *sema.FunctionType, + enumCases []T, + caseConstructor enumCaseConstructor, +) ( + functionValue interpreter.FunctionValue, + cases map[interpreter.UInt8Value]interpreter.MemberAccessibleValue, +) { + + caseCount := len(enumCases) + caseValues := make([]interpreter.EnumCase, caseCount) + constructorNestedVariables := make(map[string]interpreter.Variable, caseCount) + cases = make(map[interpreter.UInt8Value]interpreter.MemberAccessibleValue, caseCount) + + for i, enumCase := range enumCases { + rawValue := interpreter.UInt8Value(enumCase.RawValue()) + caseValue := caseConstructor(rawValue) + cases[rawValue] = caseValue + caseValues[i] = interpreter.EnumCase{ + Value: caseValue, + RawValue: rawValue, + } + constructorNestedVariables[enumCase.Name()] = + interpreter.NewVariableWithValue(nil, caseValue) + } + + functionValue = interpreter.EnumLookupFunction( + nil, + functionType, + caseValues, + constructorNestedVariables, + ) + + return +} diff --git a/stdlib/hashalgorithm.go b/stdlib/hashalgorithm.go index f7878bb6dd..be6aaabda0 100644 --- a/stdlib/hashalgorithm.go +++ b/stdlib/hashalgorithm.go @@ -27,7 +27,7 @@ import ( "github.com/onflow/cadence/sema" ) -var hashAlgorithmLookupType = cryptoAlgorithmEnumLookupType( +var hashAlgorithmLookupType = nativeEnumLookupType( sema.HashAlgorithmType, sema.HashAlgorithms, ) @@ -237,7 +237,7 @@ func hash( // these functions are left as is, since there are differences in the implementations between interpreter and vm func NewInterpreterHashAlgorithmConstructor(hasher Hasher) StandardLibraryValue { - interpreterHashAlgorithmConstructorValue, _ := interpreterCryptoAlgorithmEnumValueAndCaseValues( + interpreterHashAlgorithmConstructorValue, _ := interpreterNativeEnumValueAndCaseValues( hashAlgorithmLookupType, sema.HashAlgorithms, func(rawValue interpreter.UInt8Value) interpreter.MemberAccessibleValue { diff --git a/stdlib/roundingmode.go b/stdlib/roundingmode.go new file mode 100644 index 0000000000..1a200c0bb5 --- /dev/null +++ b/stdlib/roundingmode.go @@ -0,0 +1,112 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package stdlib + +import ( + "github.com/onflow/cadence/bbq/commons" + "github.com/onflow/cadence/bbq/vm" + "github.com/onflow/cadence/common" + "github.com/onflow/cadence/interpreter" + "github.com/onflow/cadence/sema" +) + +var roundingModeStaticType interpreter.StaticType = interpreter.ConvertSemaCompositeTypeToStaticCompositeType( + nil, + sema.RoundingModeType, +) + +func NewRoundingModeCase(rawValue interpreter.UInt8Value) interpreter.MemberAccessibleValue { + + fields := map[string]interpreter.Value{ + sema.EnumRawValueFieldName: rawValue, + } + + return interpreter.NewSimpleCompositeValue( + nil, + sema.RoundingModeType.ID(), + roundingModeStaticType, + []string{sema.EnumRawValueFieldName}, + fields, + nil, + nil, + nil, + nil, + ) +} + +var roundingModeLookupType = nativeEnumLookupType( + sema.RoundingModeType, + sema.RoundingModes, +) + +var interpreterRoundingModeConstructorValue, RoundingModeCaseValues = interpreterNativeEnumValueAndCaseValues( + roundingModeLookupType, + sema.RoundingModes, + NewRoundingModeCase, +) + +var InterpreterRoundingModeConstructor = StandardLibraryValue{ + Name: sema.RoundingModeTypeName, + Type: roundingModeLookupType, + Value: interpreterRoundingModeConstructorValue, + Kind: common.DeclarationKindEnum, +} + +var vmRoundingModeConstructorValue = vm.NewNativeFunctionValue( + sema.RoundingModeTypeName, + roundingModeLookupType, + func( + context interpreter.NativeFunctionContext, + _ interpreter.TypeArgumentsIterator, + _ interpreter.ArgumentTypesIterator, + _ interpreter.Value, + args []interpreter.Value, + ) interpreter.Value { + rawValue := args[0].(interpreter.UInt8Value) + + caseValue, ok := RoundingModeCaseValues[rawValue] + if !ok { + return interpreter.Nil + } + + return interpreter.NewSomeValueNonCopying(context, caseValue) + }, +) + +var VMRoundingModeConstructor = StandardLibraryValue{ + Name: sema.RoundingModeTypeName, + Type: roundingModeLookupType, + Value: vmRoundingModeConstructorValue, + Kind: common.DeclarationKindEnum, +} + +var VMRoundingModeCaseValues = func() []VMValue { + values := make([]VMValue, len(sema.RoundingModes)) + for i, roundingMode := range sema.RoundingModes { + rawValue := interpreter.UInt8Value(roundingMode.RawValue()) + values[i] = VMValue{ + Name: commons.TypeQualifiedName( + sema.RoundingModeType, + roundingMode.Name(), + ), + Value: RoundingModeCaseValues[rawValue], + } + } + return values +}() diff --git a/stdlib/signaturealgorithm.go b/stdlib/signaturealgorithm.go index 201afe59bf..e6ffa9752d 100644 --- a/stdlib/signaturealgorithm.go +++ b/stdlib/signaturealgorithm.go @@ -50,12 +50,12 @@ func NewSignatureAlgorithmCase(rawValue interpreter.UInt8Value) interpreter.Memb ) } -var signatureAlgorithmLookupType = cryptoAlgorithmEnumLookupType( +var signatureAlgorithmLookupType = nativeEnumLookupType( sema.SignatureAlgorithmType, sema.SignatureAlgorithms, ) -var interpreterSignatureAlgorithmConstructorValue, SignatureAlgorithmCaseValues = interpreterCryptoAlgorithmEnumValueAndCaseValues( +var interpreterSignatureAlgorithmConstructorValue, SignatureAlgorithmCaseValues = interpreterNativeEnumValueAndCaseValues( signatureAlgorithmLookupType, sema.SignatureAlgorithms, NewSignatureAlgorithmCase, diff --git a/test_utils/test_utils.go b/test_utils/test_utils.go index 7c0f0bec5d..5859c2d0a6 100644 --- a/test_utils/test_utils.go +++ b/test_utils/test_utils.go @@ -20,6 +20,7 @@ package test_utils import ( "fmt" + "slices" "strings" "testing" @@ -319,6 +320,29 @@ func ParseCheckAndPrepareWithOptions( // (i.e: only get the values that were added externally for tests) interpreterBaseActivationVariables := interpreterBaseActivation.ValuesInCurrentLevel() + // Collect nested variables (e.g. enum case values like "RoundingMode.towardZero") + // from HostFunctionValues, so they can be registered in both the VM and compiler activations. + type nestedVariableEntry struct { + qualifiedName string + value interpreter.Value + } + var nestedEntries []nestedVariableEntry + + for name, variable := range interpreterBaseActivationVariables { //nolint:maprange + value := variable.GetValue(nil) + if functionValue, ok := value.(*interpreter.HostFunctionValue); ok { + for nestedName, nestedVar := range functionValue.NestedVariables { //nolint:maprange + nestedEntries = append(nestedEntries, nestedVariableEntry{ + qualifiedName: name + "." + nestedName, + value: nestedVar.GetValue(nil), + }) + } + } + } + slices.SortFunc(nestedEntries, func(a, b nestedVariableEntry) int { + return strings.Compare(a.qualifiedName, b.qualifiedName) + }) + vmConfig.BuiltinGlobalsProvider = func(_ common.Location) *activations.Activation[vm.Variable] { activation := activations.NewActivation(nil, vm.DefaultBuiltinGlobals()) @@ -336,7 +360,7 @@ func ParseCheckAndPrepareWithOptions( value := variable.GetValue(nil) if functionValue, ok := value.(*interpreter.HostFunctionValue); ok { - value = vm.NewNativeFunctionValue( + nativeFn := vm.NewNativeFunctionValue( name, functionValue.Type, func( @@ -371,6 +395,13 @@ func ParseCheckAndPrepareWithOptions( }, ) + // Transfer nested variables (e.g. enum case values) + // from the interpreter's HostFunctionValue to the VM's NativeFunctionValue. + for nestedName, nestedVar := range functionValue.NestedVariables { //nolint:maprange + nativeFn.SetField(nestedName, nestedVar.GetValue(nil)) + } + + value = nativeFn } vmVariable := interpreter.NewVariableWithValue( @@ -381,6 +412,14 @@ func ParseCheckAndPrepareWithOptions( activation.Set(name, vmVariable) } + // Register nested variables as separate qualified globals. + for _, entry := range nestedEntries { + activation.Set( + entry.qualifiedName, + interpreter.NewVariableWithValue(nil, entry.value), + ) + } + return activation } @@ -399,6 +438,15 @@ func ParseCheckAndPrepareWithOptions( compiler.NewGlobalImport(name), ) } + + // Register nested variables as separate qualified compiler globals. + for _, entry := range nestedEntries { + activation.Set( + entry.qualifiedName, + compiler.NewGlobalImport(entry.qualifiedName), + ) + } + return activation }, } From 3024cfeddd4676a4bd7113d65343a6e898308945 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Mon, 20 Apr 2026 10:32:43 -0700 Subject: [PATCH 2/2] rename RoundingMode to RoundingRule --- interpreter/fixedpoint_test.go | 120 +++++++++--------- interpreter/interpreter.go | 4 +- interpreter/value_fix64.go | 6 +- interpreter/value_fixedpoint.go | 2 +- interpreter/value_ufix64.go | 6 +- runtime/account_test.go | 2 +- runtime/convertValues.go | 12 +- runtime/program_params_validation_test.go | 8 +- ...ing_mode_test.go => rounding_rule_test.go} | 60 ++++----- ...ing_mode_test.go => rounding_rule_test.go} | 32 ++--- ...ing_mode_type.go => rounding_rule_type.go} | 90 ++++++------- ...ype_test.go => rounding_rule_type_test.go} | 28 ++-- sema/type.go | 6 +- stdlib/builtin.go | 6 +- stdlib/{roundingmode.go => roundingrule.go} | 62 ++++----- test_utils/test_utils.go | 2 +- 16 files changed, 223 insertions(+), 223 deletions(-) rename runtime/{rounding_mode_test.go => rounding_rule_test.go} (75%) rename sema/{rounding_mode_test.go => rounding_rule_test.go} (86%) rename sema/{rounding_mode_type.go => rounding_rule_type.go} (60%) rename sema/{rounding_mode_type_test.go => rounding_rule_type_test.go} (62%) rename stdlib/{roundingmode.go => roundingrule.go} (58%) diff --git a/interpreter/fixedpoint_test.go b/interpreter/fixedpoint_test.go index 43de33ed64..bbbd1dea1a 100644 --- a/interpreter/fixedpoint_test.go +++ b/interpreter/fixedpoint_test.go @@ -1354,13 +1354,13 @@ func TestInterpretFixedPointLeastSignificantDecimalHandling(t *testing.T) { }) } -func parseCheckAndPrepareWithRoundingMode(t *testing.T, code string) Invokable { +func parseCheckAndPrepareWithRoundingRule(t *testing.T, code string) Invokable { t.Helper() baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) baseActivation := activations.NewActivation(nil, interpreter.BaseActivation) - valueDeclaration := stdlib.InterpreterRoundingModeConstructor + valueDeclaration := stdlib.InterpreterRoundingRuleConstructor baseValueActivation.DeclareValue(valueDeclaration) interpreter.Declare(baseActivation, valueDeclaration) @@ -1387,7 +1387,7 @@ func parseCheckAndPrepareWithRoundingMode(t *testing.T, code string) Invokable { return invokable } -func TestInterpretFix64WithRoundingMode(t *testing.T) { +func TestInterpretFix64WithRoundingRule(t *testing.T) { t.Parallel() // Fix128 has 24 decimal places, Fix64 has 8. @@ -1416,7 +1416,7 @@ func TestInterpretFix64WithRoundingMode(t *testing.T) { name: "towardZero, positive, non-halfway below", code: `fun main(): Fix64 { let x: Fix128 = 1.000000003000000000000000 - return Fix64(x, rounding: RoundingMode.towardZero) + return Fix64(x, rounding: RoundingRule.towardZero) }`, expected: interpreter.NewUnmeteredFix64Value(100000000), // 1.00000000 }, @@ -1424,7 +1424,7 @@ func TestInterpretFix64WithRoundingMode(t *testing.T) { name: "towardZero, positive, exact halfway", code: `fun main(): Fix64 { let x: Fix128 = 1.000000005000000000000000 - return Fix64(x, rounding: RoundingMode.towardZero) + return Fix64(x, rounding: RoundingRule.towardZero) }`, expected: interpreter.NewUnmeteredFix64Value(100000000), // 1.00000000 }, @@ -1432,7 +1432,7 @@ func TestInterpretFix64WithRoundingMode(t *testing.T) { name: "towardZero, positive, non-halfway above", code: `fun main(): Fix64 { let x: Fix128 = 1.000000007000000000000000 - return Fix64(x, rounding: RoundingMode.towardZero) + return Fix64(x, rounding: RoundingRule.towardZero) }`, expected: interpreter.NewUnmeteredFix64Value(100000000), // 1.00000000 }, @@ -1440,7 +1440,7 @@ func TestInterpretFix64WithRoundingMode(t *testing.T) { name: "towardZero, negative, non-halfway below", code: `fun main(): Fix64 { let x: Fix128 = -1.000000003000000000000000 - return Fix64(x, rounding: RoundingMode.towardZero) + return Fix64(x, rounding: RoundingRule.towardZero) }`, expected: interpreter.NewUnmeteredFix64Value(-100000000), // -1.00000000 }, @@ -1448,7 +1448,7 @@ func TestInterpretFix64WithRoundingMode(t *testing.T) { name: "towardZero, negative, exact halfway", code: `fun main(): Fix64 { let x: Fix128 = -1.000000005000000000000000 - return Fix64(x, rounding: RoundingMode.towardZero) + return Fix64(x, rounding: RoundingRule.towardZero) }`, expected: interpreter.NewUnmeteredFix64Value(-100000000), // -1.00000000 }, @@ -1456,7 +1456,7 @@ func TestInterpretFix64WithRoundingMode(t *testing.T) { name: "towardZero, negative, non-halfway above", code: `fun main(): Fix64 { let x: Fix128 = -1.000000007000000000000000 - return Fix64(x, rounding: RoundingMode.towardZero) + return Fix64(x, rounding: RoundingRule.towardZero) }`, expected: interpreter.NewUnmeteredFix64Value(-100000000), // -1.00000000 }, @@ -1466,7 +1466,7 @@ func TestInterpretFix64WithRoundingMode(t *testing.T) { name: "awayFromZero, positive, non-halfway below", code: `fun main(): Fix64 { let x: Fix128 = 1.000000003000000000000000 - return Fix64(x, rounding: RoundingMode.awayFromZero) + return Fix64(x, rounding: RoundingRule.awayFromZero) }`, expected: interpreter.NewUnmeteredFix64Value(100000001), // 1.00000001 }, @@ -1474,7 +1474,7 @@ func TestInterpretFix64WithRoundingMode(t *testing.T) { name: "awayFromZero, positive, exact halfway", code: `fun main(): Fix64 { let x: Fix128 = 1.000000005000000000000000 - return Fix64(x, rounding: RoundingMode.awayFromZero) + return Fix64(x, rounding: RoundingRule.awayFromZero) }`, expected: interpreter.NewUnmeteredFix64Value(100000001), // 1.00000001 }, @@ -1482,7 +1482,7 @@ func TestInterpretFix64WithRoundingMode(t *testing.T) { name: "awayFromZero, positive, non-halfway above", code: `fun main(): Fix64 { let x: Fix128 = 1.000000007000000000000000 - return Fix64(x, rounding: RoundingMode.awayFromZero) + return Fix64(x, rounding: RoundingRule.awayFromZero) }`, expected: interpreter.NewUnmeteredFix64Value(100000001), // 1.00000001 }, @@ -1490,7 +1490,7 @@ func TestInterpretFix64WithRoundingMode(t *testing.T) { name: "awayFromZero, negative, non-halfway below", code: `fun main(): Fix64 { let x: Fix128 = -1.000000003000000000000000 - return Fix64(x, rounding: RoundingMode.awayFromZero) + return Fix64(x, rounding: RoundingRule.awayFromZero) }`, expected: interpreter.NewUnmeteredFix64Value(-100000001), // -1.00000001 }, @@ -1498,7 +1498,7 @@ func TestInterpretFix64WithRoundingMode(t *testing.T) { name: "awayFromZero, negative, exact halfway", code: `fun main(): Fix64 { let x: Fix128 = -1.000000005000000000000000 - return Fix64(x, rounding: RoundingMode.awayFromZero) + return Fix64(x, rounding: RoundingRule.awayFromZero) }`, expected: interpreter.NewUnmeteredFix64Value(-100000001), // -1.00000001 }, @@ -1506,7 +1506,7 @@ func TestInterpretFix64WithRoundingMode(t *testing.T) { name: "awayFromZero, negative, non-halfway above", code: `fun main(): Fix64 { let x: Fix128 = -1.000000007000000000000000 - return Fix64(x, rounding: RoundingMode.awayFromZero) + return Fix64(x, rounding: RoundingRule.awayFromZero) }`, expected: interpreter.NewUnmeteredFix64Value(-100000001), // -1.00000001 }, @@ -1516,7 +1516,7 @@ func TestInterpretFix64WithRoundingMode(t *testing.T) { name: "nearestHalfAway, positive, non-halfway below", code: `fun main(): Fix64 { let x: Fix128 = 1.000000003000000000000000 - return Fix64(x, rounding: RoundingMode.nearestHalfAway) + return Fix64(x, rounding: RoundingRule.nearestHalfAway) }`, expected: interpreter.NewUnmeteredFix64Value(100000000), // 1.00000000 }, @@ -1524,7 +1524,7 @@ func TestInterpretFix64WithRoundingMode(t *testing.T) { name: "nearestHalfAway, positive, exact halfway", code: `fun main(): Fix64 { let x: Fix128 = 1.000000005000000000000000 - return Fix64(x, rounding: RoundingMode.nearestHalfAway) + return Fix64(x, rounding: RoundingRule.nearestHalfAway) }`, expected: interpreter.NewUnmeteredFix64Value(100000001), // 1.00000001 (tie → away) }, @@ -1532,7 +1532,7 @@ func TestInterpretFix64WithRoundingMode(t *testing.T) { name: "nearestHalfAway, positive, non-halfway above", code: `fun main(): Fix64 { let x: Fix128 = 1.000000007000000000000000 - return Fix64(x, rounding: RoundingMode.nearestHalfAway) + return Fix64(x, rounding: RoundingRule.nearestHalfAway) }`, expected: interpreter.NewUnmeteredFix64Value(100000001), // 1.00000001 }, @@ -1540,7 +1540,7 @@ func TestInterpretFix64WithRoundingMode(t *testing.T) { name: "nearestHalfAway, negative, exact halfway", code: `fun main(): Fix64 { let x: Fix128 = -1.000000005000000000000000 - return Fix64(x, rounding: RoundingMode.nearestHalfAway) + return Fix64(x, rounding: RoundingRule.nearestHalfAway) }`, expected: interpreter.NewUnmeteredFix64Value(-100000001), // -1.00000001 (tie → away) }, @@ -1550,7 +1550,7 @@ func TestInterpretFix64WithRoundingMode(t *testing.T) { name: "nearestHalfEven, positive, non-halfway below", code: `fun main(): Fix64 { let x: Fix128 = 1.000000003000000000000000 - return Fix64(x, rounding: RoundingMode.nearestHalfEven) + return Fix64(x, rounding: RoundingRule.nearestHalfEven) }`, expected: interpreter.NewUnmeteredFix64Value(100000000), // 1.00000000 }, @@ -1558,7 +1558,7 @@ func TestInterpretFix64WithRoundingMode(t *testing.T) { name: "nearestHalfEven, positive, exact halfway (last digit 0 is even)", code: `fun main(): Fix64 { let x: Fix128 = 1.000000005000000000000000 - return Fix64(x, rounding: RoundingMode.nearestHalfEven) + return Fix64(x, rounding: RoundingRule.nearestHalfEven) }`, expected: interpreter.NewUnmeteredFix64Value(100000000), // 1.00000000 (tie → even, 0 is even) }, @@ -1566,7 +1566,7 @@ func TestInterpretFix64WithRoundingMode(t *testing.T) { name: "nearestHalfEven, positive, non-halfway above", code: `fun main(): Fix64 { let x: Fix128 = 1.000000007000000000000000 - return Fix64(x, rounding: RoundingMode.nearestHalfEven) + return Fix64(x, rounding: RoundingRule.nearestHalfEven) }`, expected: interpreter.NewUnmeteredFix64Value(100000001), // 1.00000001 }, @@ -1574,7 +1574,7 @@ func TestInterpretFix64WithRoundingMode(t *testing.T) { name: "nearestHalfEven, positive, exact halfway (last digit 1 is odd)", code: `fun main(): Fix64 { let x: Fix128 = 1.000000015000000000000000 - return Fix64(x, rounding: RoundingMode.nearestHalfEven) + return Fix64(x, rounding: RoundingRule.nearestHalfEven) }`, expected: interpreter.NewUnmeteredFix64Value(100000002), // 1.00000002 (tie → even, 2 is even) }, @@ -1582,7 +1582,7 @@ func TestInterpretFix64WithRoundingMode(t *testing.T) { name: "nearestHalfEven, negative, exact halfway", code: `fun main(): Fix64 { let x: Fix128 = -1.000000005000000000000000 - return Fix64(x, rounding: RoundingMode.nearestHalfEven) + return Fix64(x, rounding: RoundingRule.nearestHalfEven) }`, expected: interpreter.NewUnmeteredFix64Value(-100000000), // -1.00000000 (tie → even) }, @@ -1592,7 +1592,7 @@ func TestInterpretFix64WithRoundingMode(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() - invokable := parseCheckAndPrepareWithRoundingMode(t, tc.code) + invokable := parseCheckAndPrepareWithRoundingRule(t, tc.code) result, err := invokable.Invoke("main") require.NoError(t, err) assert.Equal(t, tc.expected, result) @@ -1617,9 +1617,9 @@ func TestInterpretFix64WithRoundingMode(t *testing.T) { t.Run("integer conversion ignores rounding", func(t *testing.T) { t.Parallel() - invokable := parseCheckAndPrepareWithRoundingMode(t, ` + invokable := parseCheckAndPrepareWithRoundingRule(t, ` fun main(): Fix64 { - return Fix64(42, rounding: RoundingMode.awayFromZero) + return Fix64(42, rounding: RoundingRule.awayFromZero) } `) @@ -1629,7 +1629,7 @@ func TestInterpretFix64WithRoundingMode(t *testing.T) { }) } -func TestInterpretUFix64WithRoundingMode(t *testing.T) { +func TestInterpretUFix64WithRoundingRule(t *testing.T) { t.Parallel() type testCase struct { @@ -1644,7 +1644,7 @@ func TestInterpretUFix64WithRoundingMode(t *testing.T) { name: "towardZero, non-halfway below", code: `fun main(): UFix64 { let x: UFix128 = 1.000000003000000000000000 - return UFix64(x, rounding: RoundingMode.towardZero) + return UFix64(x, rounding: RoundingRule.towardZero) }`, expected: interpreter.NewUnmeteredUFix64Value(100000000), }, @@ -1652,7 +1652,7 @@ func TestInterpretUFix64WithRoundingMode(t *testing.T) { name: "towardZero, exact halfway", code: `fun main(): UFix64 { let x: UFix128 = 1.000000005000000000000000 - return UFix64(x, rounding: RoundingMode.towardZero) + return UFix64(x, rounding: RoundingRule.towardZero) }`, expected: interpreter.NewUnmeteredUFix64Value(100000000), }, @@ -1660,7 +1660,7 @@ func TestInterpretUFix64WithRoundingMode(t *testing.T) { name: "towardZero, non-halfway above", code: `fun main(): UFix64 { let x: UFix128 = 1.000000007000000000000000 - return UFix64(x, rounding: RoundingMode.towardZero) + return UFix64(x, rounding: RoundingRule.towardZero) }`, expected: interpreter.NewUnmeteredUFix64Value(100000000), }, @@ -1670,7 +1670,7 @@ func TestInterpretUFix64WithRoundingMode(t *testing.T) { name: "awayFromZero, non-halfway below", code: `fun main(): UFix64 { let x: UFix128 = 1.000000003000000000000000 - return UFix64(x, rounding: RoundingMode.awayFromZero) + return UFix64(x, rounding: RoundingRule.awayFromZero) }`, expected: interpreter.NewUnmeteredUFix64Value(100000001), }, @@ -1678,7 +1678,7 @@ func TestInterpretUFix64WithRoundingMode(t *testing.T) { name: "awayFromZero, exact halfway", code: `fun main(): UFix64 { let x: UFix128 = 1.000000005000000000000000 - return UFix64(x, rounding: RoundingMode.awayFromZero) + return UFix64(x, rounding: RoundingRule.awayFromZero) }`, expected: interpreter.NewUnmeteredUFix64Value(100000001), }, @@ -1686,7 +1686,7 @@ func TestInterpretUFix64WithRoundingMode(t *testing.T) { name: "awayFromZero, non-halfway above", code: `fun main(): UFix64 { let x: UFix128 = 1.000000007000000000000000 - return UFix64(x, rounding: RoundingMode.awayFromZero) + return UFix64(x, rounding: RoundingRule.awayFromZero) }`, expected: interpreter.NewUnmeteredUFix64Value(100000001), }, @@ -1696,7 +1696,7 @@ func TestInterpretUFix64WithRoundingMode(t *testing.T) { name: "nearestHalfAway, non-halfway below", code: `fun main(): UFix64 { let x: UFix128 = 1.000000003000000000000000 - return UFix64(x, rounding: RoundingMode.nearestHalfAway) + return UFix64(x, rounding: RoundingRule.nearestHalfAway) }`, expected: interpreter.NewUnmeteredUFix64Value(100000000), }, @@ -1704,7 +1704,7 @@ func TestInterpretUFix64WithRoundingMode(t *testing.T) { name: "nearestHalfAway, exact halfway", code: `fun main(): UFix64 { let x: UFix128 = 1.000000005000000000000000 - return UFix64(x, rounding: RoundingMode.nearestHalfAway) + return UFix64(x, rounding: RoundingRule.nearestHalfAway) }`, expected: interpreter.NewUnmeteredUFix64Value(100000001), // tie → away }, @@ -1712,7 +1712,7 @@ func TestInterpretUFix64WithRoundingMode(t *testing.T) { name: "nearestHalfAway, non-halfway above", code: `fun main(): UFix64 { let x: UFix128 = 1.000000007000000000000000 - return UFix64(x, rounding: RoundingMode.nearestHalfAway) + return UFix64(x, rounding: RoundingRule.nearestHalfAway) }`, expected: interpreter.NewUnmeteredUFix64Value(100000001), }, @@ -1722,7 +1722,7 @@ func TestInterpretUFix64WithRoundingMode(t *testing.T) { name: "nearestHalfEven, non-halfway below", code: `fun main(): UFix64 { let x: UFix128 = 1.000000003000000000000000 - return UFix64(x, rounding: RoundingMode.nearestHalfEven) + return UFix64(x, rounding: RoundingRule.nearestHalfEven) }`, expected: interpreter.NewUnmeteredUFix64Value(100000000), }, @@ -1730,7 +1730,7 @@ func TestInterpretUFix64WithRoundingMode(t *testing.T) { name: "nearestHalfEven, exact halfway (last digit 0 is even)", code: `fun main(): UFix64 { let x: UFix128 = 1.000000005000000000000000 - return UFix64(x, rounding: RoundingMode.nearestHalfEven) + return UFix64(x, rounding: RoundingRule.nearestHalfEven) }`, expected: interpreter.NewUnmeteredUFix64Value(100000000), // tie → even, 0 is even }, @@ -1738,7 +1738,7 @@ func TestInterpretUFix64WithRoundingMode(t *testing.T) { name: "nearestHalfEven, non-halfway above", code: `fun main(): UFix64 { let x: UFix128 = 1.000000007000000000000000 - return UFix64(x, rounding: RoundingMode.nearestHalfEven) + return UFix64(x, rounding: RoundingRule.nearestHalfEven) }`, expected: interpreter.NewUnmeteredUFix64Value(100000001), }, @@ -1746,7 +1746,7 @@ func TestInterpretUFix64WithRoundingMode(t *testing.T) { name: "nearestHalfEven, exact halfway (last digit 1 is odd)", code: `fun main(): UFix64 { let x: UFix128 = 1.000000015000000000000000 - return UFix64(x, rounding: RoundingMode.nearestHalfEven) + return UFix64(x, rounding: RoundingRule.nearestHalfEven) }`, expected: interpreter.NewUnmeteredUFix64Value(100000002), // tie → even, 2 is even }, @@ -1756,7 +1756,7 @@ func TestInterpretUFix64WithRoundingMode(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() - invokable := parseCheckAndPrepareWithRoundingMode(t, tc.code) + invokable := parseCheckAndPrepareWithRoundingRule(t, tc.code) result, err := invokable.Invoke("main") require.NoError(t, err) assert.Equal(t, tc.expected, result) @@ -1779,17 +1779,17 @@ func TestInterpretUFix64WithRoundingMode(t *testing.T) { }) } -func TestInterpretFix64WithRoundingModeOverflow(t *testing.T) { +func TestInterpretFix64WithRoundingRuleOverflow(t *testing.T) { t.Parallel() t.Run("Fix128 overflow", func(t *testing.T) { t.Parallel() - invokable := parseCheckAndPrepareWithRoundingMode(t, ` + invokable := parseCheckAndPrepareWithRoundingRule(t, ` fun main(): Fix64 { // Fix128 max is much larger than Fix64 max let x: Fix128 = Fix128.max - return Fix64(x, rounding: RoundingMode.towardZero) + return Fix64(x, rounding: RoundingRule.towardZero) } `) @@ -1802,10 +1802,10 @@ func TestInterpretFix64WithRoundingModeOverflow(t *testing.T) { t.Run("Fix128 negative overflow", func(t *testing.T) { t.Parallel() - invokable := parseCheckAndPrepareWithRoundingMode(t, ` + invokable := parseCheckAndPrepareWithRoundingRule(t, ` fun main(): Fix64 { let x: Fix128 = Fix128.min - return Fix64(x, rounding: RoundingMode.towardZero) + return Fix64(x, rounding: RoundingRule.towardZero) } `) @@ -1818,11 +1818,11 @@ func TestInterpretFix64WithRoundingModeOverflow(t *testing.T) { t.Run("rounding causes overflow", func(t *testing.T) { t.Parallel() - invokable := parseCheckAndPrepareWithRoundingMode(t, ` + invokable := parseCheckAndPrepareWithRoundingRule(t, ` fun main(): Fix64 { // Fix64.max as Fix128, plus a fraction that would round up let x: Fix128 = 92233720368.547758079999999999999999 - return Fix64(x, rounding: RoundingMode.awayFromZero) + return Fix64(x, rounding: RoundingRule.awayFromZero) } `) @@ -1833,16 +1833,16 @@ func TestInterpretFix64WithRoundingModeOverflow(t *testing.T) { }) } -func TestInterpretUFix64WithRoundingModeOverflow(t *testing.T) { +func TestInterpretUFix64WithRoundingRuleOverflow(t *testing.T) { t.Parallel() t.Run("UFix128 overflow", func(t *testing.T) { t.Parallel() - invokable := parseCheckAndPrepareWithRoundingMode(t, ` + invokable := parseCheckAndPrepareWithRoundingRule(t, ` fun main(): UFix64 { let x: UFix128 = UFix128.max - return UFix64(x, rounding: RoundingMode.towardZero) + return UFix64(x, rounding: RoundingRule.towardZero) } `) @@ -1855,10 +1855,10 @@ func TestInterpretUFix64WithRoundingModeOverflow(t *testing.T) { t.Run("Fix128 negative to UFix64", func(t *testing.T) { t.Parallel() - invokable := parseCheckAndPrepareWithRoundingMode(t, ` + invokable := parseCheckAndPrepareWithRoundingRule(t, ` fun main(): UFix64 { let x: Fix128 = -1.0 - return UFix64(x, rounding: RoundingMode.towardZero) + return UFix64(x, rounding: RoundingRule.towardZero) } `) @@ -1869,16 +1869,16 @@ func TestInterpretUFix64WithRoundingModeOverflow(t *testing.T) { }) } -func TestInterpretRoundingModeEnum(t *testing.T) { +func TestInterpretRoundingRuleEnum(t *testing.T) { t.Parallel() - invokable := parseCheckAndPrepareWithRoundingMode(t, ` + invokable := parseCheckAndPrepareWithRoundingRule(t, ` fun main(): [UInt8] { return [ - RoundingMode.towardZero.rawValue, - RoundingMode.awayFromZero.rawValue, - RoundingMode.nearestHalfAway.rawValue, - RoundingMode.nearestHalfEven.rawValue + RoundingRule.towardZero.rawValue, + RoundingRule.awayFromZero.rawValue, + RoundingRule.nearestHalfAway.rawValue, + RoundingRule.nearestHalfEven.rawValue ] } `) diff --git a/interpreter/interpreter.go b/interpreter/interpreter.go index 95ee7707f3..c7a481b748 100644 --- a/interpreter/interpreter.go +++ b/interpreter/interpreter.go @@ -4463,8 +4463,8 @@ func NativeConverterFunctionWithRounding( args []Value, ) Value { if len(args) > 1 { - roundingMode := extractRoundingMode(args[1]) - return convertWithRounding(context, args[0], roundingMode) + roundingRule := extractRoundingRule(args[1]) + return convertWithRounding(context, args[0], roundingRule) } return convert(context, args[0]) } diff --git a/interpreter/value_fix64.go b/interpreter/value_fix64.go index 98f692db7a..08a4da79c1 100644 --- a/interpreter/value_fix64.go +++ b/interpreter/value_fix64.go @@ -516,13 +516,13 @@ func ConvertFix64(memoryGauge common.MemoryGauge, value Value) Fix64Value { } } -func ConvertFix64WithRounding(memoryGauge common.MemoryGauge, value Value, roundingMode fix.RoundingMode) Fix64Value { +func ConvertFix64WithRounding(memoryGauge common.MemoryGauge, value Value, roundingRule fix.RoundingMode) Fix64Value { switch value := value.(type) { case Fix128Value: return NewFix64Value( memoryGauge, func() int64 { - result, err := fix.Fix128(value).ToFix64(roundingMode) + result, err := fix.Fix128(value).ToFix64(roundingRule) if err != nil { handleFixedPointConversionError(err) } @@ -534,7 +534,7 @@ func ConvertFix64WithRounding(memoryGauge common.MemoryGauge, value Value, round return NewFix64Value( memoryGauge, func() int64 { - result, err := fix.UFix128(value).ToUFix64(roundingMode) + result, err := fix.UFix128(value).ToUFix64(roundingRule) if err != nil { handleFixedPointConversionError(err) } diff --git a/interpreter/value_fixedpoint.go b/interpreter/value_fixedpoint.go index be97dcd3b1..a95ed3f6f7 100644 --- a/interpreter/value_fixedpoint.go +++ b/interpreter/value_fixedpoint.go @@ -25,7 +25,7 @@ import ( "github.com/onflow/cadence/sema" ) -func extractRoundingMode(value Value) fix.RoundingMode { +func extractRoundingRule(value Value) fix.RoundingMode { composite, ok := value.(*SimpleCompositeValue) if !ok { panic(errors.NewUnreachableError()) diff --git a/interpreter/value_ufix64.go b/interpreter/value_ufix64.go index d739ffe39a..a9908b6d6e 100644 --- a/interpreter/value_ufix64.go +++ b/interpreter/value_ufix64.go @@ -169,13 +169,13 @@ func ConvertUFix64(memoryGauge common.MemoryGauge, value Value) UFix64Value { } } -func ConvertUFix64WithRounding(memoryGauge common.MemoryGauge, value Value, roundingMode fix.RoundingMode) UFix64Value { +func ConvertUFix64WithRounding(memoryGauge common.MemoryGauge, value Value, roundingRule fix.RoundingMode) UFix64Value { switch value := value.(type) { case UFix128Value: return NewUFix64Value( memoryGauge, func() uint64 { - result, err := fix.UFix128(value).ToUFix64(roundingMode) + result, err := fix.UFix128(value).ToUFix64(roundingRule) if err != nil { handleFixedPointConversionError(err) } @@ -192,7 +192,7 @@ func ConvertUFix64WithRounding(memoryGauge common.MemoryGauge, value Value, roun panic(&UnderflowError{}) } // A non-negative Fix128 has the same bit representation as UFix128 - result, err := fix.UFix128(fix128).ToUFix64(roundingMode) + result, err := fix.UFix128(fix128).ToUFix64(roundingRule) if err != nil { handleFixedPointConversionError(err) } diff --git a/runtime/account_test.go b/runtime/account_test.go index 22a3a28b2a..0b00ee6864 100644 --- a/runtime/account_test.go +++ b/runtime/account_test.go @@ -1223,7 +1223,7 @@ var AccountKeyType = ExportedBuiltinType(sema.AccountKeyType).(*cadence.StructTy var PublicKeyType = ExportedBuiltinType(sema.PublicKeyType).(*cadence.StructType) var SignAlgoType = ExportedBuiltinType(sema.SignatureAlgorithmType).(*cadence.EnumType) var HashAlgoType = ExportedBuiltinType(sema.HashAlgorithmType).(*cadence.EnumType) -var RoundingModeEnumType = ExportedBuiltinType(sema.RoundingModeType).(*cadence.EnumType) +var RoundingRuleEnumType = ExportedBuiltinType(sema.RoundingRuleType).(*cadence.EnumType) func ExportedBuiltinType(internalType sema.Type) cadence.Type { return ExportType(internalType, map[sema.TypeID]cadence.Type{}) diff --git a/runtime/convertValues.go b/runtime/convertValues.go index e12ed867e5..fa7ad08be9 100644 --- a/runtime/convertValues.go +++ b/runtime/convertValues.go @@ -1582,8 +1582,8 @@ func (i valueImporter) importCompositeValue( // (e.g. it has host functions) return i.importSignatureAlgorithm(fields) - case sema.RoundingModeType: - return i.importRoundingMode(fields) + case sema.RoundingRuleType: + return i.importRoundingRule(fields) default: return nil, errors.NewDefaultUserError( @@ -1775,7 +1775,7 @@ func (valueImporter) importSignatureAlgorithm( return caseValue, nil } -func (valueImporter) importRoundingMode( +func (valueImporter) importRoundingRule( fields []interpreter.CompositeField, ) ( interpreter.MemberAccessibleValue, @@ -1785,7 +1785,7 @@ func (valueImporter) importRoundingMode( var foundRawValue bool var rawValue interpreter.UInt8Value - ty := sema.RoundingModeType + ty := sema.RoundingRuleType for _, field := range fields { switch field.Name { @@ -1817,10 +1817,10 @@ func (valueImporter) importRoundingMode( ) } - caseValue, ok := stdlib.RoundingModeCaseValues[rawValue] + caseValue, ok := stdlib.RoundingRuleCaseValues[rawValue] if !ok { return nil, errors.NewDefaultUserError( - "unknown RoundingMode with rawValue %d", + "unknown RoundingRule with rawValue %d", rawValue, ) } diff --git a/runtime/program_params_validation_test.go b/runtime/program_params_validation_test.go index d8ec5faa5c..4e5d98f0df 100644 --- a/runtime/program_params_validation_test.go +++ b/runtime/program_params_validation_test.go @@ -474,12 +474,12 @@ func TestRuntimeScriptParameterTypeValidation(t *testing.T) { }, ).WithType(SignAlgoType) - case sema.RoundingModeType: + case sema.RoundingRuleType: value = cadence.NewEnum( []cadence.Value{ cadence.NewUInt8(0), }, - ).WithType(RoundingModeEnumType) + ).WithType(RoundingRuleEnumType) case sema.PublicKeyType: value = cadence.NewStruct( @@ -1073,12 +1073,12 @@ func TestRuntimeTransactionParameterTypeValidation(t *testing.T) { }, ).WithType(SignAlgoType) - case sema.RoundingModeType: + case sema.RoundingRuleType: value = cadence.NewEnum( []cadence.Value{ cadence.NewUInt8(0), }, - ).WithType(RoundingModeEnumType) + ).WithType(RoundingRuleEnumType) case sema.PublicKeyType: value = cadence.NewStruct( diff --git a/runtime/rounding_mode_test.go b/runtime/rounding_rule_test.go similarity index 75% rename from runtime/rounding_mode_test.go rename to runtime/rounding_rule_test.go index 3b32a9ac37..c14d8876d7 100644 --- a/runtime/rounding_mode_test.go +++ b/runtime/rounding_rule_test.go @@ -32,12 +32,12 @@ import ( . "github.com/onflow/cadence/test_utils/runtime_utils" ) -func newRoundingModeArgument(rawValue uint8) cadence.Value { +func newRoundingRuleArgument(rawValue uint8) cadence.Value { return cadence.NewEnum([]cadence.Value{ cadence.UInt8(rawValue), }).WithType(cadence.NewEnumType( nil, - sema.RoundingModeTypeName, + sema.RoundingRuleTypeName, cadence.UInt8Type, []cadence.Field{ { @@ -49,7 +49,7 @@ func newRoundingModeArgument(rawValue uint8) cadence.Value { )) } -func TestRuntimeRoundingModeExport(t *testing.T) { +func TestRuntimeRoundingRuleExport(t *testing.T) { t.Parallel() @@ -57,13 +57,13 @@ func TestRuntimeRoundingModeExport(t *testing.T) { runtimeInterface := &TestRuntimeInterface{} nextScriptLocation := NewScriptLocationGenerator() - testRoundingMode := func(mode sema.NativeEnumCase) { + testRoundingRule := func(rule sema.NativeEnumCase) { script := fmt.Sprintf(` - access(all) fun main(): RoundingMode { - return RoundingMode.%s + access(all) fun main(): RoundingRule { + return RoundingRule.%s } `, - mode.Name(), + rule.Name(), ) value, err := runtime.ExecuteScript( @@ -85,17 +85,17 @@ func TestRuntimeRoundingModeExport(t *testing.T) { fields := cadence.FieldsMappedByName(enumValue) require.Len(t, fields, 1) assert.Equal(t, - cadence.NewUInt8(mode.RawValue()), + cadence.NewUInt8(rule.RawValue()), fields[sema.EnumRawValueFieldName], ) } - for _, mode := range sema.RoundingModes { - testRoundingMode(mode) + for _, rule := range sema.RoundingRules { + testRoundingRule(rule) } } -func TestRuntimeRoundingModeImport(t *testing.T) { +func TestRuntimeRoundingRuleImport(t *testing.T) { t.Parallel() @@ -108,18 +108,18 @@ func TestRuntimeRoundingModeImport(t *testing.T) { nextScriptLocation := NewScriptLocationGenerator() const script = ` - access(all) fun main(mode: RoundingMode): UInt8 { - return mode.rawValue + access(all) fun main(rule: RoundingRule): UInt8 { + return rule.rawValue } ` - testRoundingMode := func(mode sema.NativeEnumCase) { + testRoundingRule := func(rule sema.NativeEnumCase) { value, err := runtime.ExecuteScript( Script{ Source: []byte(script), Arguments: encodeArgs([]cadence.Value{ - newRoundingModeArgument(mode.RawValue()), + newRoundingRuleArgument(rule.RawValue()), }), }, Context{ @@ -130,15 +130,15 @@ func TestRuntimeRoundingModeImport(t *testing.T) { ) require.NoError(t, err) - assert.Equal(t, cadence.UInt8(mode.RawValue()), value) + assert.Equal(t, cadence.UInt8(rule.RawValue()), value) } - for _, mode := range sema.RoundingModes { - testRoundingMode(mode) + for _, rule := range sema.RoundingRules { + testRoundingRule(rule) } } -func TestRuntimeRoundingModeImportInvalid(t *testing.T) { +func TestRuntimeRoundingRuleImportInvalid(t *testing.T) { t.Parallel() @@ -151,8 +151,8 @@ func TestRuntimeRoundingModeImportInvalid(t *testing.T) { nextScriptLocation := NewScriptLocationGenerator() const script = ` - access(all) fun main(mode: RoundingMode): UInt8 { - return mode.rawValue + access(all) fun main(rule: RoundingRule): UInt8 { + return rule.rawValue } ` @@ -160,7 +160,7 @@ func TestRuntimeRoundingModeImportInvalid(t *testing.T) { Script{ Source: []byte(script), Arguments: encodeArgs([]cadence.Value{ - newRoundingModeArgument(99), // invalid raw value + newRoundingRuleArgument(99), // invalid raw value }), }, Context{ @@ -171,10 +171,10 @@ func TestRuntimeRoundingModeImportInvalid(t *testing.T) { ) require.Error(t, err) - assert.Contains(t, err.Error(), "unknown RoundingMode") + assert.Contains(t, err.Error(), "unknown RoundingRule") } -func TestRuntimeFix64ConversionWithRoundingModeArgument(t *testing.T) { +func TestRuntimeFix64ConversionWithRoundingRuleArgument(t *testing.T) { t.Parallel() @@ -190,9 +190,9 @@ func TestRuntimeFix64ConversionWithRoundingModeArgument(t *testing.T) { t.Parallel() const script = ` - access(all) fun main(mode: RoundingMode): Fix64 { + access(all) fun main(rule: RoundingRule): Fix64 { let x: Fix128 = 1.000000005000000000000000 - return Fix64(x, rounding: mode) + return Fix64(x, rounding: rule) } ` @@ -200,7 +200,7 @@ func TestRuntimeFix64ConversionWithRoundingModeArgument(t *testing.T) { value, err := runtime.ExecuteScript( Script{ Source: []byte(script), - Arguments: encodeArgs([]cadence.Value{newRoundingModeArgument(0)}), + Arguments: encodeArgs([]cadence.Value{newRoundingRuleArgument(0)}), }, Context{ Interface: runtimeInterface, @@ -217,9 +217,9 @@ func TestRuntimeFix64ConversionWithRoundingModeArgument(t *testing.T) { t.Parallel() const script = ` - access(all) fun main(mode: RoundingMode): UFix64 { + access(all) fun main(rule: RoundingRule): UFix64 { let x: UFix128 = 1.000000005000000000000000 - return UFix64(x, rounding: mode) + return UFix64(x, rounding: rule) } ` @@ -227,7 +227,7 @@ func TestRuntimeFix64ConversionWithRoundingModeArgument(t *testing.T) { value, err := runtime.ExecuteScript( Script{ Source: []byte(script), - Arguments: encodeArgs([]cadence.Value{newRoundingModeArgument(1)}), + Arguments: encodeArgs([]cadence.Value{newRoundingRuleArgument(1)}), }, Context{ Interface: runtimeInterface, diff --git a/sema/rounding_mode_test.go b/sema/rounding_rule_test.go similarity index 86% rename from sema/rounding_mode_test.go rename to sema/rounding_rule_test.go index 39dab7c8fe..40c2fc769e 100644 --- a/sema/rounding_mode_test.go +++ b/sema/rounding_rule_test.go @@ -30,7 +30,7 @@ import ( . "github.com/onflow/cadence/test_utils/sema_utils" ) -func TestCheckRoundingModeCases(t *testing.T) { +func TestCheckRoundingRuleCases(t *testing.T) { t.Parallel() @@ -39,14 +39,14 @@ func TestCheckRoundingModeCases(t *testing.T) { baseValueActivation.DeclareValue(value) } - test := func(mode sema.NativeEnumCase) { + test := func(rule sema.NativeEnumCase) { _, err := ParseAndCheckWithOptions(t, fmt.Sprintf( ` - let mode: RoundingMode = RoundingMode.%s + let rule: RoundingRule = RoundingRule.%s `, - mode.Name(), + rule.Name(), ), ParseAndCheckOptions{ CheckerConfig: &sema.Config{ @@ -60,21 +60,21 @@ func TestCheckRoundingModeCases(t *testing.T) { require.NoError(t, err) } - for _, mode := range sema.RoundingModes { - test(mode) + for _, rule := range sema.RoundingRules { + test(rule) } } -func TestCheckRoundingModeConstructor(t *testing.T) { +func TestCheckRoundingRuleConstructor(t *testing.T) { t.Parallel() baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) - baseValueActivation.DeclareValue(stdlib.InterpreterRoundingModeConstructor) + baseValueActivation.DeclareValue(stdlib.InterpreterRoundingRuleConstructor) _, err := ParseAndCheckWithOptions(t, ` - let mode = RoundingMode(rawValue: 0) + let rule = RoundingRule(rawValue: 0) `, ParseAndCheckOptions{ CheckerConfig: &sema.Config{ @@ -88,7 +88,7 @@ func TestCheckRoundingModeConstructor(t *testing.T) { require.NoError(t, err) } -func TestCheckRoundingModeRawValue(t *testing.T) { +func TestCheckRoundingRuleRawValue(t *testing.T) { t.Parallel() @@ -99,8 +99,8 @@ func TestCheckRoundingModeRawValue(t *testing.T) { _, err := ParseAndCheckWithOptions(t, ` - let mode = RoundingMode.towardZero - let rawValue: UInt8 = mode.rawValue + let rule = RoundingRule.towardZero + let rawValue: UInt8 = rule.rawValue `, ParseAndCheckOptions{ CheckerConfig: &sema.Config{ @@ -114,7 +114,7 @@ func TestCheckRoundingModeRawValue(t *testing.T) { require.NoError(t, err) } -func TestCheckFix64WithRoundingMode(t *testing.T) { +func TestCheckFix64WithRoundingRule(t *testing.T) { t.Parallel() @@ -128,7 +128,7 @@ func TestCheckFix64WithRoundingMode(t *testing.T) { _, err := ParseAndCheckWithOptions(t, ` - let x: Fix64 = Fix64(1, rounding: RoundingMode.towardZero) + let x: Fix64 = Fix64(1, rounding: RoundingRule.towardZero) `, ParseAndCheckOptions{ CheckerConfig: &sema.Config{ @@ -181,7 +181,7 @@ func TestCheckFix64WithRoundingMode(t *testing.T) { }) } -func TestCheckUFix64WithRoundingMode(t *testing.T) { +func TestCheckUFix64WithRoundingRule(t *testing.T) { t.Parallel() @@ -195,7 +195,7 @@ func TestCheckUFix64WithRoundingMode(t *testing.T) { _, err := ParseAndCheckWithOptions(t, ` - let x: UFix64 = UFix64(1, rounding: RoundingMode.nearestHalfEven) + let x: UFix64 = UFix64(1, rounding: RoundingRule.nearestHalfEven) `, ParseAndCheckOptions{ CheckerConfig: &sema.Config{ diff --git a/sema/rounding_mode_type.go b/sema/rounding_rule_type.go similarity index 60% rename from sema/rounding_mode_type.go rename to sema/rounding_rule_type.go index 294e8c54f8..b5cb88fae7 100644 --- a/sema/rounding_mode_type.go +++ b/sema/rounding_rule_type.go @@ -20,89 +20,89 @@ package sema import "github.com/onflow/cadence/errors" -const RoundingModeTypeName = "RoundingMode" +const RoundingRuleTypeName = "RoundingRule" -var RoundingModeType = newNativeEnumType( - RoundingModeTypeName, +var RoundingRuleType = newNativeEnumType( + RoundingRuleTypeName, UInt8Type, nil, ) -var RoundingModeTypeAnnotation = NewTypeAnnotation(RoundingModeType) +var RoundingRuleTypeAnnotation = NewTypeAnnotation(RoundingRuleType) -type RoundingMode uint8 +type RoundingRule uint8 -// NOTE: only add new modes, do *NOT* change existing items, +// NOTE: only add new rules, do *NOT* change existing items, // reuse raw values for other items, swap the order, etc. // // # Existing stored values use these raw values and should not change // -// IMPORTANT: update RoundingModes +// IMPORTANT: update RoundingRules const ( - RoundingModeTowardZero RoundingMode = iota - RoundingModeAwayFromZero - RoundingModeNearestHalfAway - RoundingModeNearestHalfEven + RoundingRuleTowardZero RoundingRule = iota + RoundingRuleAwayFromZero + RoundingRuleNearestHalfAway + RoundingRuleNearestHalfEven // !!! *WARNING* !!! - // ADD NEW MODES *BEFORE* THIS WARNING. - // DO *NOT* ADD NEW MODES AFTER THIS LINE! - RoundingMode_Count + // ADD NEW RULES *BEFORE* THIS WARNING. + // DO *NOT* ADD NEW RULES AFTER THIS LINE! + RoundingRule_Count ) -var RoundingModes = []RoundingMode{ - RoundingModeTowardZero, - RoundingModeAwayFromZero, - RoundingModeNearestHalfAway, - RoundingModeNearestHalfEven, +var RoundingRules = []RoundingRule{ + RoundingRuleTowardZero, + RoundingRuleAwayFromZero, + RoundingRuleNearestHalfAway, + RoundingRuleNearestHalfEven, } -func (mode RoundingMode) Name() string { - switch mode { - case RoundingModeTowardZero: +func (rule RoundingRule) Name() string { + switch rule { + case RoundingRuleTowardZero: return "towardZero" - case RoundingModeAwayFromZero: + case RoundingRuleAwayFromZero: return "awayFromZero" - case RoundingModeNearestHalfAway: + case RoundingRuleNearestHalfAway: return "nearestHalfAway" - case RoundingModeNearestHalfEven: + case RoundingRuleNearestHalfEven: return "nearestHalfEven" } panic(errors.NewUnreachableError()) } -func (mode RoundingMode) RawValue() uint8 { - switch mode { - case RoundingModeTowardZero: +func (rule RoundingRule) RawValue() uint8 { + switch rule { + case RoundingRuleTowardZero: return 0 - case RoundingModeAwayFromZero: + case RoundingRuleAwayFromZero: return 1 - case RoundingModeNearestHalfAway: + case RoundingRuleNearestHalfAway: return 2 - case RoundingModeNearestHalfEven: + case RoundingRuleNearestHalfEven: return 3 } panic(errors.NewUnreachableError()) } -func (mode RoundingMode) DocString() string { - switch mode { - case RoundingModeTowardZero: - return RoundingModeTowardZeroDocString - case RoundingModeAwayFromZero: - return RoundingModeAwayFromZeroDocString - case RoundingModeNearestHalfAway: - return RoundingModeNearestHalfAwayDocString - case RoundingModeNearestHalfEven: - return RoundingModeNearestHalfEvenDocString +func (rule RoundingRule) DocString() string { + switch rule { + case RoundingRuleTowardZero: + return RoundingRuleTowardZeroDocString + case RoundingRuleAwayFromZero: + return RoundingRuleAwayFromZeroDocString + case RoundingRuleNearestHalfAway: + return RoundingRuleNearestHalfAwayDocString + case RoundingRuleNearestHalfEven: + return RoundingRuleNearestHalfEvenDocString } panic(errors.NewUnreachableError()) } -const RoundingModeTowardZeroDocString = ` +const RoundingRuleTowardZeroDocString = ` Round to the closest representable fixed-point value that has a magnitude less than or equal to the magnitude of the real result, effectively truncating the fractional part. @@ -110,7 +110,7 @@ effectively truncating the fractional part. e.g. 5e-8 / 2 = 2e-8, -5e-8 / 2 = -2e-8 ` -const RoundingModeAwayFromZeroDocString = ` +const RoundingRuleAwayFromZeroDocString = ` Round to the closest representable fixed-point value that has a magnitude greater than or equal to the magnitude of the real result, effectively rounding up any fractional part. @@ -118,7 +118,7 @@ effectively rounding up any fractional part. e.g. 5e-8 / 2 = 3e-8, -5e-8 / 2 = -3e-8 ` -const RoundingModeNearestHalfAwayDocString = ` +const RoundingRuleNearestHalfAwayDocString = ` Round to the closest representable fixed-point value to the real result, which could be larger (rounded up) or smaller (rounded down) depending on if the unrepresentable portion is greater than or less than one half @@ -130,7 +130,7 @@ the value will be rounded away from zero. e.g. 7e-8 / 2 = 4e-8, 5e-8 / 2 = 3e-8 ` -const RoundingModeNearestHalfEvenDocString = ` +const RoundingRuleNearestHalfEvenDocString = ` Round to the closest representable fixed-point value to the real result, which could be larger (rounded up) or smaller (rounded down) depending on if the unrepresentable portion is greater than or less than one half diff --git a/sema/rounding_mode_type_test.go b/sema/rounding_rule_type_test.go similarity index 62% rename from sema/rounding_mode_type_test.go rename to sema/rounding_rule_type_test.go index 8a517e3719..408a75c3e5 100644 --- a/sema/rounding_mode_type_test.go +++ b/sema/rounding_rule_type_test.go @@ -25,32 +25,32 @@ import ( "github.com/stretchr/testify/require" ) -func TestRoundingModeValues(t *testing.T) { +func TestRoundingRuleValues(t *testing.T) { t.Parallel() - // Ensure that the values of the RoundingMode enum are not accidentally changed, + // Ensure that the values of the RoundingRule enum are not accidentally changed, // e.g. by adding a new value in between or by changing an existing value. - expectedValues := map[RoundingMode]uint8{ - RoundingModeTowardZero: 0, - RoundingModeAwayFromZero: 1, - RoundingModeNearestHalfAway: 2, - RoundingModeNearestHalfEven: 3, - RoundingMode_Count: 4, + expectedValues := map[RoundingRule]uint8{ + RoundingRuleTowardZero: 0, + RoundingRuleAwayFromZero: 1, + RoundingRuleNearestHalfAway: 2, + RoundingRuleNearestHalfEven: 3, + RoundingRule_Count: 4, } // Check all expected values. - for mode, expectedValue := range expectedValues { - require.Equal(t, expectedValue, uint8(mode), "value mismatch for %d", mode) + for rule, expectedValue := range expectedValues { + require.Equal(t, expectedValue, uint8(rule), "value mismatch for %d", rule) } // Check that no new values have been added // without updating the expected values above. - for i := uint8(0); i < uint8(RoundingMode_Count); i++ { - mode := RoundingMode(i) - _, ok := expectedValues[mode] + for i := uint8(0); i < uint8(RoundingRule_Count); i++ { + rule := RoundingRule(i) + _, ok := expectedValues[rule] require.True(t, ok, - fmt.Sprintf("unexpected RoundingMode value %d: update expectedValues", i), + fmt.Sprintf("unexpected RoundingRule value %d: update expectedValues", i), ) } } diff --git a/sema/type.go b/sema/type.go index fb82dd3bb4..e037dc0ca0 100644 --- a/sema/type.go +++ b/sema/type.go @@ -4570,7 +4570,7 @@ var AllBuiltinTypes = common.Concat( PublicKeyType, SignatureAlgorithmType, HashAlgorithmType, - RoundingModeType, + RoundingRuleType, StorageCapabilityControllerType, AccountCapabilityControllerType, DeploymentResultType, @@ -4885,7 +4885,7 @@ func FixedPoint64ConversionFunctionType(numberType Type) *FunctionType { { Label: "rounding", Identifier: "rounding", - TypeAnnotation: RoundingModeTypeAnnotation, + TypeAnnotation: RoundingRuleTypeAnnotation, }, }, Arity: &Arity{Min: 1, Max: 2}, @@ -10205,7 +10205,7 @@ func init() { PublicKeyType, HashAlgorithmType, SignatureAlgorithmType, - RoundingModeType, + RoundingRuleType, AccountType, DeploymentResultType, } diff --git a/stdlib/builtin.go b/stdlib/builtin.go index 30cecd0b36..95ed6fb3e8 100644 --- a/stdlib/builtin.go +++ b/stdlib/builtin.go @@ -57,7 +57,7 @@ func InterpreterDefaultStandardLibraryValues(handler StandardLibraryHandler) []S InterpreterAssertFunction, InterpreterPanicFunction, InterpreterSignatureAlgorithmConstructor, - InterpreterRoundingModeConstructor, + InterpreterRoundingRuleConstructor, InterpreterInclusiveRangeConstructor, NewInterpreterLogFunction(handler), NewInterpreterRevertibleRandomFunction(handler), @@ -77,7 +77,7 @@ func VMDefaultStandardLibraryValues(handler StandardLibraryHandler) []StandardLi VMAssertFunction, VMPanicFunction, VMSignatureAlgorithmConstructor, - VMRoundingModeConstructor, + VMRoundingRuleConstructor, VMInclusiveRangeConstructor, NewVMLogFunction(handler), NewVMRevertibleRandomFunction(handler), @@ -157,7 +157,7 @@ func VMValues(handler StandardLibraryHandler) []VMValue { return common.Concat( VMSignatureAlgorithmCaseValues, NewVMHashAlgorithmCaseValues(handler), - VMRoundingModeCaseValues, + VMRoundingRuleCaseValues, ) } diff --git a/stdlib/roundingmode.go b/stdlib/roundingrule.go similarity index 58% rename from stdlib/roundingmode.go rename to stdlib/roundingrule.go index 1a200c0bb5..dd2ee25856 100644 --- a/stdlib/roundingmode.go +++ b/stdlib/roundingrule.go @@ -26,12 +26,12 @@ import ( "github.com/onflow/cadence/sema" ) -var roundingModeStaticType interpreter.StaticType = interpreter.ConvertSemaCompositeTypeToStaticCompositeType( +var roundingRuleStaticType interpreter.StaticType = interpreter.ConvertSemaCompositeTypeToStaticCompositeType( nil, - sema.RoundingModeType, + sema.RoundingRuleType, ) -func NewRoundingModeCase(rawValue interpreter.UInt8Value) interpreter.MemberAccessibleValue { +func NewRoundingRuleCase(rawValue interpreter.UInt8Value) interpreter.MemberAccessibleValue { fields := map[string]interpreter.Value{ sema.EnumRawValueFieldName: rawValue, @@ -39,8 +39,8 @@ func NewRoundingModeCase(rawValue interpreter.UInt8Value) interpreter.MemberAcce return interpreter.NewSimpleCompositeValue( nil, - sema.RoundingModeType.ID(), - roundingModeStaticType, + sema.RoundingRuleType.ID(), + roundingRuleStaticType, []string{sema.EnumRawValueFieldName}, fields, nil, @@ -50,27 +50,27 @@ func NewRoundingModeCase(rawValue interpreter.UInt8Value) interpreter.MemberAcce ) } -var roundingModeLookupType = nativeEnumLookupType( - sema.RoundingModeType, - sema.RoundingModes, +var roundingRuleLookupType = nativeEnumLookupType( + sema.RoundingRuleType, + sema.RoundingRules, ) -var interpreterRoundingModeConstructorValue, RoundingModeCaseValues = interpreterNativeEnumValueAndCaseValues( - roundingModeLookupType, - sema.RoundingModes, - NewRoundingModeCase, +var interpreterRoundingRuleConstructorValue, RoundingRuleCaseValues = interpreterNativeEnumValueAndCaseValues( + roundingRuleLookupType, + sema.RoundingRules, + NewRoundingRuleCase, ) -var InterpreterRoundingModeConstructor = StandardLibraryValue{ - Name: sema.RoundingModeTypeName, - Type: roundingModeLookupType, - Value: interpreterRoundingModeConstructorValue, +var InterpreterRoundingRuleConstructor = StandardLibraryValue{ + Name: sema.RoundingRuleTypeName, + Type: roundingRuleLookupType, + Value: interpreterRoundingRuleConstructorValue, Kind: common.DeclarationKindEnum, } -var vmRoundingModeConstructorValue = vm.NewNativeFunctionValue( - sema.RoundingModeTypeName, - roundingModeLookupType, +var vmRoundingRuleConstructorValue = vm.NewNativeFunctionValue( + sema.RoundingRuleTypeName, + roundingRuleLookupType, func( context interpreter.NativeFunctionContext, _ interpreter.TypeArgumentsIterator, @@ -80,7 +80,7 @@ var vmRoundingModeConstructorValue = vm.NewNativeFunctionValue( ) interpreter.Value { rawValue := args[0].(interpreter.UInt8Value) - caseValue, ok := RoundingModeCaseValues[rawValue] + caseValue, ok := RoundingRuleCaseValues[rawValue] if !ok { return interpreter.Nil } @@ -89,23 +89,23 @@ var vmRoundingModeConstructorValue = vm.NewNativeFunctionValue( }, ) -var VMRoundingModeConstructor = StandardLibraryValue{ - Name: sema.RoundingModeTypeName, - Type: roundingModeLookupType, - Value: vmRoundingModeConstructorValue, +var VMRoundingRuleConstructor = StandardLibraryValue{ + Name: sema.RoundingRuleTypeName, + Type: roundingRuleLookupType, + Value: vmRoundingRuleConstructorValue, Kind: common.DeclarationKindEnum, } -var VMRoundingModeCaseValues = func() []VMValue { - values := make([]VMValue, len(sema.RoundingModes)) - for i, roundingMode := range sema.RoundingModes { - rawValue := interpreter.UInt8Value(roundingMode.RawValue()) +var VMRoundingRuleCaseValues = func() []VMValue { + values := make([]VMValue, len(sema.RoundingRules)) + for i, roundingRule := range sema.RoundingRules { + rawValue := interpreter.UInt8Value(roundingRule.RawValue()) values[i] = VMValue{ Name: commons.TypeQualifiedName( - sema.RoundingModeType, - roundingMode.Name(), + sema.RoundingRuleType, + roundingRule.Name(), ), - Value: RoundingModeCaseValues[rawValue], + Value: RoundingRuleCaseValues[rawValue], } } return values diff --git a/test_utils/test_utils.go b/test_utils/test_utils.go index 5859c2d0a6..36da821d4d 100644 --- a/test_utils/test_utils.go +++ b/test_utils/test_utils.go @@ -320,7 +320,7 @@ func ParseCheckAndPrepareWithOptions( // (i.e: only get the values that were added externally for tests) interpreterBaseActivationVariables := interpreterBaseActivation.ValuesInCurrentLevel() - // Collect nested variables (e.g. enum case values like "RoundingMode.towardZero") + // Collect nested variables (e.g. enum case values like "RoundingRule.towardZero") // from HostFunctionValues, so they can be registered in both the VM and compiler activations. type nestedVariableEntry struct { qualifiedName string