diff --git a/encoding/ccf/ccf_test.go b/encoding/ccf/ccf_test.go index 29c145678d..d9946f5e9b 100644 --- a/encoding/ccf/ccf_test.go +++ b/encoding/ccf/ccf_test.go @@ -5700,6 +5700,108 @@ func TestEncodeStruct(t *testing.T) { ) } +func TestEncodeInclusiveRange(t *testing.T) { + + t.Parallel() + + simpleInclusiveRange := encodeTest{ + name: "simpleInclusiveRange", + val: func() cadence.Value { + return cadence.NewInclusiveRange( + cadence.NewInt256(10), + cadence.NewInt256(20), + cadence.NewInt256(5), + ).WithType(cadence.NewInclusiveRangeType(cadence.NewInt256Type())) + }(), + expected: []byte{ + // language=json, format=json-cdc + // {"type":"InclusiveRange","value":[{"type":"Int256","value":"10"},{"type":"Int256","value":"20"},{"type":"Int256","value":"5"}]} + // + // language=edn, format=ccf + // 130([145(137(10)), [10, 20, 5]]) + // + // language=cbor, format=ccf + // tag + 0xd8, ccf.CBORTagTypeAndValue, + // array, 2 items follow + 0x82, + // type (InclusiveRange) + // tag + 0xd8, ccf.CBORTagInclusiveRangeType, + // tag + 0xd8, ccf.CBORTagSimpleType, + // Int256 type ID (10) + 0x0a, + // array data without inlined type definition + // array, 3 items follow + 0x83, + // tag (big num) + 0xc2, + // bytes, 1 bytes follow + 0x41, + // 10 + 0xa, + // tag (big num) + 0xc2, + // bytes, 1 bytes follow + 0x41, + // 20 + 0x14, + // tag (big num) + 0xc2, + // bytes, 1 bytes follow + 0x41, + // 5 + 0x5, + }, + } + + inclusiveRangeWithPrimitive := encodeTest{ + name: "simpleInclusiveRange", + val: func() cadence.Value { + return cadence.NewInclusiveRange( + cadence.NewInt8(10), + cadence.NewInt8(20), + cadence.NewInt8(5), + ).WithType(cadence.NewInclusiveRangeType(cadence.NewInt8Type())) + }(), + expected: []byte{ + // language=json, format=json-cdc + // {"type":"InclusiveRange","value":[{"type":"Int8","value":"10"},{"type":"Int8","value":"20"},{"type":"Int8","value":"5"}]} + // + // language=edn, format=ccf + // 130([145(137(5)), [10, 20, 5]]) + // + // language=cbor, format=ccf + // tag + 0xd8, ccf.CBORTagTypeAndValue, + // array, 2 items follow + 0x82, + // type (InclusiveRange) + // tag + 0xd8, ccf.CBORTagInclusiveRangeType, + // tag + 0xd8, ccf.CBORTagSimpleType, + // Int8 type ID (5) + 0x05, + // array data without inlined type definition + // array, 3 items follow + 0x83, + // element 1, 10 + 0xa, + // element 2, 20 + 0x14, + // element 3, 5 + 0x5, + }, + } + + testAllEncodeAndDecode(t, + simpleInclusiveRange, + inclusiveRangeWithPrimitive, + ) +} + func TestEncodeEvent(t *testing.T) { t.Parallel() @@ -8085,6 +8187,43 @@ func TestEncodeType(t *testing.T) { }) + t.Run("with static InclusiveRange", func(t *testing.T) { + t.Parallel() + + testEncodeAndDecode( + t, + cadence.TypeValue{ + StaticType: &cadence.InclusiveRangeType{ + ElementType: cadence.IntType{}, + }, + }, + []byte{ + // language=json, format=json-cdc + // {"type":"Type","value":{"staticType":{"kind":"InclusiveRange", "element" : {"kind" : "Int"}}}} + // + // language=edn, format=ccf + // 130([137(41), 194([185(4)])]) + // + // language=cbor, format=ccf + // tag + 0xd8, ccf.CBORTagTypeAndValue, + // array, 2 elements follow + 0x82, + // tag + 0xd8, ccf.CBORTagSimpleType, + // Meta type ID (41) + 0x18, 0x29, + // tag + 0xd8, ccf.CBORTagInclusiveRangeTypeValue, + // tag + 0xd8, ccf.CBORTagSimpleTypeValue, + // Int type (4) + 0x04, + }, + ) + + }) + t.Run("with static struct with no field", func(t *testing.T) { t.Parallel() @@ -13997,6 +14136,219 @@ func TestDecodeInvalidData(t *testing.T) { 0x01, }, }, + { + name: "nil element type in inclusiverange type", + data: []byte{ + // language=edn, format=ccf + // 130([145(nil), [10, 20, 5]]) + // + // language=cbor, format=ccf + // tag + 0xd8, ccf.CBORTagTypeAndValue, + // array, 2 items follow + 0x82, + // type (InclusiveRange) + // tag + 0xd8, ccf.CBORTagInclusiveRangeType, + // null + 0xf6, + // array data without inlined type definition + // array, 3 items follow + 0x83, + // tag (big num) + 0xc2, + // bytes, 1 bytes follow + 0x41, + // 10 + 0xa, + // tag (big num) + 0xc2, + // bytes, 1 bytes follow + 0x41, + // 20 + 0x14, + // tag (big num) + 0xc2, + // bytes, 1 bytes follow + 0x41, + // 5 + 0x5, + }, + }, + { + name: "invalid array head in inclusiverange value", + data: []byte{ + // language=edn, format=ccf + // 130([145(4), [10, 20, 5]]) + // + // language=cbor, format=ccf + // tag + 0xd8, ccf.CBORTagTypeAndValue, + // array, 2 items follow + 0x82, + // type (InclusiveRange) + // tag + 0xd8, ccf.CBORTagInclusiveRangeType, + // tag + 0xd8, ccf.CBORTagSimpleType, + // Int type ID (4) + 0x04, + // primitive type where array was expected + 0xe0, + // tag (big num) + 0xc2, + // bytes, 1 bytes follow + 0x41, + // 10 + 0xa, + // tag (big num) + 0xc2, + // bytes, 1 bytes follow + 0x41, + // 20 + 0x14, + // tag (big num) + 0xc2, + // bytes, 1 bytes follow + 0x41, + // 5 + 0x5, + }, + }, + { + name: "incorrect member count (2 instead of 3) in inclusiverange value", + data: []byte{ + // language=edn, format=ccf + // 130([145(4), [10, 20, 5]]) + // + // language=cbor, format=ccf + // tag + 0xd8, ccf.CBORTagTypeAndValue, + // array, 2 items follow + 0x82, + // type (InclusiveRange) + // tag + 0xd8, ccf.CBORTagInclusiveRangeType, + // tag + 0xd8, ccf.CBORTagSimpleType, + // Int type ID (4) + 0x04, + // array data without inlined type definition + // array, 2 items follow where 3 were expected + 0x82, + // tag (big num) + 0xc2, + // bytes, 1 bytes follow + 0x41, + // 10 + 0xa, + // tag (big num) + 0xc2, + // bytes, 1 bytes follow + 0x41, + // 20 + 0x14, + }, + }, + { + name: "invalid start value in inclusiverange value", + data: []byte{ + // language=edn, format=ccf + // 130([145(5), [10, 20, 5]]) + // + // language=cbor, format=ccf + // tag + 0xd8, ccf.CBORTagTypeAndValue, + // array, 2 items follow + 0x82, + // type (InclusiveRange) + // tag + 0xd8, ccf.CBORTagInclusiveRangeType, + // tag + 0xd8, ccf.CBORTagSimpleType, + // Int8 type ID (5) + 0x05, + // array data without inlined type definition + // array, 3 items follow + 0x83, + // tag (big num) but expected an Int8 + 0xc2, + // bytes, 1 bytes follow + 0x41, + // 10 + 0xa, + // 20 + 0x14, + // 5 + 0x05, + }, + }, + { + name: "invalid end value in inclusiverange value", + data: []byte{ + // language=edn, format=ccf + // 130([145(5), [10, 20, 5]]) + // + // language=cbor, format=ccf + // tag + 0xd8, ccf.CBORTagTypeAndValue, + // array, 2 items follow + 0x82, + // type (InclusiveRange) + // tag + 0xd8, ccf.CBORTagInclusiveRangeType, + // tag + 0xd8, ccf.CBORTagSimpleType, + // Int8 type ID (5) + 0x05, + // array data without inlined type definition + // array, 3 items follow + 0x83, + // 10 + 0xa, + // tag (big num) but expected an Int8 + 0xc2, + // bytes, 1 bytes follow + 0x41, + // 20 + 0x14, + // 5 + 0x05, + }, + }, + { + name: "invalid step value in inclusiverange value", + data: []byte{ + // language=edn, format=ccf + // 130([145(5), [10, 20, 5]]) + // + // language=cbor, format=ccf + // tag + 0xd8, ccf.CBORTagTypeAndValue, + // array, 2 items follow + 0x82, + // type (InclusiveRange) + // tag + 0xd8, ccf.CBORTagInclusiveRangeType, + // tag + 0xd8, ccf.CBORTagSimpleType, + // Int8 type ID (5) + 0x05, + // array data without inlined type definition + // array, 3 items follow + 0x83, + // 10 + 0xa, + // 20 + 0x14, + // tag (big num) but expected an Int8 + 0xc2, + // bytes, 1 bytes follow + 0x41, + // 5 + 0x05, + }, + }, { name: "nil composite field type", data: []byte{ @@ -14193,6 +14545,27 @@ func TestDecodeInvalidData(t *testing.T) { 0xf6, }, }, + { + name: "nil element type in inclusiverange type value", + data: []byte{ + // language=edn, format=ccf + // 130([137(41), 194([null])]) + // + // language=cbor, format=ccf + // tag + 0xd8, ccf.CBORTagTypeAndValue, + // array, 2 elements follow + 0x82, + // tag + 0xd8, ccf.CBORTagSimpleType, + // Meta type ID (41) + 0x18, 0x29, + // tag + 0xd8, ccf.CBORTagInclusiveRangeTypeValue, + // null + 0xf6, + }, + }, { name: "nil field type in struct type value", data: []byte{ diff --git a/encoding/ccf/consts.go b/encoding/ccf/consts.go index ee01a62912..d918cfc821 100644 --- a/encoding/ccf/consts.go +++ b/encoding/ccf/consts.go @@ -64,7 +64,7 @@ const ( CBORTagReferenceType CBORTagRestrictedType CBORTagCapabilityType - _ + CBORTagInclusiveRangeType _ _ _ @@ -120,7 +120,7 @@ const ( CBORTagRestrictedTypeValue CBORTagCapabilityTypeValue CBORTagFunctionTypeValue - _ + CBORTagInclusiveRangeTypeValue // InclusiveRange is stored as a composite value. _ _ _ diff --git a/encoding/ccf/decode.go b/encoding/ccf/decode.go index a4b5b5808b..56f1890e29 100644 --- a/encoding/ccf/decode.go +++ b/encoding/ccf/decode.go @@ -358,6 +358,7 @@ func (d *Decoder) decodeTypeAndValue(types *cadenceTypeByCCFTypeID) (cadence.Val // / path-value // / path-capability-value // / id-capability-value +// / inclusiverange-value // / function-value // / type-value // @@ -507,6 +508,9 @@ func (d *Decoder) decodeValue(t cadence.Type, types *cadenceTypeByCCFTypeID) (ca case *cadence.ResourceType: return d.decodeResource(t, types) + case *cadence.InclusiveRangeType: + return d.decodeInclusiveRange(t, types) + case *cadence.StructType: return d.decodeStruct(t, types) @@ -1309,6 +1313,52 @@ func (d *Decoder) decodeEnum(typ *cadence.EnumType, types *cadenceTypeByCCFTypeI return v.WithType(typ), nil } +// decodeInclusiveRange decodes encoded inclusiverange-value as +// language=CDDL +// inclusiverange-value = [ +// +// start: value, +// end: value, +// step: value, +// +// ] +func (d *Decoder) decodeInclusiveRange(typ *cadence.InclusiveRangeType, types *cadenceTypeByCCFTypeID) (cadence.Value, error) { + // Decode array head of length 3. + err := decodeCBORArrayWithKnownSize(d.dec, 3) + if err != nil { + return nil, err + } + + elementType := typ.ElementType + + // Decode start. + start, err := d.decodeValue(elementType, types) + if err != nil { + return nil, err + } + + // Decode end. + end, err := d.decodeValue(elementType, types) + if err != nil { + return nil, err + } + + // Decode step. + step, err := d.decodeValue(elementType, types) + if err != nil { + return nil, err + } + + v := cadence.NewMeteredInclusiveRange( + d.gauge, + start, + end, + step, + ) + + return v.WithType(typ), nil +} + // decodePath decodes path-value as // language=CDDL // path-value = [ @@ -1442,6 +1492,7 @@ func (d *Decoder) decodeCapability(typ *cadence.CapabilityType, types *cadenceTy // / varsized-array-type-value // / constsized-array-type-value // / dict-type-value +// / inclusiverange-type-value // / struct-type-value // / resource-type-value // / contract-type-value @@ -1482,6 +1533,9 @@ func (d *Decoder) decodeTypeValue(visited *cadenceTypeByCCFTypeID) (cadence.Type case CBORTagDictTypeValue: return d.decodeDictType(visited, d.decodeTypeValue) + case CBORTagInclusiveRangeTypeValue: + return d.decodeInclusiveRangeType(visited, d.decodeTypeValue) + case CBORTagCapabilityTypeValue: return d.decodeCapabilityType(visited, d.decodeNullableTypeValue) diff --git a/encoding/ccf/decode_type.go b/encoding/ccf/decode_type.go index d461d40a24..3a82648e51 100644 --- a/encoding/ccf/decode_type.go +++ b/encoding/ccf/decode_type.go @@ -45,6 +45,7 @@ type decodeTypeFn func(types *cadenceTypeByCCFTypeID) (cadence.Type, error) // / reference-type // / restricted-type // / capability-type +// / inclusiverange-type // / type-ref // // All exported Cadence types needs to be handled in this function, @@ -71,6 +72,9 @@ func (d *Decoder) decodeInlineType(types *cadenceTypeByCCFTypeID) (cadence.Type, case CBORTagDictType: return d.decodeDictType(types, d.decodeInlineType) + case CBORTagInclusiveRangeType: + return d.decodeInclusiveRangeType(types, d.decodeInlineType) + case CBORTagReferenceType: return d.decodeReferenceType(types, d.decodeInlineType) @@ -436,6 +440,36 @@ func (d *Decoder) decodeDictType( return cadence.NewMeteredDictionaryType(d.gauge, keyType, elementType), nil } +// decodeInclusiveRangeType decodes inclusiverange-type or inclusiverange-type-value as +// language=CDDL +// inclusiverange-type = +// +// ; cbor-tag-inclusiverange-type +// #6.145(inline-type) +// +// inclusiverange-type-value = +// +// ; cbor-tag-inclusiverange-type-value +// #6.194(type-value) +// +// NOTE: decodeTypeFn is responsible for decoding inline-type or type-value. +func (d *Decoder) decodeInclusiveRangeType( + types *cadenceTypeByCCFTypeID, + decodeTypeFn decodeTypeFn, +) (cadence.Type, error) { + // element 0: element type (inline-type or type-value) + elementType, err := decodeTypeFn(types) + if err != nil { + return nil, err + } + + if elementType == nil { + return nil, errors.New("unexpected nil type as inclusiverange element type") + } + + return cadence.NewMeteredInclusiveRangeType(d.gauge, elementType), nil +} + // decodeCapabilityType decodes capability-type or capability-type-value as // language=CDDL // capability-type = diff --git a/encoding/ccf/encode.go b/encoding/ccf/encode.go index ab237ec2e8..fad8494851 100644 --- a/encoding/ccf/encode.go +++ b/encoding/ccf/encode.go @@ -378,6 +378,7 @@ func (e *Encoder) encodeTypeDefs(types []cadence.Type, tids ccfTypeIDByCadenceTy // / path-value // / path-capability-value // / id-capability-value +// / inclusiverange-value // / function-value // / type-value // @@ -563,6 +564,9 @@ func (e *Encoder) encodeValue( case cadence.Dictionary: return e.encodeDictionary(v, tids) + case *cadence.InclusiveRange: + return e.encodeInclusiveRange(v, tids) + case cadence.Struct: return e.encodeStruct(v, tids) @@ -977,6 +981,34 @@ func encodeAndSortKeyValuePairs( return encodedPairs, nil } +// encodeInclusiveRange encodes cadence.InclusiveRange as +// language=CDDL +// inclusiverange-value = [3*3 (key: value, value: value)] +func (e *Encoder) encodeInclusiveRange(v *cadence.InclusiveRange, tids ccfTypeIDByCadenceType) error { + staticElementType := v.InclusiveRangeType.ElementType + + // Encode array head with array size of 3. + err := e.enc.EncodeArrayHead(3) + if err != nil { + return err + } + + // Encode start key as value. + err = e.encodeValue(v.Start, staticElementType, tids) + if err != nil { + return err + } + + // Encode end as value. + err = e.encodeValue(v.End, staticElementType, tids) + if err != nil { + return err + } + + // Encode step key as value. + return e.encodeValue(v.Step, staticElementType, tids) +} + // encodeStruct encodes cadence.Struct as // language=CDDL // composite-value = [* (field: value)] @@ -1232,6 +1264,7 @@ func (e *Encoder) encodeFunction(typ *cadence.FunctionType, visited ccfTypeIDByC // / reference-type-value // / restricted-type-value // / capability-type-value +// / inclusiverange-type-value // / type-value-ref // // TypeValue is used differently from inline type or type definition. @@ -1282,6 +1315,9 @@ func (e *Encoder) encodeTypeValue(typ cadence.Type, visited ccfTypeIDByCadenceTy case *cadence.ContractType: return e.encodeContractTypeValue(typ, visited) + case *cadence.InclusiveRangeType: + return e.encodeInclusiveRangeTypeValue(typ, visited) + case *cadence.StructInterfaceType: return e.encodeStructInterfaceTypeValue(typ, visited) @@ -1411,6 +1447,22 @@ func (e *Encoder) encodeDictTypeValue(typ *cadence.DictionaryType, visited ccfTy ) } +// encodeInclusiveRangeTypeValue encodes cadence.InclusiveRangeType as +// language=CDDL +// inclusiverange-type-value = +// +// ; cbor-tag-inclusiverange-type-value +// #6.194(type-value) +func (e *Encoder) encodeInclusiveRangeTypeValue(typ *cadence.InclusiveRangeType, visited ccfTypeIDByCadenceType) error { + rawTagNum := []byte{0xd8, CBORTagInclusiveRangeTypeValue} + return e.encodeInclusiveRangeTypeWithRawTag( + typ, + visited, + e.encodeTypeValue, + rawTagNum, + ) +} + // encodeReferenceTypeValue encodes cadence.ReferenceType as // language=CDDL // reference-type-value = diff --git a/encoding/ccf/encode_type.go b/encoding/ccf/encode_type.go index e725bfa0d0..d917bf53ab 100644 --- a/encoding/ccf/encode_type.go +++ b/encoding/ccf/encode_type.go @@ -39,6 +39,7 @@ type encodeTypeFn func(typ cadence.Type, tids ccfTypeIDByCadenceType) error // / reference-type // / restricted-type // / capability-type +// / inclusiverange-type // / type-ref // // All exported Cadence types need to be supported by this function, @@ -62,6 +63,9 @@ func (e *Encoder) encodeInlineType(typ cadence.Type, tids ccfTypeIDByCadenceType case *cadence.DictionaryType: return e.encodeDictType(typ, tids) + case *cadence.InclusiveRangeType: + return e.encodeInclusiveRangeType(typ, tids) + case cadence.CompositeType, cadence.InterfaceType: id, err := tids.id(typ) if err != nil { @@ -296,6 +300,43 @@ func (e *Encoder) encodeDictTypeWithRawTag( return encodeTypeFn(typ.ElementType, tids) } +// encodeInclusiveRangeType encodes cadence.InclusiveRangeType as +// language=CDDL +// inclusiverange-type = +// +// ; cbor-tag-inclusiverange-type +// #6.145(inline-type) +func (e *Encoder) encodeInclusiveRangeType( + typ *cadence.InclusiveRangeType, + tids ccfTypeIDByCadenceType, +) error { + rawTagNum := []byte{0xd8, CBORTagInclusiveRangeType} + return e.encodeInclusiveRangeTypeWithRawTag( + typ, + tids, + e.encodeInlineType, + rawTagNum, + ) +} + +// encodeInclusiveRangeTypeWithRawTag encodes cadence.InclusiveRangeType +// with given tag number and encode type function. +func (e *Encoder) encodeInclusiveRangeTypeWithRawTag( + typ *cadence.InclusiveRangeType, + tids ccfTypeIDByCadenceType, + encodeTypeFn encodeTypeFn, + rawTagNumber []byte, +) error { + // Encode CBOR tag number. + err := e.enc.EncodeRawBytes(rawTagNumber) + if err != nil { + return err + } + + // Encode element type with given encodeTypeFn + return encodeTypeFn(typ.ElementType, tids) +} + // encodeReferenceType encodes cadence.ReferenceType as // language=CDDL // reference-type = diff --git a/encoding/ccf/traverse_value.go b/encoding/ccf/traverse_value.go index d82c83891c..ee982cf43e 100644 --- a/encoding/ccf/traverse_value.go +++ b/encoding/ccf/traverse_value.go @@ -220,7 +220,8 @@ func (ct *compositeTypes) traverseType(typ cadence.Type) (checkRuntimeType bool) cadence.IntegerType, cadence.SignedIntegerType, cadence.FixedPointType, - cadence.SignedFixedPointType: + cadence.SignedFixedPointType, + *cadence.InclusiveRangeType: // TODO: Maybe there are more types that we can skip checking runtime type for composite type. return false diff --git a/encoding/json/decode.go b/encoding/json/decode.go index 1c7d25997c..46e28e243a 100644 --- a/encoding/json/decode.go +++ b/encoding/json/decode.go @@ -136,6 +136,10 @@ const ( returnKey = "return" typeBoundKey = "typeBound" functionTypeKey = "functionType" + elementKey = "element" + startKey = "start" + endKey = "end" + stepKey = "step" ) func (d *Decoder) decodeJSON(v any) cadence.Value { @@ -222,6 +226,8 @@ func (d *Decoder) decodeJSON(v any) cadence.Value { return d.decodeEvent(valueJSON) case contractTypeStr: return d.decodeContract(valueJSON) + case inclusiveRangeTypeStr: + return d.decodeInclusiveRange(valueJSON) case pathTypeStr: return d.decodePath(valueJSON) case typeTypeStr: @@ -866,6 +872,26 @@ func (d *Decoder) decodeEnum(valueJSON any) cadence.Enum { )) } +func (d *Decoder) decodeInclusiveRange(valueJSON any) *cadence.InclusiveRange { + obj := toObject(valueJSON) + + start := obj.GetValue(d, startKey) + end := obj.GetValue(d, endKey) + step := obj.GetValue(d, stepKey) + + value := cadence.NewMeteredInclusiveRange( + d.gauge, + start, + end, + step, + ) + + return value.WithType(cadence.NewMeteredInclusiveRangeType( + d.gauge, + start.Type(), + )) +} + func (d *Decoder) decodePath(valueJSON any) cadence.Path { obj := toObject(valueJSON) @@ -1185,6 +1211,11 @@ func (d *Decoder) decodeType(valueJSON any, results typeDecodingResults) cadence d.decodeType(obj.Get(keyKey), results), d.decodeType(obj.Get(valueKey), results), ) + case "InclusiveRange": + return cadence.NewMeteredInclusiveRangeType( + d.gauge, + d.decodeType(obj.Get(elementKey), results), + ) case "ConstantSizedArray": size := toUInt(obj.Get(sizeKey)) return cadence.NewMeteredConstantSizedArrayType( diff --git a/encoding/json/encode.go b/encoding/json/encode.go index 4fc7cb49c0..63808cac20 100644 --- a/encoding/json/encode.go +++ b/encoding/json/encode.go @@ -116,6 +116,12 @@ type jsonDictionaryItem struct { Value jsonValue `json:"value"` } +type jsonInclusiveRangeValue struct { + Start jsonValue `json:"start"` + End jsonValue `json:"end"` + Step jsonValue `json:"step"` +} + type jsonCompositeValue struct { ID string `json:"id"` Fields []jsonCompositeField `json:"fields"` @@ -170,6 +176,11 @@ type jsonDictionaryType struct { Kind string `json:"kind"` } +type jsonInclusiveRangeType struct { + ElementType jsonValue `json:"element"` + Kind string `json:"kind"` +} + type jsonReferenceType struct { Type jsonValue `json:"type"` Kind string `json:"kind"` @@ -223,48 +234,49 @@ type jsonFunctionValue struct { } const ( - voidTypeStr = "Void" - optionalTypeStr = "Optional" - boolTypeStr = "Bool" - characterTypeStr = "Character" - stringTypeStr = "String" - addressTypeStr = "Address" - intTypeStr = "Int" - int8TypeStr = "Int8" - int16TypeStr = "Int16" - int32TypeStr = "Int32" - int64TypeStr = "Int64" - int128TypeStr = "Int128" - int256TypeStr = "Int256" - uintTypeStr = "UInt" - uint8TypeStr = "UInt8" - uint16TypeStr = "UInt16" - uint32TypeStr = "UInt32" - uint64TypeStr = "UInt64" - uint128TypeStr = "UInt128" - uint256TypeStr = "UInt256" - word8TypeStr = "Word8" - word16TypeStr = "Word16" - word32TypeStr = "Word32" - word64TypeStr = "Word64" - word128TypeStr = "Word128" - word256TypeStr = "Word256" - fix64TypeStr = "Fix64" - ufix64TypeStr = "UFix64" - arrayTypeStr = "Array" - dictionaryTypeStr = "Dictionary" - structTypeStr = "Struct" - resourceTypeStr = "Resource" - attachmentTypeStr = "Attachment" - eventTypeStr = "Event" - contractTypeStr = "Contract" - linkTypeStr = "Link" - accountLinkTypeStr = "AccountLink" - pathTypeStr = "Path" - typeTypeStr = "Type" - capabilityTypeStr = "Capability" - enumTypeStr = "Enum" - functionTypeStr = "Function" + voidTypeStr = "Void" + optionalTypeStr = "Optional" + boolTypeStr = "Bool" + characterTypeStr = "Character" + stringTypeStr = "String" + addressTypeStr = "Address" + intTypeStr = "Int" + int8TypeStr = "Int8" + int16TypeStr = "Int16" + int32TypeStr = "Int32" + int64TypeStr = "Int64" + int128TypeStr = "Int128" + int256TypeStr = "Int256" + uintTypeStr = "UInt" + uint8TypeStr = "UInt8" + uint16TypeStr = "UInt16" + uint32TypeStr = "UInt32" + uint64TypeStr = "UInt64" + uint128TypeStr = "UInt128" + uint256TypeStr = "UInt256" + word8TypeStr = "Word8" + word16TypeStr = "Word16" + word32TypeStr = "Word32" + word64TypeStr = "Word64" + word128TypeStr = "Word128" + word256TypeStr = "Word256" + fix64TypeStr = "Fix64" + ufix64TypeStr = "UFix64" + arrayTypeStr = "Array" + dictionaryTypeStr = "Dictionary" + structTypeStr = "Struct" + resourceTypeStr = "Resource" + attachmentTypeStr = "Attachment" + eventTypeStr = "Event" + contractTypeStr = "Contract" + inclusiveRangeTypeStr = "InclusiveRange" + linkTypeStr = "Link" + accountLinkTypeStr = "AccountLink" + pathTypeStr = "Path" + typeTypeStr = "Type" + capabilityTypeStr = "Capability" + enumTypeStr = "Enum" + functionTypeStr = "Function" ) // Prepare traverses the object graph of the provided value and constructs @@ -331,6 +343,8 @@ func Prepare(v cadence.Value) jsonValue { return prepareArray(v) case cadence.Dictionary: return prepareDictionary(v) + case *cadence.InclusiveRange: + return prepareInclusiveRange(v) case cadence.Struct: return prepareStruct(v) case cadence.Resource: @@ -592,6 +606,17 @@ func prepareDictionary(v cadence.Dictionary) jsonValue { } } +func prepareInclusiveRange(v *cadence.InclusiveRange) jsonValue { + return jsonValueObject{ + Type: inclusiveRangeTypeStr, + Value: jsonInclusiveRangeValue{ + Start: Prepare(v.Start), + End: Prepare(v.End), + Step: Prepare(v.Step), + }, + } +} + func prepareStruct(v cadence.Struct) jsonValue { return prepareComposite(structTypeStr, v.StructType.ID(), v.StructType.Fields, v.Fields) } @@ -841,6 +866,11 @@ func prepareType(typ cadence.Type, results typePreparationResults) jsonValue { KeyType: prepareType(typ.KeyType, results), ValueType: prepareType(typ.ElementType, results), } + case *cadence.InclusiveRangeType: + return jsonInclusiveRangeType{ + Kind: "InclusiveRange", + ElementType: prepareType(typ.ElementType, results), + } case *cadence.StructType: return jsonNominalType{ Kind: "Struct", diff --git a/encoding/json/encoding_test.go b/encoding/json/encoding_test.go index 65afb32b94..dbe4fb0f6f 100644 --- a/encoding/json/encoding_test.go +++ b/encoding/json/encoding_test.go @@ -1448,6 +1448,42 @@ func TestEncodeStruct(t *testing.T) { testAllEncodeAndDecode(t, simpleStruct, resourceStruct) } +func TestEncodeInclusiveRange(t *testing.T) { + + t.Parallel() + + simpleInclusiveRange := encodeTest{ + "Simple", + cadence.NewInclusiveRange( + cadence.NewInt256(10), + cadence.NewInt256(20), + cadence.NewInt256(5), + ).WithType(cadence.NewInclusiveRangeType(cadence.NewInt256Type())), + // language=json + ` + { + "type": "InclusiveRange", + "value": { + "start": { + "type": "Int256", + "value": "10" + }, + "end": { + "type": "Int256", + "value": "20" + }, + "step": { + "type": "Int256", + "value": "5" + } + } + } + `, + } + + testAllEncodeAndDecode(t, simpleInclusiveRange) +} + func TestEncodeEvent(t *testing.T) { t.Parallel() @@ -1932,6 +1968,33 @@ func TestEncodeType(t *testing.T) { }) + t.Run("with static InclusiveRange", func(t *testing.T) { + + testEncodeAndDecode( + t, + cadence.TypeValue{ + StaticType: &cadence.InclusiveRangeType{ + ElementType: cadence.IntType{}, + }, + }, + // language=json + ` + { + "type": "Type", + "value": { + "staticType": { + "kind": "InclusiveRange", + "element": { + "kind": "Int" + } + } + } + } + `, + ) + + }) + t.Run("with static struct", func(t *testing.T) { testEncodeAndDecode( diff --git a/runtime/common/memorykind.go b/runtime/common/memorykind.go index bf1888009f..c35c04fd96 100644 --- a/runtime/common/memorykind.go +++ b/runtime/common/memorykind.go @@ -72,6 +72,7 @@ const ( MemoryKindVariableSizedStaticType MemoryKindConstantSizedStaticType MemoryKindDictionaryStaticType + MemoryKindInclusiveRangeStaticType MemoryKindOptionalStaticType MemoryKindRestrictedStaticType MemoryKindReferenceStaticType @@ -90,6 +91,7 @@ const ( MemoryKindCadenceArrayValueBase MemoryKindCadenceArrayValueLength MemoryKindCadenceDictionaryValue + MemoryKindCadenceInclusiveRangeValue MemoryKindCadenceKeyValuePair MemoryKindCadenceStructValueBase MemoryKindCadenceStructValueSize @@ -116,6 +118,7 @@ const ( MemoryKindCadenceVariableSizedArrayType MemoryKindCadenceConstantSizedArrayType MemoryKindCadenceDictionaryType + MemoryKindCadenceInclusiveRangeType MemoryKindCadenceField MemoryKindCadenceParameter MemoryKindCadenceTypeParameter @@ -241,6 +244,7 @@ const ( MemoryKindRestrictedSemaType MemoryKindReferenceSemaType MemoryKindCapabilitySemaType + MemoryKindInclusiveRangeSemaType // ordered-map MemoryKindOrderedMap diff --git a/runtime/common/memorykind_string.go b/runtime/common/memorykind_string.go index 2f5fddc90e..a84eae199b 100644 --- a/runtime/common/memorykind_string.go +++ b/runtime/common/memorykind_string.go @@ -49,163 +49,167 @@ func _() { _ = x[MemoryKindVariableSizedStaticType-38] _ = x[MemoryKindConstantSizedStaticType-39] _ = x[MemoryKindDictionaryStaticType-40] - _ = x[MemoryKindOptionalStaticType-41] - _ = x[MemoryKindRestrictedStaticType-42] - _ = x[MemoryKindReferenceStaticType-43] - _ = x[MemoryKindCapabilityStaticType-44] - _ = x[MemoryKindFunctionStaticType-45] - _ = x[MemoryKindCadenceVoidValue-46] - _ = x[MemoryKindCadenceOptionalValue-47] - _ = x[MemoryKindCadenceBoolValue-48] - _ = x[MemoryKindCadenceStringValue-49] - _ = x[MemoryKindCadenceCharacterValue-50] - _ = x[MemoryKindCadenceAddressValue-51] - _ = x[MemoryKindCadenceIntValue-52] - _ = x[MemoryKindCadenceNumberValue-53] - _ = x[MemoryKindCadenceArrayValueBase-54] - _ = x[MemoryKindCadenceArrayValueLength-55] - _ = x[MemoryKindCadenceDictionaryValue-56] - _ = x[MemoryKindCadenceKeyValuePair-57] - _ = x[MemoryKindCadenceStructValueBase-58] - _ = x[MemoryKindCadenceStructValueSize-59] - _ = x[MemoryKindCadenceResourceValueBase-60] - _ = x[MemoryKindCadenceAttachmentValueBase-61] - _ = x[MemoryKindCadenceResourceValueSize-62] - _ = x[MemoryKindCadenceAttachmentValueSize-63] - _ = x[MemoryKindCadenceEventValueBase-64] - _ = x[MemoryKindCadenceEventValueSize-65] - _ = x[MemoryKindCadenceContractValueBase-66] - _ = x[MemoryKindCadenceContractValueSize-67] - _ = x[MemoryKindCadenceEnumValueBase-68] - _ = x[MemoryKindCadenceEnumValueSize-69] - _ = x[MemoryKindCadencePathLinkValue-70] - _ = x[MemoryKindCadenceAccountLinkValue-71] - _ = x[MemoryKindCadencePathValue-72] - _ = x[MemoryKindCadenceTypeValue-73] - _ = x[MemoryKindCadenceIDCapabilityValue-74] - _ = x[MemoryKindCadencePathCapabilityValue-75] - _ = x[MemoryKindCadenceFunctionValue-76] - _ = x[MemoryKindCadenceOptionalType-77] - _ = x[MemoryKindCadenceVariableSizedArrayType-78] - _ = x[MemoryKindCadenceConstantSizedArrayType-79] - _ = x[MemoryKindCadenceDictionaryType-80] - _ = x[MemoryKindCadenceField-81] - _ = x[MemoryKindCadenceParameter-82] - _ = x[MemoryKindCadenceTypeParameter-83] - _ = x[MemoryKindCadenceStructType-84] - _ = x[MemoryKindCadenceResourceType-85] - _ = x[MemoryKindCadenceAttachmentType-86] - _ = x[MemoryKindCadenceEventType-87] - _ = x[MemoryKindCadenceContractType-88] - _ = x[MemoryKindCadenceStructInterfaceType-89] - _ = x[MemoryKindCadenceResourceInterfaceType-90] - _ = x[MemoryKindCadenceContractInterfaceType-91] - _ = x[MemoryKindCadenceFunctionType-92] - _ = x[MemoryKindCadenceReferenceType-93] - _ = x[MemoryKindCadenceRestrictedType-94] - _ = x[MemoryKindCadenceCapabilityType-95] - _ = x[MemoryKindCadenceEnumType-96] - _ = x[MemoryKindRawString-97] - _ = x[MemoryKindAddressLocation-98] - _ = x[MemoryKindBytes-99] - _ = x[MemoryKindVariable-100] - _ = x[MemoryKindCompositeTypeInfo-101] - _ = x[MemoryKindCompositeField-102] - _ = x[MemoryKindInvocation-103] - _ = x[MemoryKindStorageMap-104] - _ = x[MemoryKindStorageKey-105] - _ = x[MemoryKindTypeToken-106] - _ = x[MemoryKindErrorToken-107] - _ = x[MemoryKindSpaceToken-108] - _ = x[MemoryKindProgram-109] - _ = x[MemoryKindIdentifier-110] - _ = x[MemoryKindArgument-111] - _ = x[MemoryKindBlock-112] - _ = x[MemoryKindFunctionBlock-113] - _ = x[MemoryKindParameter-114] - _ = x[MemoryKindParameterList-115] - _ = x[MemoryKindTypeParameter-116] - _ = x[MemoryKindTypeParameterList-117] - _ = x[MemoryKindTransfer-118] - _ = x[MemoryKindMembers-119] - _ = x[MemoryKindTypeAnnotation-120] - _ = x[MemoryKindDictionaryEntry-121] - _ = x[MemoryKindFunctionDeclaration-122] - _ = x[MemoryKindCompositeDeclaration-123] - _ = x[MemoryKindAttachmentDeclaration-124] - _ = x[MemoryKindInterfaceDeclaration-125] - _ = x[MemoryKindEnumCaseDeclaration-126] - _ = x[MemoryKindFieldDeclaration-127] - _ = x[MemoryKindTransactionDeclaration-128] - _ = x[MemoryKindImportDeclaration-129] - _ = x[MemoryKindVariableDeclaration-130] - _ = x[MemoryKindSpecialFunctionDeclaration-131] - _ = x[MemoryKindPragmaDeclaration-132] - _ = x[MemoryKindAssignmentStatement-133] - _ = x[MemoryKindBreakStatement-134] - _ = x[MemoryKindContinueStatement-135] - _ = x[MemoryKindEmitStatement-136] - _ = x[MemoryKindExpressionStatement-137] - _ = x[MemoryKindForStatement-138] - _ = x[MemoryKindIfStatement-139] - _ = x[MemoryKindReturnStatement-140] - _ = x[MemoryKindSwapStatement-141] - _ = x[MemoryKindSwitchStatement-142] - _ = x[MemoryKindWhileStatement-143] - _ = x[MemoryKindRemoveStatement-144] - _ = x[MemoryKindBooleanExpression-145] - _ = x[MemoryKindVoidExpression-146] - _ = x[MemoryKindNilExpression-147] - _ = x[MemoryKindStringExpression-148] - _ = x[MemoryKindIntegerExpression-149] - _ = x[MemoryKindFixedPointExpression-150] - _ = x[MemoryKindArrayExpression-151] - _ = x[MemoryKindDictionaryExpression-152] - _ = x[MemoryKindIdentifierExpression-153] - _ = x[MemoryKindInvocationExpression-154] - _ = x[MemoryKindMemberExpression-155] - _ = x[MemoryKindIndexExpression-156] - _ = x[MemoryKindConditionalExpression-157] - _ = x[MemoryKindUnaryExpression-158] - _ = x[MemoryKindBinaryExpression-159] - _ = x[MemoryKindFunctionExpression-160] - _ = x[MemoryKindCastingExpression-161] - _ = x[MemoryKindCreateExpression-162] - _ = x[MemoryKindDestroyExpression-163] - _ = x[MemoryKindReferenceExpression-164] - _ = x[MemoryKindForceExpression-165] - _ = x[MemoryKindPathExpression-166] - _ = x[MemoryKindAttachExpression-167] - _ = x[MemoryKindConstantSizedType-168] - _ = x[MemoryKindDictionaryType-169] - _ = x[MemoryKindFunctionType-170] - _ = x[MemoryKindInstantiationType-171] - _ = x[MemoryKindNominalType-172] - _ = x[MemoryKindOptionalType-173] - _ = x[MemoryKindReferenceType-174] - _ = x[MemoryKindRestrictedType-175] - _ = x[MemoryKindVariableSizedType-176] - _ = x[MemoryKindPosition-177] - _ = x[MemoryKindRange-178] - _ = x[MemoryKindElaboration-179] - _ = x[MemoryKindActivation-180] - _ = x[MemoryKindActivationEntries-181] - _ = x[MemoryKindVariableSizedSemaType-182] - _ = x[MemoryKindConstantSizedSemaType-183] - _ = x[MemoryKindDictionarySemaType-184] - _ = x[MemoryKindOptionalSemaType-185] - _ = x[MemoryKindRestrictedSemaType-186] - _ = x[MemoryKindReferenceSemaType-187] - _ = x[MemoryKindCapabilitySemaType-188] - _ = x[MemoryKindOrderedMap-189] - _ = x[MemoryKindOrderedMapEntryList-190] - _ = x[MemoryKindOrderedMapEntry-191] - _ = x[MemoryKindLast-192] + _ = x[MemoryKindInclusiveRangeStaticType-41] + _ = x[MemoryKindOptionalStaticType-42] + _ = x[MemoryKindRestrictedStaticType-43] + _ = x[MemoryKindReferenceStaticType-44] + _ = x[MemoryKindCapabilityStaticType-45] + _ = x[MemoryKindFunctionStaticType-46] + _ = x[MemoryKindCadenceVoidValue-47] + _ = x[MemoryKindCadenceOptionalValue-48] + _ = x[MemoryKindCadenceBoolValue-49] + _ = x[MemoryKindCadenceStringValue-50] + _ = x[MemoryKindCadenceCharacterValue-51] + _ = x[MemoryKindCadenceAddressValue-52] + _ = x[MemoryKindCadenceIntValue-53] + _ = x[MemoryKindCadenceNumberValue-54] + _ = x[MemoryKindCadenceArrayValueBase-55] + _ = x[MemoryKindCadenceArrayValueLength-56] + _ = x[MemoryKindCadenceDictionaryValue-57] + _ = x[MemoryKindCadenceInclusiveRangeValue-58] + _ = x[MemoryKindCadenceKeyValuePair-59] + _ = x[MemoryKindCadenceStructValueBase-60] + _ = x[MemoryKindCadenceStructValueSize-61] + _ = x[MemoryKindCadenceResourceValueBase-62] + _ = x[MemoryKindCadenceAttachmentValueBase-63] + _ = x[MemoryKindCadenceResourceValueSize-64] + _ = x[MemoryKindCadenceAttachmentValueSize-65] + _ = x[MemoryKindCadenceEventValueBase-66] + _ = x[MemoryKindCadenceEventValueSize-67] + _ = x[MemoryKindCadenceContractValueBase-68] + _ = x[MemoryKindCadenceContractValueSize-69] + _ = x[MemoryKindCadenceEnumValueBase-70] + _ = x[MemoryKindCadenceEnumValueSize-71] + _ = x[MemoryKindCadencePathLinkValue-72] + _ = x[MemoryKindCadenceAccountLinkValue-73] + _ = x[MemoryKindCadencePathValue-74] + _ = x[MemoryKindCadenceTypeValue-75] + _ = x[MemoryKindCadenceIDCapabilityValue-76] + _ = x[MemoryKindCadencePathCapabilityValue-77] + _ = x[MemoryKindCadenceFunctionValue-78] + _ = x[MemoryKindCadenceOptionalType-79] + _ = x[MemoryKindCadenceVariableSizedArrayType-80] + _ = x[MemoryKindCadenceConstantSizedArrayType-81] + _ = x[MemoryKindCadenceDictionaryType-82] + _ = x[MemoryKindCadenceInclusiveRangeType-83] + _ = x[MemoryKindCadenceField-84] + _ = x[MemoryKindCadenceParameter-85] + _ = x[MemoryKindCadenceTypeParameter-86] + _ = x[MemoryKindCadenceStructType-87] + _ = x[MemoryKindCadenceResourceType-88] + _ = x[MemoryKindCadenceAttachmentType-89] + _ = x[MemoryKindCadenceEventType-90] + _ = x[MemoryKindCadenceContractType-91] + _ = x[MemoryKindCadenceStructInterfaceType-92] + _ = x[MemoryKindCadenceResourceInterfaceType-93] + _ = x[MemoryKindCadenceContractInterfaceType-94] + _ = x[MemoryKindCadenceFunctionType-95] + _ = x[MemoryKindCadenceReferenceType-96] + _ = x[MemoryKindCadenceRestrictedType-97] + _ = x[MemoryKindCadenceCapabilityType-98] + _ = x[MemoryKindCadenceEnumType-99] + _ = x[MemoryKindRawString-100] + _ = x[MemoryKindAddressLocation-101] + _ = x[MemoryKindBytes-102] + _ = x[MemoryKindVariable-103] + _ = x[MemoryKindCompositeTypeInfo-104] + _ = x[MemoryKindCompositeField-105] + _ = x[MemoryKindInvocation-106] + _ = x[MemoryKindStorageMap-107] + _ = x[MemoryKindStorageKey-108] + _ = x[MemoryKindTypeToken-109] + _ = x[MemoryKindErrorToken-110] + _ = x[MemoryKindSpaceToken-111] + _ = x[MemoryKindProgram-112] + _ = x[MemoryKindIdentifier-113] + _ = x[MemoryKindArgument-114] + _ = x[MemoryKindBlock-115] + _ = x[MemoryKindFunctionBlock-116] + _ = x[MemoryKindParameter-117] + _ = x[MemoryKindParameterList-118] + _ = x[MemoryKindTypeParameter-119] + _ = x[MemoryKindTypeParameterList-120] + _ = x[MemoryKindTransfer-121] + _ = x[MemoryKindMembers-122] + _ = x[MemoryKindTypeAnnotation-123] + _ = x[MemoryKindDictionaryEntry-124] + _ = x[MemoryKindFunctionDeclaration-125] + _ = x[MemoryKindCompositeDeclaration-126] + _ = x[MemoryKindAttachmentDeclaration-127] + _ = x[MemoryKindInterfaceDeclaration-128] + _ = x[MemoryKindEnumCaseDeclaration-129] + _ = x[MemoryKindFieldDeclaration-130] + _ = x[MemoryKindTransactionDeclaration-131] + _ = x[MemoryKindImportDeclaration-132] + _ = x[MemoryKindVariableDeclaration-133] + _ = x[MemoryKindSpecialFunctionDeclaration-134] + _ = x[MemoryKindPragmaDeclaration-135] + _ = x[MemoryKindAssignmentStatement-136] + _ = x[MemoryKindBreakStatement-137] + _ = x[MemoryKindContinueStatement-138] + _ = x[MemoryKindEmitStatement-139] + _ = x[MemoryKindExpressionStatement-140] + _ = x[MemoryKindForStatement-141] + _ = x[MemoryKindIfStatement-142] + _ = x[MemoryKindReturnStatement-143] + _ = x[MemoryKindSwapStatement-144] + _ = x[MemoryKindSwitchStatement-145] + _ = x[MemoryKindWhileStatement-146] + _ = x[MemoryKindRemoveStatement-147] + _ = x[MemoryKindBooleanExpression-148] + _ = x[MemoryKindVoidExpression-149] + _ = x[MemoryKindNilExpression-150] + _ = x[MemoryKindStringExpression-151] + _ = x[MemoryKindIntegerExpression-152] + _ = x[MemoryKindFixedPointExpression-153] + _ = x[MemoryKindArrayExpression-154] + _ = x[MemoryKindDictionaryExpression-155] + _ = x[MemoryKindIdentifierExpression-156] + _ = x[MemoryKindInvocationExpression-157] + _ = x[MemoryKindMemberExpression-158] + _ = x[MemoryKindIndexExpression-159] + _ = x[MemoryKindConditionalExpression-160] + _ = x[MemoryKindUnaryExpression-161] + _ = x[MemoryKindBinaryExpression-162] + _ = x[MemoryKindFunctionExpression-163] + _ = x[MemoryKindCastingExpression-164] + _ = x[MemoryKindCreateExpression-165] + _ = x[MemoryKindDestroyExpression-166] + _ = x[MemoryKindReferenceExpression-167] + _ = x[MemoryKindForceExpression-168] + _ = x[MemoryKindPathExpression-169] + _ = x[MemoryKindAttachExpression-170] + _ = x[MemoryKindConstantSizedType-171] + _ = x[MemoryKindDictionaryType-172] + _ = x[MemoryKindFunctionType-173] + _ = x[MemoryKindInstantiationType-174] + _ = x[MemoryKindNominalType-175] + _ = x[MemoryKindOptionalType-176] + _ = x[MemoryKindReferenceType-177] + _ = x[MemoryKindRestrictedType-178] + _ = x[MemoryKindVariableSizedType-179] + _ = x[MemoryKindPosition-180] + _ = x[MemoryKindRange-181] + _ = x[MemoryKindElaboration-182] + _ = x[MemoryKindActivation-183] + _ = x[MemoryKindActivationEntries-184] + _ = x[MemoryKindVariableSizedSemaType-185] + _ = x[MemoryKindConstantSizedSemaType-186] + _ = x[MemoryKindDictionarySemaType-187] + _ = x[MemoryKindOptionalSemaType-188] + _ = x[MemoryKindRestrictedSemaType-189] + _ = x[MemoryKindReferenceSemaType-190] + _ = x[MemoryKindCapabilitySemaType-191] + _ = x[MemoryKindInclusiveRangeSemaType-192] + _ = x[MemoryKindOrderedMap-193] + _ = x[MemoryKindOrderedMapEntryList-194] + _ = x[MemoryKindOrderedMapEntry-195] + _ = x[MemoryKindLast-196] } -const _MemoryKind_name = "UnknownAddressValueStringValueCharacterValueNumberValueArrayValueBaseDictionaryValueBaseCompositeValueBaseSimpleCompositeValueBaseOptionalValueTypeValuePathValueIDCapabilityValuePathCapabilityValuePathLinkValueAccountLinkValueStorageReferenceValueAccountReferenceValueEphemeralReferenceValueInterpretedFunctionValueHostFunctionValueBoundFunctionValueBigIntSimpleCompositeValuePublishedValueStorageCapabilityControllerValueAccountCapabilityControllerValueAtreeArrayDataSlabAtreeArrayMetaDataSlabAtreeArrayElementOverheadAtreeMapDataSlabAtreeMapMetaDataSlabAtreeMapElementOverheadAtreeMapPreAllocatedElementAtreeEncodedSlabPrimitiveStaticTypeCompositeStaticTypeInterfaceStaticTypeVariableSizedStaticTypeConstantSizedStaticTypeDictionaryStaticTypeOptionalStaticTypeRestrictedStaticTypeReferenceStaticTypeCapabilityStaticTypeFunctionStaticTypeCadenceVoidValueCadenceOptionalValueCadenceBoolValueCadenceStringValueCadenceCharacterValueCadenceAddressValueCadenceIntValueCadenceNumberValueCadenceArrayValueBaseCadenceArrayValueLengthCadenceDictionaryValueCadenceKeyValuePairCadenceStructValueBaseCadenceStructValueSizeCadenceResourceValueBaseCadenceAttachmentValueBaseCadenceResourceValueSizeCadenceAttachmentValueSizeCadenceEventValueBaseCadenceEventValueSizeCadenceContractValueBaseCadenceContractValueSizeCadenceEnumValueBaseCadenceEnumValueSizeCadencePathLinkValueCadenceAccountLinkValueCadencePathValueCadenceTypeValueCadenceIDCapabilityValueCadencePathCapabilityValueCadenceFunctionValueCadenceOptionalTypeCadenceVariableSizedArrayTypeCadenceConstantSizedArrayTypeCadenceDictionaryTypeCadenceFieldCadenceParameterCadenceTypeParameterCadenceStructTypeCadenceResourceTypeCadenceAttachmentTypeCadenceEventTypeCadenceContractTypeCadenceStructInterfaceTypeCadenceResourceInterfaceTypeCadenceContractInterfaceTypeCadenceFunctionTypeCadenceReferenceTypeCadenceRestrictedTypeCadenceCapabilityTypeCadenceEnumTypeRawStringAddressLocationBytesVariableCompositeTypeInfoCompositeFieldInvocationStorageMapStorageKeyTypeTokenErrorTokenSpaceTokenProgramIdentifierArgumentBlockFunctionBlockParameterParameterListTypeParameterTypeParameterListTransferMembersTypeAnnotationDictionaryEntryFunctionDeclarationCompositeDeclarationAttachmentDeclarationInterfaceDeclarationEnumCaseDeclarationFieldDeclarationTransactionDeclarationImportDeclarationVariableDeclarationSpecialFunctionDeclarationPragmaDeclarationAssignmentStatementBreakStatementContinueStatementEmitStatementExpressionStatementForStatementIfStatementReturnStatementSwapStatementSwitchStatementWhileStatementRemoveStatementBooleanExpressionVoidExpressionNilExpressionStringExpressionIntegerExpressionFixedPointExpressionArrayExpressionDictionaryExpressionIdentifierExpressionInvocationExpressionMemberExpressionIndexExpressionConditionalExpressionUnaryExpressionBinaryExpressionFunctionExpressionCastingExpressionCreateExpressionDestroyExpressionReferenceExpressionForceExpressionPathExpressionAttachExpressionConstantSizedTypeDictionaryTypeFunctionTypeInstantiationTypeNominalTypeOptionalTypeReferenceTypeRestrictedTypeVariableSizedTypePositionRangeElaborationActivationActivationEntriesVariableSizedSemaTypeConstantSizedSemaTypeDictionarySemaTypeOptionalSemaTypeRestrictedSemaTypeReferenceSemaTypeCapabilitySemaTypeOrderedMapOrderedMapEntryListOrderedMapEntryLast" +const _MemoryKind_name = "UnknownAddressValueStringValueCharacterValueNumberValueArrayValueBaseDictionaryValueBaseCompositeValueBaseSimpleCompositeValueBaseOptionalValueTypeValuePathValueIDCapabilityValuePathCapabilityValuePathLinkValueAccountLinkValueStorageReferenceValueAccountReferenceValueEphemeralReferenceValueInterpretedFunctionValueHostFunctionValueBoundFunctionValueBigIntSimpleCompositeValuePublishedValueStorageCapabilityControllerValueAccountCapabilityControllerValueAtreeArrayDataSlabAtreeArrayMetaDataSlabAtreeArrayElementOverheadAtreeMapDataSlabAtreeMapMetaDataSlabAtreeMapElementOverheadAtreeMapPreAllocatedElementAtreeEncodedSlabPrimitiveStaticTypeCompositeStaticTypeInterfaceStaticTypeVariableSizedStaticTypeConstantSizedStaticTypeDictionaryStaticTypeInclusiveRangeStaticTypeOptionalStaticTypeRestrictedStaticTypeReferenceStaticTypeCapabilityStaticTypeFunctionStaticTypeCadenceVoidValueCadenceOptionalValueCadenceBoolValueCadenceStringValueCadenceCharacterValueCadenceAddressValueCadenceIntValueCadenceNumberValueCadenceArrayValueBaseCadenceArrayValueLengthCadenceDictionaryValueCadenceInclusiveRangeValueCadenceKeyValuePairCadenceStructValueBaseCadenceStructValueSizeCadenceResourceValueBaseCadenceAttachmentValueBaseCadenceResourceValueSizeCadenceAttachmentValueSizeCadenceEventValueBaseCadenceEventValueSizeCadenceContractValueBaseCadenceContractValueSizeCadenceEnumValueBaseCadenceEnumValueSizeCadencePathLinkValueCadenceAccountLinkValueCadencePathValueCadenceTypeValueCadenceIDCapabilityValueCadencePathCapabilityValueCadenceFunctionValueCadenceOptionalTypeCadenceVariableSizedArrayTypeCadenceConstantSizedArrayTypeCadenceDictionaryTypeCadenceInclusiveRangeTypeCadenceFieldCadenceParameterCadenceTypeParameterCadenceStructTypeCadenceResourceTypeCadenceAttachmentTypeCadenceEventTypeCadenceContractTypeCadenceStructInterfaceTypeCadenceResourceInterfaceTypeCadenceContractInterfaceTypeCadenceFunctionTypeCadenceReferenceTypeCadenceRestrictedTypeCadenceCapabilityTypeCadenceEnumTypeRawStringAddressLocationBytesVariableCompositeTypeInfoCompositeFieldInvocationStorageMapStorageKeyTypeTokenErrorTokenSpaceTokenProgramIdentifierArgumentBlockFunctionBlockParameterParameterListTypeParameterTypeParameterListTransferMembersTypeAnnotationDictionaryEntryFunctionDeclarationCompositeDeclarationAttachmentDeclarationInterfaceDeclarationEnumCaseDeclarationFieldDeclarationTransactionDeclarationImportDeclarationVariableDeclarationSpecialFunctionDeclarationPragmaDeclarationAssignmentStatementBreakStatementContinueStatementEmitStatementExpressionStatementForStatementIfStatementReturnStatementSwapStatementSwitchStatementWhileStatementRemoveStatementBooleanExpressionVoidExpressionNilExpressionStringExpressionIntegerExpressionFixedPointExpressionArrayExpressionDictionaryExpressionIdentifierExpressionInvocationExpressionMemberExpressionIndexExpressionConditionalExpressionUnaryExpressionBinaryExpressionFunctionExpressionCastingExpressionCreateExpressionDestroyExpressionReferenceExpressionForceExpressionPathExpressionAttachExpressionConstantSizedTypeDictionaryTypeFunctionTypeInstantiationTypeNominalTypeOptionalTypeReferenceTypeRestrictedTypeVariableSizedTypePositionRangeElaborationActivationActivationEntriesVariableSizedSemaTypeConstantSizedSemaTypeDictionarySemaTypeOptionalSemaTypeRestrictedSemaTypeReferenceSemaTypeCapabilitySemaTypeInclusiveRangeSemaTypeOrderedMapOrderedMapEntryListOrderedMapEntryLast" -var _MemoryKind_index = [...]uint16{0, 7, 19, 30, 44, 55, 69, 88, 106, 130, 143, 152, 161, 178, 197, 210, 226, 247, 268, 291, 315, 332, 350, 356, 376, 390, 422, 454, 472, 494, 519, 535, 555, 578, 605, 621, 640, 659, 678, 701, 724, 744, 762, 782, 801, 821, 839, 855, 875, 891, 909, 930, 949, 964, 982, 1003, 1026, 1048, 1067, 1089, 1111, 1135, 1161, 1185, 1211, 1232, 1253, 1277, 1301, 1321, 1341, 1361, 1384, 1400, 1416, 1440, 1466, 1486, 1505, 1534, 1563, 1584, 1596, 1612, 1632, 1649, 1668, 1689, 1705, 1724, 1750, 1778, 1806, 1825, 1845, 1866, 1887, 1902, 1911, 1926, 1931, 1939, 1956, 1970, 1980, 1990, 2000, 2009, 2019, 2029, 2036, 2046, 2054, 2059, 2072, 2081, 2094, 2107, 2124, 2132, 2139, 2153, 2168, 2187, 2207, 2228, 2248, 2267, 2283, 2305, 2322, 2341, 2367, 2384, 2403, 2417, 2434, 2447, 2466, 2478, 2489, 2504, 2517, 2532, 2546, 2561, 2578, 2592, 2605, 2621, 2638, 2658, 2673, 2693, 2713, 2733, 2749, 2764, 2785, 2800, 2816, 2834, 2851, 2867, 2884, 2903, 2918, 2932, 2948, 2965, 2979, 2991, 3008, 3019, 3031, 3044, 3058, 3075, 3083, 3088, 3099, 3109, 3126, 3147, 3168, 3186, 3202, 3220, 3237, 3255, 3265, 3284, 3299, 3303} +var _MemoryKind_index = [...]uint16{0, 7, 19, 30, 44, 55, 69, 88, 106, 130, 143, 152, 161, 178, 197, 210, 226, 247, 268, 291, 315, 332, 350, 356, 376, 390, 422, 454, 472, 494, 519, 535, 555, 578, 605, 621, 640, 659, 678, 701, 724, 744, 768, 786, 806, 825, 845, 863, 879, 899, 915, 933, 954, 973, 988, 1006, 1027, 1050, 1072, 1098, 1117, 1139, 1161, 1185, 1211, 1235, 1261, 1282, 1303, 1327, 1351, 1371, 1391, 1411, 1434, 1450, 1466, 1490, 1516, 1536, 1555, 1584, 1613, 1634, 1659, 1671, 1687, 1707, 1724, 1743, 1764, 1780, 1799, 1825, 1853, 1881, 1900, 1920, 1941, 1962, 1977, 1986, 2001, 2006, 2014, 2031, 2045, 2055, 2065, 2075, 2084, 2094, 2104, 2111, 2121, 2129, 2134, 2147, 2156, 2169, 2182, 2199, 2207, 2214, 2228, 2243, 2262, 2282, 2303, 2323, 2342, 2358, 2380, 2397, 2416, 2442, 2459, 2478, 2492, 2509, 2522, 2541, 2553, 2564, 2579, 2592, 2607, 2621, 2636, 2653, 2667, 2680, 2696, 2713, 2733, 2748, 2768, 2788, 2808, 2824, 2839, 2860, 2875, 2891, 2909, 2926, 2942, 2959, 2978, 2993, 3007, 3023, 3040, 3054, 3066, 3083, 3094, 3106, 3119, 3133, 3150, 3158, 3163, 3174, 3184, 3201, 3222, 3243, 3261, 3277, 3295, 3312, 3330, 3352, 3362, 3381, 3396, 3400} func (i MemoryKind) String() string { if i >= MemoryKind(len(_MemoryKind_index)-1) { diff --git a/runtime/common/metering.go b/runtime/common/metering.go index 9135d75b01..974249e509 100644 --- a/runtime/common/metering.go +++ b/runtime/common/metering.go @@ -159,27 +159,29 @@ var ( // Static Types - PrimitiveStaticTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindPrimitiveStaticType) - CompositeStaticTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindCompositeStaticType) - InterfaceStaticTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindInterfaceStaticType) - VariableSizedStaticTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindVariableSizedStaticType) - ConstantSizedStaticTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindConstantSizedStaticType) - DictionaryStaticTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindDictionaryStaticType) - OptionalStaticTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindOptionalStaticType) - RestrictedStaticTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindRestrictedStaticType) - ReferenceStaticTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindReferenceStaticType) - CapabilityStaticTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindCapabilityStaticType) - FunctionStaticTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindFunctionStaticType) + PrimitiveStaticTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindPrimitiveStaticType) + CompositeStaticTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindCompositeStaticType) + InterfaceStaticTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindInterfaceStaticType) + VariableSizedStaticTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindVariableSizedStaticType) + ConstantSizedStaticTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindConstantSizedStaticType) + DictionaryStaticTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindDictionaryStaticType) + InclusiveRangeStaticTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindInclusiveRangeStaticType) + OptionalStaticTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindOptionalStaticType) + RestrictedStaticTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindRestrictedStaticType) + ReferenceStaticTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindReferenceStaticType) + CapabilityStaticTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindCapabilityStaticType) + FunctionStaticTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindFunctionStaticType) // Sema types - VariableSizedSemaTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindVariableSizedSemaType) - ConstantSizedSemaTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindConstantSizedSemaType) - DictionarySemaTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindDictionarySemaType) - OptionalSemaTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindOptionalSemaType) - RestrictedSemaTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindRestrictedSemaType) - ReferenceSemaTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindReferenceSemaType) - CapabilitySemaTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindCapabilitySemaType) + VariableSizedSemaTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindVariableSizedSemaType) + ConstantSizedSemaTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindConstantSizedSemaType) + DictionarySemaTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindDictionarySemaType) + InclusiveRangeSemaTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindInclusiveRangeSemaType) + OptionalSemaTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindOptionalSemaType) + RestrictedSemaTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindRestrictedSemaType) + ReferenceSemaTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindReferenceSemaType) + CapabilitySemaTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindCapabilitySemaType) // Storage related memory usages @@ -191,6 +193,7 @@ var ( // Cadence external values CadenceDictionaryValueMemoryUsage = NewConstantMemoryUsage(MemoryKindCadenceDictionaryValue) + CadenceInclusiveRangeValueMemoryUsage = NewConstantMemoryUsage(MemoryKindCadenceInclusiveRangeValue) CadenceArrayValueBaseMemoryUsage = NewConstantMemoryUsage(MemoryKindCadenceArrayValueBase) CadenceStructValueBaseMemoryUsage = NewConstantMemoryUsage(MemoryKindCadenceStructValueBase) CadenceResourceValueBaseMemoryUsage = NewConstantMemoryUsage(MemoryKindCadenceResourceValueBase) @@ -219,6 +222,7 @@ var ( CadenceContractInterfaceTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindCadenceContractInterfaceType) CadenceContractTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindCadenceContractType) CadenceDictionaryTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindCadenceDictionaryType) + CadenceInclusiveRangeTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindCadenceInclusiveRangeType) CadenceEnumTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindCadenceEnumType) CadenceEventTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindCadenceEventType) CadenceFunctionTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindCadenceFunctionType) @@ -264,12 +268,13 @@ var ( // Static types string representations - VariableSizedStaticTypeStringMemoryUsage = NewRawStringMemoryUsage(2) // [] - DictionaryStaticTypeStringMemoryUsage = NewRawStringMemoryUsage(4) // {: } - OptionalStaticTypeStringMemoryUsage = NewRawStringMemoryUsage(1) // ? - AuthReferenceStaticTypeStringMemoryUsage = NewRawStringMemoryUsage(5) // auth& - ReferenceStaticTypeStringMemoryUsage = NewRawStringMemoryUsage(1) // & - CapabilityStaticTypeStringMemoryUsage = NewRawStringMemoryUsage(12) // Capability<> + VariableSizedStaticTypeStringMemoryUsage = NewRawStringMemoryUsage(2) // [] + DictionaryStaticTypeStringMemoryUsage = NewRawStringMemoryUsage(4) // {: } + InclusiveRangeStaticTypeStringMemoryUsage = NewRawStringMemoryUsage(16) // InclusiveRange<> + OptionalStaticTypeStringMemoryUsage = NewRawStringMemoryUsage(1) // ? + AuthReferenceStaticTypeStringMemoryUsage = NewRawStringMemoryUsage(5) // auth& + ReferenceStaticTypeStringMemoryUsage = NewRawStringMemoryUsage(1) // & + CapabilityStaticTypeStringMemoryUsage = NewRawStringMemoryUsage(12) // Capability<> ) func UseMemory(gauge MemoryGauge, usage MemoryUsage) { diff --git a/runtime/convertTypes.go b/runtime/convertTypes.go index 886839dda7..aed9f8c937 100644 --- a/runtime/convertTypes.go +++ b/runtime/convertTypes.go @@ -62,6 +62,8 @@ func ExportMeteredType( return exportInterfaceType(gauge, t, results) case *sema.DictionaryType: return exportDictionaryType(gauge, t, results) + case *sema.InclusiveRangeType: + return exportInclusiveRangeType(gauge, t, results) case *sema.FunctionType: return exportFunctionType(gauge, t, results) case *sema.AddressType: @@ -402,6 +404,19 @@ func exportDictionaryType( ) } +func exportInclusiveRangeType( + gauge common.MemoryGauge, + t *sema.InclusiveRangeType, + results map[sema.TypeID]cadence.Type, +) *cadence.InclusiveRangeType { + convertedMemberType := ExportMeteredType(gauge, t.MemberType, results) + + return cadence.NewMeteredInclusiveRangeType( + gauge, + convertedMemberType, + ) +} + func exportFunctionType( gauge common.MemoryGauge, t *sema.FunctionType, @@ -627,6 +642,8 @@ func ImportType(memoryGauge common.MemoryGauge, t cadence.Type) interpreter.Stat ImportType(memoryGauge, t.KeyType), ImportType(memoryGauge, t.ElementType), ) + case *cadence.InclusiveRangeType: + return interpreter.NewInclusiveRangeStaticType(memoryGauge, ImportType(memoryGauge, t.ElementType)) case *cadence.StructType, *cadence.ResourceType, *cadence.EventType, diff --git a/runtime/convertValues.go b/runtime/convertValues.go index 82a2caa0c7..431dc90bdc 100644 --- a/runtime/convertValues.go +++ b/runtime/convertValues.go @@ -380,6 +380,16 @@ func exportCompositeValue( } } + switch semaType := semaType.(type) { + case *sema.CompositeType: + // Continue. + case *sema.InclusiveRangeType: + // InclusiveRange is stored as a CompositeValue but isn't a CompositeType. + return exportCompositeValueAsInclusiveRange(v, semaType, inter, locationRange, seenReferences) + default: + panic(errors.NewUnreachableError()) + } + compositeType, ok := semaType.(*sema.CompositeType) if !ok { panic(errors.NewUnreachableError()) @@ -613,6 +623,63 @@ func exportDictionaryValue( return dictionary.WithType(exportType), err } +func exportCompositeValueAsInclusiveRange( + v interpreter.Value, + inclusiveRangeType *sema.InclusiveRangeType, + inter *interpreter.Interpreter, + locationRange interpreter.LocationRange, + seenReferences seenReferences, +) ( + *cadence.InclusiveRange, + error, +) { + compositeValue, ok := v.(*interpreter.CompositeValue) + if !ok { + // InclusiveRange is stored as a CompositeValue. + panic(errors.NewUnreachableError()) + } + + getNonComputedField := func(fieldName string) (cadence.Value, error) { + fieldValue := compositeValue.GetField(inter, locationRange, fieldName) + if fieldValue == nil { + // Bug if the field is absent. + panic(errors.NewUnreachableError()) + } + + return exportValueWithInterpreter( + fieldValue, + inter, + locationRange, + seenReferences, + ) + } + + startValue, err := getNonComputedField(sema.InclusiveRangeTypeStartFieldName) + if err != nil { + return &cadence.InclusiveRange{}, err + } + + endValue, err := getNonComputedField(sema.InclusiveRangeTypeEndFieldName) + if err != nil { + return &cadence.InclusiveRange{}, err + } + + stepValue, err := getNonComputedField(sema.InclusiveRangeTypeStepFieldName) + if err != nil { + return &cadence.InclusiveRange{}, err + } + + inclusiveRange := cadence.NewMeteredInclusiveRange( + inter, + startValue, + endValue, + stepValue, + ) + + t := exportInclusiveRangeType(inter, inclusiveRangeType, map[sema.TypeID]cadence.Type{}) + return inclusiveRange.WithType(t), err +} + func exportPathLinkValue(v interpreter.PathLinkValue, inter *interpreter.Interpreter) (cadence.PathLink, error) { path, err := exportPathValue(inter, v.TargetPath) if err != nil { @@ -853,6 +920,8 @@ func (i valueImporter) importValue(value cadence.Value, expectedType sema.Type) v.EnumType.Fields, v.Fields, ) + case *cadence.InclusiveRange: + return i.importInclusiveRangeValue(v, expectedType) case cadence.TypeValue: return i.importTypeValue(v.StaticType) case cadence.PathCapability: @@ -1382,6 +1451,84 @@ func (i valueImporter) importDictionaryValue( ), nil } +func (i valueImporter) importInclusiveRangeValue( + v *cadence.InclusiveRange, + expectedType sema.Type, +) ( + *interpreter.CompositeValue, + error, +) { + + var memberType sema.Type + + inclusiveRangeType, ok := expectedType.(*sema.InclusiveRangeType) + if ok { + memberType = inclusiveRangeType.MemberType + } + + inter := i.inter + locationRange := i.locationRange + + // start, end and step. The order matters. + members := make([]interpreter.IntegerValue, 3) + + // import members. + for index, value := range []cadence.Value{v.Start, v.End, v.Step} { + importedValue, err := i.importValue(value, memberType) + if err != nil { + return nil, err + } + importedIntegerValue, ok := importedValue.(interpreter.IntegerValue) + if !ok { + return nil, errors.NewDefaultUserError( + "cannot import inclusiverange: start, end and step must be integers", + ) + } + + members[index] = importedIntegerValue + } + + if inclusiveRangeType == nil { + memberSemaType, err := inter.ConvertStaticToSemaType(members[0].StaticType(inter)) + if err != nil { + return nil, err + } + + memberType = memberSemaType + inclusiveRangeType = sema.NewInclusiveRangeType( + inter, + memberType, + ) + } + + inclusiveRangeStaticType, ok := interpreter.ConvertSemaToStaticType(inter, inclusiveRangeType).(interpreter.InclusiveRangeStaticType) + if !ok { + panic(errors.NewUnreachableError()) + } + + // Ensure that start, end and step have the same static type. + // Usually this validation would be done outside of this function in ConformsToStaticType but + // we do it here because the NewInclusiveRangeValueWithStep constructor performs validations + // which involve comparisons between these values and hence they need to be of the same static + // type. + if members[0].StaticType(inter) != members[1].StaticType(inter) || + members[0].StaticType(inter) != members[2].StaticType(inter) { + return nil, errors.NewDefaultUserError( + "cannot import inclusiverange: start, end and step must be of the same type", + ) + } + + return interpreter.NewInclusiveRangeValueWithStep( + inter, + locationRange, + members[0], + members[1], + members[2], + inclusiveRangeStaticType, + inclusiveRangeType, + ), nil +} + func (i valueImporter) importCompositeValue( kind common.CompositeKind, location Location, diff --git a/runtime/convertValues_test.go b/runtime/convertValues_test.go index eff3689143..4ea222dc19 100644 --- a/runtime/convertValues_test.go +++ b/runtime/convertValues_test.go @@ -1215,6 +1215,15 @@ func TestImportRuntimeType(t *testing.T) { ValueType: interpreter.PrimitiveStaticTypeInt, }, }, + { + label: "InclusiveRange", + actual: &cadence.InclusiveRangeType{ + ElementType: cadence.IntType{}, + }, + expected: interpreter.InclusiveRangeStaticType{ + ElementType: interpreter.PrimitiveStaticTypeInt, + }, + }, { label: "Reference", actual: &cadence.ReferenceType{ @@ -1411,6 +1420,187 @@ func TestExportAddressValue(t *testing.T) { assert.Equal(t, expected, actual) } +func TestExportInclusiveRangeValue(t *testing.T) { + + t.Parallel() + + t.Run("with_step", func(t *testing.T) { + + t.Parallel() + + script := ` + pub fun main(): InclusiveRange { + return InclusiveRange(10, 20, step: 2) + } + ` + + inclusiveRangeType := cadence.NewInclusiveRangeType(cadence.IntType{}) + + actual := cadence.ValueWithCachedTypeID(exportValueFromScript(t, script)) + expected := cadence.ValueWithCachedTypeID( + cadence.NewInclusiveRange( + cadence.NewInt(10), + cadence.NewInt(20), + cadence.NewInt(2)).WithType(inclusiveRangeType), + ) + + assert.Equal(t, expected, actual) + }) + + t.Run("without_step", func(t *testing.T) { + + t.Parallel() + + script := ` + pub fun main(): InclusiveRange { + return InclusiveRange(10, 20) + } + ` + + inclusiveRangeType := cadence.NewInclusiveRangeType(cadence.IntType{}) + + actual := cadence.ValueWithCachedTypeID(exportValueFromScript(t, script)) + expected := cadence.ValueWithCachedTypeID( + cadence.NewInclusiveRange( + cadence.NewInt(10), + cadence.NewInt(20), + cadence.NewInt(1)).WithType(inclusiveRangeType), + ) + + assert.Equal(t, expected, actual) + }) +} + +func TestImportInclusiveRangeValue(t *testing.T) { + + t.Parallel() + + t.Run("simple - InclusiveRange", func(t *testing.T) { + t.Parallel() + + value := cadence.NewInclusiveRange(cadence.NewInt(10), cadence.NewInt(-10), cadence.NewInt(-2)) + + inter := newTestInterpreter(t) + + actual, err := ImportValue( + inter, + interpreter.EmptyLocationRange, + nil, + value, + sema.NewInclusiveRangeType(inter, sema.IntType), + ) + require.NoError(t, err) + + AssertValuesEqual( + t, + inter, + interpreter.NewInclusiveRangeValueWithStep( + inter, + interpreter.EmptyLocationRange, + interpreter.NewIntValueFromInt64(inter, 10), + interpreter.NewIntValueFromInt64(inter, -10), + interpreter.NewIntValueFromInt64(inter, -2), + interpreter.InclusiveRangeStaticType{ + ElementType: interpreter.PrimitiveStaticTypeInt, + }, + sema.NewInclusiveRangeType(nil, sema.IntType), + ), + actual, + ) + }) + + t.Run("import with broader type - AnyStruct", func(t *testing.T) { + t.Parallel() + + value := cadence.NewInclusiveRange(cadence.NewInt(10), cadence.NewInt(-10), cadence.NewInt(-2)) + + inter := newTestInterpreter(t) + + actual, err := ImportValue( + inter, + interpreter.EmptyLocationRange, + nil, + value, + sema.AnyStructType, + ) + require.NoError(t, err) + + AssertValuesEqual( + t, + inter, + interpreter.NewInclusiveRangeValueWithStep( + inter, + interpreter.EmptyLocationRange, + interpreter.NewIntValueFromInt64(inter, 10), + interpreter.NewIntValueFromInt64(inter, -10), + interpreter.NewIntValueFromInt64(inter, -2), + interpreter.InclusiveRangeStaticType{ + ElementType: interpreter.PrimitiveStaticTypeInt, + }, + sema.NewInclusiveRangeType(nil, sema.IntType), + ), + actual, + ) + }) + + t.Run("invalid - mixed types", func(t *testing.T) { + t.Parallel() + + value := cadence.NewInclusiveRange(cadence.NewInt(10), cadence.NewUInt(100), cadence.NewUInt64(1)) + + inter := newTestInterpreter(t) + + _, err := ImportValue( + inter, + interpreter.EmptyLocationRange, + nil, + value, + sema.AnyStructType, + ) + + RequireError(t, err) + assertUserError(t, err) + + var userError errors.DefaultUserError + require.ErrorAs(t, err, &userError) + require.Contains( + t, + userError.Error(), + "cannot import inclusiverange: start, end and step must be of the same type", + ) + }) + + t.Run("invalid - InclusiveRange", func(t *testing.T) { + t.Parallel() + + strValue, err := cadence.NewString("anything") + require.NoError(t, err) + + value := cadence.NewInclusiveRange(strValue, strValue, strValue) + + inter := newTestInterpreter(t) + + _, err = ImportValue( + inter, + interpreter.EmptyLocationRange, + nil, + value, + sema.StringType, + ) + + RequireError(t, err) + assertUserError(t, err) + + var userError errors.DefaultUserError + require.ErrorAs(t, err, &userError) + require.Contains( + t, + userError.Error(), + "cannot import inclusiverange: start, end and step must be integers", + ) + }) +} + func TestExportStructValue(t *testing.T) { t.Parallel() @@ -2707,6 +2897,17 @@ func TestRuntimeArgumentPassing(t *testing.T) { ElementType: cadence.StringType{}, }), }, + { + label: "InclusiveRange", + typeSignature: "InclusiveRange", + exportedValue: cadence.NewInclusiveRange( + cadence.NewUInt128(1), + cadence.NewUInt128(500), + cadence.NewUInt128(25), + ).WithType(&cadence.InclusiveRangeType{ + ElementType: cadence.UInt128Type{}, + }), + }, { label: "Int", typeSignature: "Int", @@ -3387,6 +3588,16 @@ func TestRuntimeMalformedArgumentPassing(t *testing.T) { }), expectedInvalidEntryPointArgumentErrType: &MalformedValueError{}, }, + { + label: "Malformed inclusiverange", + typeSignature: "InclusiveRange", + exportedValue: cadence.NewInclusiveRange( + cadence.NewUInt(1), + cadence.NewUInt(10), + cadence.NewUInt(3), + ), + expectedInvalidEntryPointArgumentErrType: &MalformedValueError{}, + }, } testArgumentPassing := func(test argumentPassingTest) { diff --git a/runtime/imported_values_memory_metering_test.go b/runtime/imported_values_memory_metering_test.go index 822635d036..9c04ad2398 100644 --- a/runtime/imported_values_memory_metering_test.go +++ b/runtime/imported_values_memory_metering_test.go @@ -398,6 +398,29 @@ func TestImportedValueMemoryMetering(t *testing.T) { assert.Equal(t, uint64(1), meter[common.MemoryKindCompositeValueBase]) assert.Equal(t, uint64(71), meter[common.MemoryKindRawString]) }) + + t.Run("InclusiveRange", func(t *testing.T) { + t.Parallel() + + script := []byte(` + pub fun main(x: InclusiveRange) {} + `) + + meter := make(map[common.MemoryKind]uint64) + inclusiveRangeValue := &cadence.InclusiveRange{ + InclusiveRangeType: &cadence.InclusiveRangeType{ + ElementType: cadence.IntType{}, + }, + Start: cadence.NewInt(1), + End: cadence.NewInt(50), + Step: cadence.NewInt(2), + } + + executeScript(t, script, meter, inclusiveRangeValue) + assert.Equal(t, uint64(1), meter[common.MemoryKindCompositeValueBase]) + assert.Equal(t, uint64(1), meter[common.MemoryKindInclusiveRangeStaticType]) + assert.Equal(t, uint64(1), meter[common.MemoryKindCadenceInclusiveRangeValue]) + }) } type testMemoryError struct{} diff --git a/runtime/interpreter/decode.go b/runtime/interpreter/decode.go index e74a0d8c6b..284631334d 100644 --- a/runtime/interpreter/decode.go +++ b/runtime/interpreter/decode.go @@ -1428,6 +1428,9 @@ func (d TypeDecoder) DecodeStaticType() (StaticType, error) { case CBORTagDictionaryStaticType: return d.decodeDictionaryStaticType() + case CBORTagInclusiveRangeStaticType: + return d.decodeInclusiveRangeStaticType() + case CBORTagRestrictedStaticType: return d.decodeRestrictedStaticType() @@ -1730,6 +1733,17 @@ func (d TypeDecoder) decodeDictionaryStaticType() (StaticType, error) { return NewDictionaryStaticType(d.memoryGauge, keyType, valueType), nil } +func (d TypeDecoder) decodeInclusiveRangeStaticType() (StaticType, error) { + elementType, err := d.DecodeStaticType() + if err != nil { + return nil, errors.NewUnexpectedError( + "invalid inclusive range static type encoding: %w", + err, + ) + } + return NewInclusiveRangeStaticType(d.memoryGauge, elementType), nil +} + func (d TypeDecoder) decodeRestrictedStaticType() (StaticType, error) { const expectedLength = encodedRestrictedStaticTypeLength diff --git a/runtime/interpreter/encode.go b/runtime/interpreter/encode.go index 26565e4319..e199244140 100644 --- a/runtime/interpreter/encode.go +++ b/runtime/interpreter/encode.go @@ -218,6 +218,7 @@ const ( CBORTagReferenceStaticType CBORTagRestrictedStaticType CBORTagCapabilityStaticType + CBORTagInclusiveRangeStaticType // !!! *WARNING* !!! // ADD NEW TYPES *BEFORE* THIS WARNING. @@ -1496,6 +1497,25 @@ func (t DictionaryStaticType) Encode(e *cbor.StreamEncoder) error { return t.ValueType.Encode(e) } +// Encode encodes InclusiveRangeStaticType as +// +// cbor.Tag{ +// Number: CBORTagInclusiveRangeStaticType, +// Content: StaticType(v.Type), +// } +func (t InclusiveRangeStaticType) Encode(e *cbor.StreamEncoder) error { + // Encode tag number and array head + err := e.EncodeRawBytes([]byte{ + // tag number + 0xd8, CBORTagInclusiveRangeStaticType, + }) + if err != nil { + return err + } + + return t.ElementType.Encode(e) +} + // NOTE: NEVER change, only add/increment; ensure uint64 const ( // encodedRestrictedStaticTypeTypeFieldKey uint64 = 0 diff --git a/runtime/interpreter/encoding_test.go b/runtime/interpreter/encoding_test.go index ae1097b85d..8856aa772e 100644 --- a/runtime/interpreter/encoding_test.go +++ b/runtime/interpreter/encoding_test.go @@ -4222,6 +4222,68 @@ func TestEncodeDecodeTypeValue(t *testing.T) { ) }) + t.Run("inclusiverange, int", func(t *testing.T) { + + t.Parallel() + + value := TypeValue{ + Type: InclusiveRangeStaticType{ + ElementType: PrimitiveStaticTypeInt, + }, + } + + encoded := []byte{ + // tag + 0xd8, CBORTagTypeValue, + // array, 1 items follow + 0x81, + // tag + 0xd8, CBORTagInclusiveRangeStaticType, + // tag + 0xd8, CBORTagPrimitiveStaticType, + // positive integer 36 + 0x18, 0x24, + } + + testEncodeDecode(t, + encodeDecodeTest{ + value: value, + encoded: encoded, + }, + ) + }) + + t.Run("inclusiverange, uint256", func(t *testing.T) { + + t.Parallel() + + value := TypeValue{ + Type: InclusiveRangeStaticType{ + ElementType: PrimitiveStaticTypeUInt256, + }, + } + + encoded := []byte{ + // tag + 0xd8, CBORTagTypeValue, + // array, 1 items follow + 0x81, + // tag + 0xd8, CBORTagInclusiveRangeStaticType, + // tag + 0xd8, CBORTagPrimitiveStaticType, + // positive integer 50 + 0x18, 0x32, + } + + testEncodeDecode(t, + encodeDecodeTest{ + value: value, + encoded: encoded, + }, + ) + }) + t.Run("without static type", func(t *testing.T) { t.Parallel() @@ -4325,7 +4387,7 @@ func TestCBORTagValue(t *testing.T) { t.Parallel() t.Run("No new types added in between", func(t *testing.T) { - require.Equal(t, byte(222), byte(CBORTag_Count)) + require.Equal(t, byte(223), byte(CBORTag_Count)) }) } diff --git a/runtime/interpreter/errors.go b/runtime/interpreter/errors.go index 5abf9a6c61..431aa07fb6 100644 --- a/runtime/interpreter/errors.go +++ b/runtime/interpreter/errors.go @@ -1008,3 +1008,22 @@ func WrappedExternalError(err error) error { return errors.NewExternalError(err) } } + +// InclusiveRangeConstructionError + +type InclusiveRangeConstructionError struct { + LocationRange + Message string +} + +var _ errors.UserError = InclusiveRangeConstructionError{} + +func (InclusiveRangeConstructionError) IsUserError() {} + +func (e InclusiveRangeConstructionError) Error() string { + const message = "InclusiveRange construction failed" + if e.Message == "" { + return message + } + return fmt.Sprintf("%s: %s", message, e.Message) +} diff --git a/runtime/interpreter/integer.go b/runtime/interpreter/integer.go new file mode 100644 index 0000000000..3b32a694ad --- /dev/null +++ b/runtime/interpreter/integer.go @@ -0,0 +1,114 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * 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 ( + "sync" + + "github.com/onflow/cadence/runtime/errors" +) + +func GetSmallIntegerValue(value int8, staticType StaticType) IntegerValue { + return cachedSmallIntegerValues.Get(value, staticType) +} + +type integerValueCacheKey struct { + value int8 + staticType StaticType +} + +type smallIntegerValueCache struct { + m sync.Map +} + +var cachedSmallIntegerValues = smallIntegerValueCache{} + +func (c *smallIntegerValueCache) Get(value int8, staticType StaticType) IntegerValue { + key := integerValueCacheKey{ + value: value, + staticType: staticType, + } + + existingValue, ok := c.m.Load(key) + if ok { + return existingValue.(IntegerValue) + } + + newValue := c.new(value, staticType) + c.m.Store(key, newValue) + return newValue +} + +// getValueForIntegerType returns a Cadence integer value +// of the given Cadence static type for the given Go integer value. +// +// It is important NOT to meter the memory usage in this function, +// as it would lead to non-determinism as the values produced by this function are cached. +// It could happen that on some execution nodes the value might be cached due to executing a +// transaction or script that needed the value previously, while on other execution nodes it might +// not be cached yet. +func (c *smallIntegerValueCache) new(value int8, staticType StaticType) IntegerValue { + switch staticType { + case PrimitiveStaticTypeInt: + return NewUnmeteredIntValueFromInt64(int64(value)) + case PrimitiveStaticTypeInt8: + return NewUnmeteredInt8Value(value) + case PrimitiveStaticTypeInt16: + return NewUnmeteredInt16Value(int16(value)) + case PrimitiveStaticTypeInt32: + return NewUnmeteredInt32Value(int32(value)) + case PrimitiveStaticTypeInt64: + return NewUnmeteredInt64Value(int64(value)) + case PrimitiveStaticTypeInt128: + return NewUnmeteredInt128ValueFromInt64(int64(value)) + case PrimitiveStaticTypeInt256: + return NewUnmeteredInt256ValueFromInt64(int64(value)) + + case PrimitiveStaticTypeUInt: + return NewUnmeteredUIntValueFromUint64(uint64(value)) + case PrimitiveStaticTypeUInt8: + return NewUnmeteredUInt8Value(uint8(value)) + case PrimitiveStaticTypeUInt16: + return NewUnmeteredUInt16Value(uint16(value)) + case PrimitiveStaticTypeUInt32: + return NewUnmeteredUInt32Value(uint32(value)) + case PrimitiveStaticTypeUInt64: + return NewUnmeteredUInt64Value(uint64(value)) + case PrimitiveStaticTypeUInt128: + return NewUnmeteredUInt128ValueFromUint64(uint64(value)) + case PrimitiveStaticTypeUInt256: + return NewUnmeteredUInt256ValueFromUint64(uint64(value)) + + case PrimitiveStaticTypeWord8: + return NewUnmeteredWord8Value(uint8(value)) + case PrimitiveStaticTypeWord16: + return NewUnmeteredWord16Value(uint16(value)) + case PrimitiveStaticTypeWord32: + return NewUnmeteredWord32Value(uint32(value)) + case PrimitiveStaticTypeWord64: + return NewUnmeteredWord64Value(uint64(value)) + case PrimitiveStaticTypeWord128: + return NewUnmeteredWord128ValueFromUint64(uint64(value)) + case PrimitiveStaticTypeWord256: + return NewUnmeteredWord256ValueFromUint64(uint64(value)) + + default: + panic(errors.NewUnreachableError()) + } +} diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index de338e7b8d..8fc3f70c1e 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -3551,6 +3551,38 @@ var runtimeTypeConstructors = []runtimeTypeConstructor{ }, ), }, + { + name: "InclusiveRangeType", + converter: NewUnmeteredHostFunctionValue( + sema.InclusiveRangeTypeFunctionType, + func(invocation Invocation) Value { + typeValue, ok := invocation.Arguments[0].(TypeValue) + if !ok { + panic(errors.NewUnreachableError()) + } + + inter := invocation.Interpreter + + ty := typeValue.Type + // InclusiveRanges must hold integers + elemSemaTy := inter.MustConvertStaticToSemaType(ty) + if !sema.IsSameTypeKind(elemSemaTy, sema.IntegerType) { + return Nil + } + + return NewSomeValueNonCopying( + inter, + NewTypeValue( + inter, + NewInclusiveRangeStaticType( + inter, + ty, + ), + ), + ) + }, + ), + }, } func defineRuntimeTypeConstructorFunctions(activation *VariableActivation) { diff --git a/runtime/interpreter/statictype.go b/runtime/interpreter/statictype.go index adb270488e..d81e2d24b2 100644 --- a/runtime/interpreter/statictype.go +++ b/runtime/interpreter/statictype.go @@ -291,6 +291,57 @@ func (t VariableSizedStaticType) ID() TypeID { return sema.VariableSizedTypeID(t.Type.ID()) } +// InclusiveRangeStaticType + +type InclusiveRangeStaticType struct { + ElementType StaticType +} + +var _ StaticType = InclusiveRangeStaticType{} +var _ atree.TypeInfo = InclusiveRangeStaticType{} + +func NewInclusiveRangeStaticType( + memoryGauge common.MemoryGauge, + elementType StaticType, +) InclusiveRangeStaticType { + common.UseMemory(memoryGauge, common.InclusiveRangeStaticTypeMemoryUsage) + + return InclusiveRangeStaticType{ + ElementType: elementType, + } +} + +func (InclusiveRangeStaticType) isStaticType() {} + +func (InclusiveRangeStaticType) elementSize() uint { + return UnknownElementSize +} + +func (t InclusiveRangeStaticType) String() string { + return t.MeteredString(nil) +} + +func (t InclusiveRangeStaticType) MeteredString(memoryGauge common.MemoryGauge) string { + common.UseMemory(memoryGauge, common.InclusiveRangeStaticTypeStringMemoryUsage) + + elementStr := t.ElementType.MeteredString(memoryGauge) + + return fmt.Sprintf("InclusiveRange<%s>", elementStr) +} + +func (t InclusiveRangeStaticType) Equal(other StaticType) bool { + otherRangeType, ok := other.(InclusiveRangeStaticType) + if !ok { + return false + } + + return t.ElementType.Equal(otherRangeType.ElementType) +} + +func (t InclusiveRangeStaticType) ID() TypeID { + return sema.InclusiveRangeTypeID(string(t.ElementType.ID())) +} + // ConstantSizedStaticType type ConstantSizedStaticType struct { @@ -775,6 +826,10 @@ func ConvertSemaToStaticType(memoryGauge common.MemoryGauge, t sema.Type) Static borrowType := ConvertSemaToStaticType(memoryGauge, t.BorrowType) return NewCapabilityStaticType(memoryGauge, borrowType) + case *sema.InclusiveRangeType: + memberType := ConvertSemaToStaticType(memoryGauge, t.MemberType) + return NewInclusiveRangeStaticType(memoryGauge, memberType) + case *sema.FunctionType: return NewFunctionStaticType(memoryGauge, t) } @@ -899,6 +954,17 @@ func ConvertStaticToSemaType( valueType, ), nil + case InclusiveRangeStaticType: + elementType, err := ConvertStaticToSemaType(memoryGauge, t.ElementType, getInterface, getComposite) + if err != nil { + return nil, err + } + + return sema.NewInclusiveRangeType( + memoryGauge, + elementType, + ), nil + case OptionalStaticType: ty, err := ConvertStaticToSemaType(memoryGauge, t.Type, getInterface, getComposite) if err != nil { diff --git a/runtime/interpreter/statictype_test.go b/runtime/interpreter/statictype_test.go index 43ac96c283..93fbd88a0f 100644 --- a/runtime/interpreter/statictype_test.go +++ b/runtime/interpreter/statictype_test.go @@ -687,6 +687,56 @@ func TestDictionaryStaticType_Equal(t *testing.T) { }) } +func TestInclusiveRangeStaticType_Equal(t *testing.T) { + + t.Parallel() + + t.Run("equal", func(t *testing.T) { + + t.Parallel() + + require.True(t, + InclusiveRangeStaticType{ + ElementType: PrimitiveStaticTypeInt256, + }.Equal( + InclusiveRangeStaticType{ + ElementType: PrimitiveStaticTypeInt256, + }, + ), + ) + }) + + t.Run("different member types", func(t *testing.T) { + + t.Parallel() + + require.False(t, + InclusiveRangeStaticType{ + ElementType: PrimitiveStaticTypeInt, + }.Equal( + InclusiveRangeStaticType{ + ElementType: PrimitiveStaticTypeWord256, + }, + ), + ) + }) + + t.Run("different kind", func(t *testing.T) { + + t.Parallel() + + require.False(t, + InclusiveRangeStaticType{ + ElementType: PrimitiveStaticTypeInt, + }.Equal( + VariableSizedStaticType{ + Type: PrimitiveStaticTypeInt, + }, + ), + ) + }) +} + func TestRestrictedStaticType_Equal(t *testing.T) { t.Parallel() @@ -1302,6 +1352,15 @@ func TestStaticTypeConversion(t *testing.T) { ValueType: PrimitiveStaticTypeString, }, }, + { + name: "InclusiveRange", + semaType: &sema.InclusiveRangeType{ + MemberType: sema.IntType, + }, + staticType: InclusiveRangeStaticType{ + ElementType: PrimitiveStaticTypeInt, + }, + }, { name: "Restricted", semaType: &sema.RestrictedType{ diff --git a/runtime/interpreter/storage_test.go b/runtime/interpreter/storage_test.go index b97cbc784a..23a0d73b63 100644 --- a/runtime/interpreter/storage_test.go +++ b/runtime/interpreter/storage_test.go @@ -86,6 +86,62 @@ func TestCompositeStorage(t *testing.T) { ) } +func TestInclusiveRangeStorage(t *testing.T) { + + t.Parallel() + + storage := newUnmeteredInMemoryStorage() + + inter, err := NewInterpreter( + nil, + common.AddressLocation{}, + &Config{Storage: storage}, + ) + require.NoError(t, err) + + value := NewInclusiveRangeValueWithStep( + inter, + EmptyLocationRange, + NewUnmeteredInt16Value(1), + NewUnmeteredInt16Value(100), + NewUnmeteredInt16Value(5), + NewInclusiveRangeStaticType(inter, PrimitiveStaticTypeInt16), + sema.NewInclusiveRangeType(inter, sema.Int16Type), + ) + + require.NotEqual(t, atree.StorageIDUndefined, value.StorageID()) + + require.Equal(t, 1, storage.BasicSlabStorage.Count()) + + _, ok, err := storage.BasicSlabStorage.Retrieve(value.StorageID()) + require.NoError(t, err) + require.True(t, ok) + + // Ensure that updating a field (e.g. step) works + const stepFieldName = "step" + + value.SetMember(inter, EmptyLocationRange, stepFieldName, NewUnmeteredInt16Value(10)) + + require.Equal(t, 1, storage.BasicSlabStorage.Count()) + + retrievedStorable, ok, err := storage.BasicSlabStorage.Retrieve(value.StorageID()) + require.NoError(t, err) + require.True(t, ok) + + storedValue := StoredValue(inter, retrievedStorable, storage) + + // InclusiveRange is stored as a CompositeValue. + require.IsType(t, storedValue, &CompositeValue{}) + storedComposite := storedValue.(*CompositeValue) + + RequireValuesEqual( + t, + inter, + NewUnmeteredInt16Value(10), + storedComposite.GetField(inter, EmptyLocationRange, stepFieldName), + ) +} + func TestArrayStorage(t *testing.T) { t.Parallel() diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index 8a7aa890d2..1fd6503f34 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -16231,8 +16231,12 @@ func (UFix64Value) Scale() int { // CompositeValue type CompositeValue struct { - Destructor FunctionValue - Location common.Location + Destructor FunctionValue + Location common.Location + + // note that the staticType is not guaranteed to be a CompositeStaticType as there can be types + // which are non-composite but their values are treated as CompositeValue. + // For e.g. InclusiveRangeValue staticType StaticType Stringer func(gauge common.MemoryGauge, value *CompositeValue, seenReferences SeenReferences) string InjectedFields map[string]Value @@ -16276,6 +16280,33 @@ func NewUnmeteredCompositeField(name string, value Value) CompositeField { } } +// Create a CompositeValue with the provided StaticType. +// Useful when we wish to utilize CompositeValue as the value +// for a type which isn't CompositeType. +// For e.g. InclusiveRangeType +func NewCompositeValueWithStaticType( + interpreter *Interpreter, + locationRange LocationRange, + location common.Location, + qualifiedIdentifier string, + kind common.CompositeKind, + fields []CompositeField, + address common.Address, + staticType StaticType, +) *CompositeValue { + value := NewCompositeValue( + interpreter, + locationRange, + location, + qualifiedIdentifier, + kind, + fields, + address, + ) + value.staticType = staticType + return value +} + func NewCompositeValue( interpreter *Interpreter, locationRange LocationRange, @@ -17045,10 +17076,29 @@ func (v *CompositeValue) ConformsToStaticType( }() } - staticType := v.StaticType(interpreter).(CompositeStaticType) - + staticType := v.StaticType(interpreter) semaType := interpreter.MustConvertStaticToSemaType(staticType) + switch staticType.(type) { + case CompositeStaticType: + return v.CompositeStaticTypeConformsToStaticType(interpreter, locationRange, results, semaType) + + // CompositeValue is also used for storing types which aren't CompositeStaticType. + // E.g. InclusiveRange. + case InclusiveRangeStaticType: + return v.InclusiveRangeStaticTypeConformsToStaticType(interpreter, locationRange, results, semaType) + + default: + return false + } +} + +func (v *CompositeValue) CompositeStaticTypeConformsToStaticType( + interpreter *Interpreter, + locationRange LocationRange, + results TypeConformanceResults, + semaType sema.Type, +) bool { compositeType, ok := semaType.(*sema.CompositeType) if !ok || v.Kind != compositeType.Kind || @@ -17111,6 +17161,42 @@ func (v *CompositeValue) ConformsToStaticType( return true } +func (v *CompositeValue) InclusiveRangeStaticTypeConformsToStaticType( + interpreter *Interpreter, + locationRange LocationRange, + results TypeConformanceResults, + semaType sema.Type, +) bool { + inclusiveRangeType, ok := semaType.(*sema.InclusiveRangeType) + if !ok { + return false + } + + expectedMemberStaticType := ConvertSemaToStaticType(interpreter, inclusiveRangeType.MemberType) + for _, fieldName := range sema.InclusiveRangeTypeFieldNames { + value := v.GetField(interpreter, locationRange, fieldName) + + fieldStaticType := value.StaticType(interpreter) + + // InclusiveRange is non-covariant. + // For e.g. we disallow assigning InclusiveRange to an InclusiveRange. + // Hence we do an exact equality check instead of a sub-type check. + if !fieldStaticType.Equal(expectedMemberStaticType) { + return false + } + + if !value.ConformsToStaticType( + interpreter, + locationRange, + results, + ) { + return false + } + } + + return true +} + func (v *CompositeValue) IsStorable() bool { // Only structures, resources, enums, and contracts can be stored. diff --git a/runtime/interpreter/value_range.go b/runtime/interpreter/value_range.go new file mode 100644 index 0000000000..835caac068 --- /dev/null +++ b/runtime/interpreter/value_range.go @@ -0,0 +1,269 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * 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 ( + "fmt" + + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/errors" + "github.com/onflow/cadence/runtime/sema" +) + +// NewInclusiveRangeValue constructs an InclusiveRange value with the provided start, end with default value of step. +// NOTE: Assumes that the values start and end are of the same static type. +func NewInclusiveRangeValue( + interpreter *Interpreter, + locationRange LocationRange, + start IntegerValue, + end IntegerValue, + rangeStaticType InclusiveRangeStaticType, + rangeSemaType *sema.InclusiveRangeType, +) *CompositeValue { + startComparable, startOk := start.(ComparableValue) + endComparable, endOk := end.(ComparableValue) + if !startOk || !endOk { + panic(errors.NewUnreachableError()) + } + + step := GetSmallIntegerValue(1, rangeStaticType.ElementType) + if startComparable.Greater(interpreter, endComparable, locationRange) { + elemSemaTy := interpreter.MustConvertStaticToSemaType(rangeStaticType.ElementType) + if elemSemaTy.Tag().BelongsTo(sema.UnsignedIntegerTypeTag) { + panic(InclusiveRangeConstructionError{ + LocationRange: locationRange, + Message: fmt.Sprintf( + "step value cannot be negative for unsigned integer type %s", + elemSemaTy, + ), + }) + } + + negatedStep, ok := step.Negate(interpreter, locationRange).(IntegerValue) + if !ok { + panic(errors.NewUnreachableError()) + } + + step = negatedStep + } + + return createInclusiveRange( + interpreter, + locationRange, + start, + end, + step, + rangeStaticType, + rangeSemaType, + ) +} + +// NewInclusiveRangeValue constructs an InclusiveRange value with the provided start, end & step. +// NOTE: Assumes that the values start, end and step are of the same static type. +func NewInclusiveRangeValueWithStep( + interpreter *Interpreter, + locationRange LocationRange, + start IntegerValue, + end IntegerValue, + step IntegerValue, + rangeType InclusiveRangeStaticType, + rangeSemaType *sema.InclusiveRangeType, +) *CompositeValue { + + zeroValue := GetSmallIntegerValue(0, start.StaticType(interpreter)) + + // Validate that the step is non-zero. + if step.Equal(interpreter, locationRange, zeroValue) { + panic(InclusiveRangeConstructionError{ + LocationRange: locationRange, + Message: "step value cannot be zero", + }) + } + + // Validate that the sequence is moving towards the end value. + // If start < end, step must be > 0 + // If start > end, step must be < 0 + // If start == end, step doesn't matter. + if isSequenceMovingAwayFromEnd(interpreter, locationRange, start, end, step, zeroValue) { + + panic(InclusiveRangeConstructionError{ + LocationRange: locationRange, + Message: fmt.Sprintf( + "sequence is moving away from end: %s due to the value of step: %s and start: %s", + end, + step, + start, + ), + }) + } + + return createInclusiveRange( + interpreter, + locationRange, + start, + end, + step, + rangeType, + rangeSemaType, + ) +} + +func createInclusiveRange( + interpreter *Interpreter, + locationRange LocationRange, + start IntegerValue, + end IntegerValue, + step IntegerValue, + rangeType InclusiveRangeStaticType, + rangeSemaType *sema.InclusiveRangeType, +) *CompositeValue { + fields := []CompositeField{ + { + Name: sema.InclusiveRangeTypeStartFieldName, + Value: start, + }, + { + Name: sema.InclusiveRangeTypeEndFieldName, + Value: end, + }, + { + Name: sema.InclusiveRangeTypeStepFieldName, + Value: step, + }, + } + + rangeValue := NewCompositeValueWithStaticType( + interpreter, + locationRange, + nil, + rangeSemaType.QualifiedString(), + common.CompositeKindStructure, + fields, + common.ZeroAddress, + rangeType, + ) + + rangeValue.Functions = map[string]FunctionValue{ + sema.InclusiveRangeTypeContainsFunctionName: NewHostFunctionValue( + interpreter, + sema.InclusiveRangeContainsFunctionType( + rangeSemaType.MemberType, + ), + func(invocation Invocation) Value { + needleInteger := convertAndAssertIntegerValue(invocation.Arguments[0]) + + return rangeContains( + rangeValue, + rangeType, + invocation.Interpreter, + invocation.LocationRange, + needleInteger, + ) + }, + ), + } + + return rangeValue +} + +func rangeContains( + rangeValue *CompositeValue, + rangeType InclusiveRangeStaticType, + interpreter *Interpreter, + locationRange LocationRange, + needleValue IntegerValue, +) BoolValue { + start := getFieldAsIntegerValue(rangeValue, interpreter, locationRange, sema.InclusiveRangeTypeStartFieldName) + end := getFieldAsIntegerValue(rangeValue, interpreter, locationRange, sema.InclusiveRangeTypeEndFieldName) + step := getFieldAsIntegerValue(rangeValue, interpreter, locationRange, sema.InclusiveRangeTypeStepFieldName) + + result := start.Equal(interpreter, locationRange, needleValue) || + end.Equal(interpreter, locationRange, needleValue) + + if result { + return TrueValue + } + + // Exclusive check since we already checked for boundaries above. + if !isNeedleBetweenStartEndExclusive(interpreter, locationRange, needleValue, start, end) { + result = false + } else { + // needle is in between start and end. + // start + k * step should be equal to needle i.e. (needle - start) mod step == 0. + diff, ok := needleValue.Minus(interpreter, start, locationRange).(IntegerValue) + if !ok { + panic(errors.NewUnreachableError()) + } + + zeroValue := GetSmallIntegerValue(0, rangeType.ElementType) + mod := diff.Mod(interpreter, step, locationRange) + result = mod.Equal(interpreter, locationRange, zeroValue) + } + + return AsBoolValue(result) +} + +func getFieldAsIntegerValue( + rangeValue *CompositeValue, + interpreter *Interpreter, + locationRange LocationRange, + name string, +) IntegerValue { + return convertAndAssertIntegerValue( + rangeValue.GetField( + interpreter, + locationRange, + name, + ), + ) +} + +func isNeedleBetweenStartEndExclusive( + interpreter *Interpreter, + locationRange LocationRange, + needleValue IntegerValue, + start IntegerValue, + end IntegerValue, +) bool { + greaterThanStart := needleValue.Greater(interpreter, start, locationRange) + greaterThanEnd := needleValue.Greater(interpreter, end, locationRange) + + // needle is in between start and end values if is greater than one and smaller than the other. + return bool(greaterThanStart) != bool(greaterThanEnd) +} + +func isSequenceMovingAwayFromEnd( + interpreter *Interpreter, + locationRange LocationRange, + start IntegerValue, + end IntegerValue, + step IntegerValue, + zeroValue IntegerValue, +) BoolValue { + return (start.Less(interpreter, end, locationRange) && step.Less(interpreter, zeroValue, locationRange)) || + (start.Greater(interpreter, end, locationRange) && step.Greater(interpreter, zeroValue, locationRange)) +} + +func convertAndAssertIntegerValue(value Value) IntegerValue { + integerValue, ok := value.(IntegerValue) + if !ok { + panic(errors.NewUnreachableError()) + } + return integerValue +} diff --git a/runtime/program_params_validation_test.go b/runtime/program_params_validation_test.go index bc2f464e1e..9f33f24648 100644 --- a/runtime/program_params_validation_test.go +++ b/runtime/program_params_validation_test.go @@ -285,6 +285,99 @@ func TestRuntimeScriptParameterTypeValidation(t *testing.T) { assert.NoError(t, err) }) + t.Run("InclusiveRange", func(t *testing.T) { + t.Parallel() + + script := ` + pub fun main(arg: InclusiveRange) { + } + ` + + err := executeScript( + t, + script, + cadence.NewInclusiveRange(cadence.NewInt16(1), cadence.NewInt16(2), cadence.NewInt16(1)), + ) + + assert.NoError(t, err) + }) + + t.Run("InclusiveRange as AnyStruct", func(t *testing.T) { + t.Parallel() + + script := ` + pub fun main(arg: AnyStruct) { + } + ` + + err := executeScript( + t, + script, + cadence.NewInclusiveRange( + cadence.NewUInt16(1), + cadence.NewUInt16(2), + cadence.NewUInt16(1), + ), + ) + + assert.NoError(t, err) + }) + + // Since InclusiveRange isn't covariant. + t.Run("Invalid InclusiveRange", func(t *testing.T) { + t.Parallel() + + script := ` + pub fun main(arg: InclusiveRange) { + } + ` + + err := executeScript( + t, + script, + cadence.NewInclusiveRange(cadence.NewInt16(1), cadence.NewInt16(2), cadence.NewInt16(1)), + ) + + var entryPointErr *InvalidEntryPointArgumentError + require.ErrorAs(t, err, &entryPointErr) + }) + + t.Run("Invalid InclusiveRange with mixed value types", func(t *testing.T) { + t.Parallel() + + script := ` + pub fun main(arg: InclusiveRange) { + } + ` + + err := executeScript( + t, + script, + cadence.NewInclusiveRange(cadence.NewInt16(1), cadence.NewUInt(2), cadence.NewUInt(1)), + ) + + var entryPointErr *InvalidEntryPointArgumentError + require.ErrorAs(t, err, &entryPointErr) + }) + + t.Run("Invalid InclusiveRange with mixed value types", func(t *testing.T) { + t.Parallel() + + script := ` + pub fun main(arg: InclusiveRange) { + } + ` + + err := executeScript( + t, + script, + cadence.NewInclusiveRange(cadence.NewInt16(1), cadence.NewUInt(2), cadence.NewUInt(1)), + ) + + var entryPointErr *InvalidEntryPointArgumentError + require.ErrorAs(t, err, &entryPointErr) + }) + t.Run("Capability", func(t *testing.T) { t.Parallel() diff --git a/runtime/runtime_test.go b/runtime/runtime_test.go index 8f1af02fb3..478876289b 100644 --- a/runtime/runtime_test.go +++ b/runtime/runtime_test.go @@ -2365,6 +2365,69 @@ func TestRuntimeCompositeFunctionInvocationFromImportingProgram(t *testing.T) { require.NoError(t, err) } +func TestRuntimeStorageMultipleTransactionsInclusiveRangeFunction(t *testing.T) { + + t.Parallel() + + runtime := newTestInterpreterRuntime() + + inclusiveRangeCreation := []byte(` + pub fun createInclusiveRange(): InclusiveRange { + return InclusiveRange(10, 20) + } + `) + + script1 := []byte(` + import "inclusive-range-creation" + transaction { + prepare(signer: AuthAccount) { + let ir = createInclusiveRange() + signer.save(ir, to: /storage/inclusiveRange) + } + } + `) + + ledger := newTestLedger(nil, nil) + + runtimeInterface := &testRuntimeInterface{ + getCode: func(location Location) (bytes []byte, err error) { + switch location { + case common.StringLocation("inclusive-range-creation"): + return inclusiveRangeCreation, nil + default: + return nil, fmt.Errorf("unknown import location: %s", location) + } + }, + storage: ledger, + getSigningAccounts: func() ([]Address, error) { + return []Address{{42}}, nil + }, + } + + nextTransactionLocation := newTransactionLocationGenerator() + + err := runtime.ExecuteTransaction( + Script{ + Source: script1, + }, + Context{ + Interface: runtimeInterface, + Location: nextTransactionLocation(), + }, + ) + RequireError(t, err) + + var checkerErr *sema.CheckerError + require.ErrorAs(t, err, &checkerErr) + + errs := checker.RequireCheckerErrors(t, checkerErr, 1) + + assert.IsType(t, &sema.TypeMismatchError{}, errs[0]) + + typeMismatchError := errs[0].(*sema.TypeMismatchError) + assert.Contains(t, typeMismatchError.SecondaryError(), "expected `Storable`, got `InclusiveRange`") +} + func TestRuntimeResourceContractUseThroughReference(t *testing.T) { t.Parallel() diff --git a/runtime/sema/checker.go b/runtime/sema/checker.go index 8706de2f69..fbe9759092 100644 --- a/runtime/sema/checker.go +++ b/runtime/sema/checker.go @@ -2048,6 +2048,7 @@ func (checker *Checker) checkTypeAnnotation(typeAnnotation TypeAnnotation, pos a } checker.checkInvalidInterfaceAsType(typeAnnotation.Type, pos) + checker.checkParameterizedTypeIsInstantiated(typeAnnotation.Type, pos) } func (checker *Checker) checkInvalidInterfaceAsType(ty Type, pos ast.HasPosition) { @@ -2067,6 +2068,49 @@ func (checker *Checker) checkInvalidInterfaceAsType(ty Type, pos ast.HasPosition } } +func (checker *Checker) checkParameterizedTypeIsInstantiated(ty Type, pos ast.HasPosition) { + parameterizedType, ok := ty.(ParameterizedType) + if !ok { + return + } + + typeArgs := parameterizedType.TypeArguments() + typeParameters := parameterizedType.TypeParameters() + + typeArgumentCount := len(typeArgs) + typeParameterCount := len(typeParameters) + + if typeArgumentCount != typeParameterCount { + checker.report( + &InvalidTypeArgumentCountError{ + TypeParameterCount: typeParameterCount, + TypeArgumentCount: typeArgumentCount, + Range: ast.NewRange( + checker.memoryGauge, + pos.StartPosition(), + pos.EndPosition(checker.memoryGauge), + ), + }, + ) + } + + // Ensure that each non-optional typeparameter is non-nil. + for index, typeParam := range typeParameters { + if !typeParam.Optional && typeArgs[index] == nil { + checker.report( + &MissingTypeArgumentError{ + TypeArgumentName: typeParam.Name, + Range: ast.NewRange( + checker.memoryGauge, + pos.StartPosition(), + pos.EndPosition(checker.memoryGauge), + ), + }, + ) + } + } +} + func (checker *Checker) ValueActivationDepth() int { return checker.valueActivations.Depth() } diff --git a/runtime/sema/errors.go b/runtime/sema/errors.go index 5fc1424792..20cbb9a114 100644 --- a/runtime/sema/errors.go +++ b/runtime/sema/errors.go @@ -3717,6 +3717,24 @@ func (e *InvalidTypeArgumentCountError) SecondaryError() string { ) } +// MissingTypeArgumentError + +type MissingTypeArgumentError struct { + TypeArgumentName string + ast.Range +} + +var _ SemanticError = &MissingTypeArgumentError{} +var _ errors.UserError = &MissingTypeArgumentError{} + +func (e *MissingTypeArgumentError) isSemanticError() {} + +func (*MissingTypeArgumentError) IsUserError() {} + +func (e *MissingTypeArgumentError) Error() string { + return fmt.Sprintf("non-optional type argument %s missing", e.TypeArgumentName) +} + // TypeParameterTypeInferenceError type TypeParameterTypeInferenceError struct { diff --git a/runtime/sema/runtime_type_constructors.go b/runtime/sema/runtime_type_constructors.go index 7e0314b9fb..93fb7b2e94 100644 --- a/runtime/sema/runtime_type_constructors.go +++ b/runtime/sema/runtime_type_constructors.go @@ -201,6 +201,21 @@ var CapabilityTypeFunctionType = &FunctionType{ ), } +var InclusiveRangeTypeFunctionType = &FunctionType{ + Parameters: []Parameter{ + { + Label: ArgumentLabelNotRequired, + Identifier: "type", + TypeAnnotation: NewTypeAnnotation(MetaType), + }, + }, + ReturnTypeAnnotation: NewTypeAnnotation( + &OptionalType{ + Type: MetaType, + }, + ), +} + var runtimeTypeConstructors = []*RuntimeTypeConstructor{ { Name: OptionalTypeFunctionName, @@ -265,4 +280,11 @@ var runtimeTypeConstructors = []*RuntimeTypeConstructor{ Value: CapabilityTypeFunctionType, DocString: "Creates a run-time type representing a capability type of the given reference type. Returns nil if the type is not a reference.", }, + + { + Name: "InclusiveRangeType", + Value: InclusiveRangeTypeFunctionType, + DocString: `Creates a run-time type representing an inclusive range type of the given run-time member type. + Returns nil if the member type is not a valid inclusive range member type.`, + }, } diff --git a/runtime/sema/type.go b/runtime/sema/type.go index ec47eee467..4fe8cb6400 100644 --- a/runtime/sema/type.go +++ b/runtime/sema/type.go @@ -3411,6 +3411,7 @@ func init() { HashAlgorithmType, StorageCapabilityControllerType, AccountCapabilityControllerType, + &InclusiveRangeType{}, }, ) @@ -5266,6 +5267,295 @@ func (t *DictionaryType) Resolve(typeArguments *TypeParameterTypeOrderedMap) Typ } } +// InclusiveRangeType + +type InclusiveRangeType struct { + MemberType Type + memberResolvers map[string]MemberResolver + memberResolversOnce sync.Once +} + +var _ Type = &InclusiveRangeType{} +var _ ParameterizedType = &InclusiveRangeType{} + +func NewInclusiveRangeType(memoryGauge common.MemoryGauge, elementType Type) *InclusiveRangeType { + common.UseMemory(memoryGauge, common.DictionarySemaTypeMemoryUsage) + return &InclusiveRangeType{ + MemberType: elementType, + } +} + +func (*InclusiveRangeType) IsType() {} + +func (*InclusiveRangeType) Tag() TypeTag { + return InclusiveRangeTypeTag +} + +func (t *InclusiveRangeType) String() string { + memberString := "" + if t.MemberType != nil { + memberString = fmt.Sprintf("<%s>", t.MemberType.String()) + } + return fmt.Sprintf( + "InclusiveRange%s", + memberString, + ) +} + +func (t *InclusiveRangeType) QualifiedString() string { + memberString := "" + if t.MemberType != nil { + memberString = fmt.Sprintf("<%s>", t.MemberType.QualifiedString()) + } + return fmt.Sprintf( + "InclusiveRange%s", + memberString, + ) +} + +func InclusiveRangeTypeID(memberTypeID string) TypeID { + if memberTypeID != "" { + memberTypeID = fmt.Sprintf("<%s>", memberTypeID) + } + return TypeID(fmt.Sprintf( + "InclusiveRange%s", + memberTypeID, + )) +} + +func (t *InclusiveRangeType) ID() TypeID { + var memberTypeID string + if t.MemberType != nil { + memberTypeID = string(t.MemberType.ID()) + } + return InclusiveRangeTypeID(memberTypeID) +} + +func (t *InclusiveRangeType) Equal(other Type) bool { + otherRange, ok := other.(*InclusiveRangeType) + if !ok { + return false + } + if otherRange.MemberType == nil { + return t.MemberType == nil + } + + return otherRange.MemberType.Equal(t.MemberType) +} + +func (t *InclusiveRangeType) IsResourceType() bool { + return false +} + +func (t *InclusiveRangeType) IsInvalidType() bool { + return t.MemberType != nil && t.MemberType.IsInvalidType() +} + +func (t *InclusiveRangeType) IsStorable(results map[*Member]bool) bool { + return false +} + +func (t *InclusiveRangeType) IsExportable(results map[*Member]bool) bool { + return t.MemberType.IsExportable(results) +} + +func (t *InclusiveRangeType) IsImportable(results map[*Member]bool) bool { + return t.MemberType.IsImportable(results) +} + +func (t *InclusiveRangeType) IsEquatable() bool { + return t.MemberType.IsEquatable() +} + +func (*InclusiveRangeType) IsComparable() bool { + return false +} + +func (t *InclusiveRangeType) TypeAnnotationState() TypeAnnotationState { + if t.MemberType == nil { + return TypeAnnotationStateValid + } + + return t.MemberType.TypeAnnotationState() +} + +func (t *InclusiveRangeType) RewriteWithRestrictedTypes() (Type, bool) { + if t.MemberType == nil { + return t, false + } + rewrittenMemberType, rewritten := t.MemberType.RewriteWithRestrictedTypes() + if rewritten { + return &InclusiveRangeType{ + MemberType: rewrittenMemberType, + }, true + } + return t, false +} + +func (t *InclusiveRangeType) BaseType() Type { + if t.MemberType == nil { + return nil + } + return &InclusiveRangeType{} +} + +func (t *InclusiveRangeType) Instantiate(typeArguments []Type, report func(err error)) Type { + memberType := typeArguments[0] + return &InclusiveRangeType{ + MemberType: memberType, + } +} + +func (t *InclusiveRangeType) TypeArguments() []Type { + memberType := t.MemberType + return []Type{ + memberType, + } +} + +var inclusiveRangeTypeParameter = &TypeParameter{ + Name: "T", + TypeBound: IntegerType, +} + +func (*InclusiveRangeType) TypeParameters() []*TypeParameter { + return []*TypeParameter{ + inclusiveRangeTypeParameter, + } +} + +const InclusiveRangeTypeStartFieldName = "start" +const inclusiveRangeTypeStartFieldDocString = ` +The start of the InclusiveRange sequence +` +const InclusiveRangeTypeEndFieldName = "end" +const inclusiveRangeTypeEndFieldDocString = ` +The end of the InclusiveRange sequence +` + +const InclusiveRangeTypeStepFieldName = "step" +const inclusiveRangeTypeStepFieldDocString = ` +The step size of the InclusiveRange sequence +` + +var InclusiveRangeTypeFieldNames = []string{ + InclusiveRangeTypeStartFieldName, + InclusiveRangeTypeEndFieldName, + InclusiveRangeTypeStepFieldName, +} + +const InclusiveRangeTypeContainsFunctionName = "contains" + +const inclusiveRangeTypeContainsFunctionDocString = ` +Returns true if the given integer is in the InclusiveRange sequence +` + +func (t *InclusiveRangeType) GetMembers() map[string]MemberResolver { + t.initializeMemberResolvers() + return t.memberResolvers +} + +func InclusiveRangeContainsFunctionType(elementType Type) *FunctionType { + return &FunctionType{ + Parameters: []Parameter{ + { + Label: ArgumentLabelNotRequired, + Identifier: "element", + TypeAnnotation: NewTypeAnnotation(elementType), + }, + }, + ReturnTypeAnnotation: NewTypeAnnotation( + BoolType, + ), + } +} + +func (t *InclusiveRangeType) initializeMemberResolvers() { + t.memberResolversOnce.Do(func() { + t.memberResolvers = withBuiltinMembers(t, map[string]MemberResolver{ + InclusiveRangeTypeStartFieldName: { + Kind: common.DeclarationKindField, + Resolve: func(memoryGauge common.MemoryGauge, identifier string, _ ast.Range, _ func(error)) *Member { + return NewPublicConstantFieldMember( + memoryGauge, + t, + identifier, + t.MemberType, + inclusiveRangeTypeStartFieldDocString, + ) + }, + }, + InclusiveRangeTypeEndFieldName: { + Kind: common.DeclarationKindField, + Resolve: func(memoryGauge common.MemoryGauge, identifier string, _ ast.Range, _ func(error)) *Member { + return NewPublicConstantFieldMember( + memoryGauge, + t, + identifier, + t.MemberType, + inclusiveRangeTypeEndFieldDocString, + ) + }, + }, + InclusiveRangeTypeStepFieldName: { + Kind: common.DeclarationKindField, + Resolve: func(memoryGauge common.MemoryGauge, identifier string, _ ast.Range, _ func(error)) *Member { + return NewPublicConstantFieldMember( + memoryGauge, + t, + identifier, + t.MemberType, + inclusiveRangeTypeStepFieldDocString, + ) + }, + }, + InclusiveRangeTypeContainsFunctionName: { + Kind: common.DeclarationKindFunction, + Resolve: func(memoryGauge common.MemoryGauge, identifier string, targetRange ast.Range, report func(error)) *Member { + elementType := t.MemberType + + return NewPublicFunctionMember( + memoryGauge, + t, + identifier, + InclusiveRangeContainsFunctionType(elementType), + inclusiveRangeTypeContainsFunctionDocString, + ) + }, + }, + }) + }) +} + +func (*InclusiveRangeType) AllowsValueIndexingAssignment() bool { + return false +} + +func (t *InclusiveRangeType) Unify( + other Type, + typeParameters *TypeParameterTypeOrderedMap, + report func(err error), + outerRange ast.Range, +) bool { + otherRange, ok := other.(*InclusiveRangeType) + if !ok { + return false + } + + return t.MemberType.Unify(otherRange.MemberType, typeParameters, report, outerRange) +} + +func (t *InclusiveRangeType) Resolve(typeArguments *TypeParameterTypeOrderedMap) Type { + memberType := t.MemberType.Resolve(typeArguments) + if memberType == nil { + return nil + } + + return &InclusiveRangeType{ + MemberType: memberType, + } +} + // ReferenceType represents the reference to a value type ReferenceType struct { Type Type diff --git a/runtime/sema/type_tags.go b/runtime/sema/type_tags.go index 436c6f921e..1c9a3d1766 100644 --- a/runtime/sema/type_tags.go +++ b/runtime/sema/type_tags.go @@ -224,6 +224,8 @@ const ( interfaceTypeMask functionTypeMask + inclusiveRangeTypeMask + invalidTypeMask ) @@ -338,6 +340,7 @@ var ( RestrictedTypeTag = newTypeTagFromUpperMask(restrictedTypeMask) CapabilityTypeTag = newTypeTagFromUpperMask(capabilityTypeMask) + InclusiveRangeTypeTag = newTypeTagFromUpperMask(inclusiveRangeTypeMask) InvalidTypeTag = newTypeTagFromUpperMask(invalidTypeMask) TransactionTypeTag = newTypeTagFromUpperMask(transactionTypeMask) AnyResourceAttachmentTypeTag = newTypeTagFromUpperMask(anyResourceAttachmentMask) @@ -368,7 +371,8 @@ var ( Or(CapabilityTypeTag). Or(FunctionTypeTag). Or(StorageCapabilityControllerTypeTag). - Or(AccountCapabilityControllerTypeTag) + Or(AccountCapabilityControllerTypeTag). + Or(InclusiveRangeTypeTag) AnyResourceTypeTag = newTypeTagFromLowerMask(anyResourceTypeMask). Or(AnyResourceAttachmentTypeTag) @@ -670,7 +674,8 @@ func findSuperTypeFromUpperMask(joinedTypeTag TypeTag, types []Type) Type { restrictedTypeMask, transactionTypeMask, interfaceTypeMask, - functionTypeMask: + functionTypeMask, + inclusiveRangeTypeMask: return getSuperTypeOfDerivedTypes(types) case anyResourceAttachmentMask: diff --git a/runtime/stdlib/builtin.go b/runtime/stdlib/builtin.go index 60e0111fc2..e5801a47b5 100644 --- a/runtime/stdlib/builtin.go +++ b/runtime/stdlib/builtin.go @@ -39,6 +39,7 @@ func DefaultStandardLibraryValues(handler StandardLibraryHandler) []StandardLibr PanicFunction, SignatureAlgorithmConstructor, RLPContract, + InclusiveRangeConstructorFunction, NewLogFunction(handler), NewUnsafeRandomFunction(handler), NewGetBlockFunction(handler), diff --git a/runtime/stdlib/range.go b/runtime/stdlib/range.go new file mode 100644 index 0000000000..4f2f54227d --- /dev/null +++ b/runtime/stdlib/range.go @@ -0,0 +1,149 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * 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 ( + "fmt" + + "github.com/onflow/cadence/runtime/errors" + "github.com/onflow/cadence/runtime/interpreter" + "github.com/onflow/cadence/runtime/sema" +) + +// InclusiveRangeConstructorFunction + +const inclusiveRangeConstructorFunctionDocString = ` + Constructs a Range covering from start to end. + + The step argument is optional and determines the step size. + If not provided, the value of +1 or -1 is used based on the values of start and end. + ` + +var inclusiveRangeConstructorFunctionType = func() *sema.FunctionType { + typeParameter := &sema.TypeParameter{ + Name: "T", + TypeBound: sema.IntegerType, + } + + typeAnnotation := sema.NewTypeAnnotation( + &sema.GenericType{ + TypeParameter: typeParameter, + }, + ) + + return &sema.FunctionType{ + TypeParameters: []*sema.TypeParameter{ + typeParameter, + }, + Parameters: []sema.Parameter{ + { + Label: sema.ArgumentLabelNotRequired, + Identifier: "start", + TypeAnnotation: typeAnnotation, + }, + { + Label: sema.ArgumentLabelNotRequired, + Identifier: "end", + TypeAnnotation: typeAnnotation, + }, + { + Identifier: "step", + TypeAnnotation: typeAnnotation, + }, + }, + ReturnTypeAnnotation: sema.NewTypeAnnotation( + &sema.InclusiveRangeType{ + MemberType: typeAnnotation.Type, + }, + ), + // `step` parameter is optional + Arity: &sema.Arity{Min: 2, Max: 3}, + } +}() + +var InclusiveRangeConstructorFunction = NewStandardLibraryFunction( + "InclusiveRange", + inclusiveRangeConstructorFunctionType, + inclusiveRangeConstructorFunctionDocString, + func(invocation interpreter.Invocation) interpreter.Value { + start, startOk := invocation.Arguments[0].(interpreter.IntegerValue) + end, endOk := invocation.Arguments[1].(interpreter.IntegerValue) + + if !startOk || !endOk { + panic(errors.NewUnreachableError()) + } + + inter := invocation.Interpreter + locationRange := invocation.LocationRange + + startStaticType := start.StaticType(inter) + endStaticType := end.StaticType(inter) + if !startStaticType.Equal(endStaticType) { + panic(interpreter.InclusiveRangeConstructionError{ + LocationRange: locationRange, + Message: fmt.Sprintf( + "start and end are of different types. start: %s and end: %s", + startStaticType, + endStaticType, + ), + }) + } + + rangeStaticType := interpreter.NewInclusiveRangeStaticType(invocation.Interpreter, startStaticType) + rangeSemaType := sema.NewInclusiveRangeType(invocation.Interpreter, invocation.ArgumentTypes[0]) + + if len(invocation.Arguments) > 2 { + step, ok := invocation.Arguments[2].(interpreter.IntegerValue) + if !ok { + panic(errors.NewUnreachableError()) + } + + stepStaticType := step.StaticType(inter) + if stepStaticType != startStaticType { + panic(interpreter.InclusiveRangeConstructionError{ + LocationRange: locationRange, + Message: fmt.Sprintf( + "step must be of the same type as start and end. start/end: %s and step: %s", + startStaticType, + stepStaticType, + ), + }) + } + + return interpreter.NewInclusiveRangeValueWithStep( + inter, + locationRange, + start, + end, + step, + rangeStaticType, + rangeSemaType, + ) + } + + return interpreter.NewInclusiveRangeValue( + inter, + locationRange, + start, + end, + rangeStaticType, + rangeSemaType, + ) + }, +) diff --git a/runtime/tests/checker/operations_test.go b/runtime/tests/checker/operations_test.go index 8f9f9d3091..8f47388972 100644 --- a/runtime/tests/checker/operations_test.go +++ b/runtime/tests/checker/operations_test.go @@ -29,6 +29,7 @@ import ( "github.com/onflow/cadence/runtime/ast" "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/sema" + "github.com/onflow/cadence/runtime/stdlib" ) func TestCheckInvalidUnaryBooleanNegationOfInteger(t *testing.T) { @@ -337,6 +338,9 @@ type operationWithTypeTests struct { func TestCheckNonIntegerComparisonOperations(t *testing.T) { t.Parallel() + baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) + baseValueActivation.DeclareValue(stdlib.InclusiveRangeConstructorFunction) + allOperationTests := []operationWithTypeTests{ { operations: []ast.Operation{ @@ -363,6 +367,16 @@ func TestCheckNonIntegerComparisonOperations(t *testing.T) { {sema.BoolType, "1.2", "\"bcd\"", "Fix64", "String", []error{ &sema.InvalidBinaryOperandsError{}, }}, + { + sema.BoolType, + "InclusiveRange(1, 2)", + "InclusiveRange(3, 4)", + "InclusiveRange", + "InclusiveRange", + []error{ + &sema.InvalidBinaryOperandsError{}, + }, + }, }, }, } @@ -378,7 +392,7 @@ func TestCheckNonIntegerComparisonOperations(t *testing.T) { t.Run(testName, func(t *testing.T) { - _, err := ParseAndCheck(t, + _, err := ParseAndCheckWithOptions(t, fmt.Sprintf( `fun test(): %s { let a: %s = %s @@ -387,6 +401,11 @@ func TestCheckNonIntegerComparisonOperations(t *testing.T) { }`, test.ty, test.leftType, test.left, test.rightType, test.right, operation.Symbol(), ), + ParseAndCheckOptions{ + Config: &sema.Config{ + BaseValueActivation: baseValueActivation, + }, + }, ) errs := RequireCheckerErrors(t, err, len(test.expectedErrors)) diff --git a/runtime/tests/checker/range_value_test.go b/runtime/tests/checker/range_value_test.go new file mode 100644 index 0000000000..196f3baf25 --- /dev/null +++ b/runtime/tests/checker/range_value_test.go @@ -0,0 +1,379 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * 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 checker + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/onflow/cadence/runtime/sema" + "github.com/onflow/cadence/runtime/stdlib" +) + +type inclusiveRangeConstructionTest struct { + ty sema.Type + s, e, step int64 +} + +func TestCheckInclusiveRangeConstructionValid(t *testing.T) { + t.Parallel() + + baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) + baseValueActivation.DeclareValue(stdlib.InclusiveRangeConstructorFunction) + + validTestCases := []inclusiveRangeConstructionTest{ + // Int* + { + ty: sema.IntType, + s: 0, + e: 10, + step: 2, + }, + { + ty: sema.IntType, + s: 10, + e: -10, + step: -2, + }, + { + ty: sema.Int8Type, + s: 0, + e: 10, + step: 2, + }, + { + ty: sema.Int8Type, + s: 10, + e: -10, + step: -2, + }, + { + ty: sema.Int16Type, + s: 0, + e: 10, + step: 2, + }, + { + ty: sema.Int16Type, + s: 10, + e: -10, + step: -2, + }, + { + ty: sema.Int32Type, + s: 0, + e: 10, + step: 2, + }, + { + ty: sema.Int32Type, + s: 10, + e: -10, + step: -2, + }, + { + ty: sema.Int64Type, + s: 0, + e: 10, + step: 2, + }, + { + ty: sema.Int64Type, + s: 10, + e: -10, + step: -2, + }, + { + ty: sema.Int128Type, + s: 0, + e: 10, + step: 2, + }, + { + ty: sema.Int128Type, + s: 10, + e: -10, + step: -2, + }, + { + ty: sema.Int256Type, + s: 0, + e: 10, + step: 2, + }, + { + ty: sema.Int256Type, + s: 10, + e: -10, + step: -2, + }, + + // UInt* + { + ty: sema.UIntType, + s: 0, + e: 10, + step: 2, + }, + { + ty: sema.UInt8Type, + s: 0, + e: 10, + step: 2, + }, + { + ty: sema.UInt16Type, + s: 0, + e: 10, + step: 2, + }, + { + ty: sema.UInt32Type, + s: 0, + e: 10, + step: 2, + }, + { + ty: sema.UInt64Type, + s: 0, + e: 10, + step: 2, + }, + { + ty: sema.UInt128Type, + s: 0, + e: 10, + step: 2, + }, + { + ty: sema.UInt256Type, + s: 0, + e: 10, + step: 2, + }, + + // Word* + { + ty: sema.Word8Type, + s: 0, + e: 10, + step: 2, + }, + { + ty: sema.Word16Type, + s: 0, + e: 10, + step: 2, + }, + { + ty: sema.Word32Type, + s: 0, + e: 10, + step: 2, + }, + { + ty: sema.Word64Type, + s: 0, + e: 10, + step: 2, + }, + { + ty: sema.Word128Type, + s: 0, + e: 10, + step: 2, + }, + { + ty: sema.Word256Type, + s: 0, + e: 10, + step: 2, + }, + } + + runValidCase := func(t *testing.T, testCase inclusiveRangeConstructionTest, withStep bool) { + t.Run(testCase.ty.String(), func(t *testing.T) { + t.Parallel() + + var code string + if withStep { + code = fmt.Sprintf( + ` + let s : %s = %d + let e : %s = %d + let step : %s = %d + let r: InclusiveRange<%s> = InclusiveRange(s, e, step: step) + + let rs = r.start + let re = r.end + let rstep = r.step + let contains_res = r.contains(s) + `, + testCase.ty.String(), testCase.s, testCase.ty.String(), testCase.e, testCase.ty.String(), testCase.step, testCase.ty.String()) + } else { + code = fmt.Sprintf( + ` + let s : %s = %d + let e : %s = %d + let r: InclusiveRange<%s> = InclusiveRange(s, e) + + let rs = r.start + let re = r.end + let rstep = r.step + let contains_res = r.contains(s) + `, + testCase.ty.String(), testCase.s, testCase.ty.String(), testCase.e, testCase.ty.String()) + } + + checker, err := ParseAndCheckWithOptions(t, code, + ParseAndCheckOptions{ + Config: &sema.Config{ + BaseValueActivation: baseValueActivation, + }, + }, + ) + + require.NoError(t, err) + + checkType := func(t *testing.T, name string, expectedType sema.Type) { + resType := RequireGlobalValue(t, checker.Elaboration, name) + assert.IsType(t, expectedType, resType) + } + + checkType(t, "r", &sema.InclusiveRangeType{ + MemberType: testCase.ty, + }) + checkType(t, "rs", testCase.ty) + checkType(t, "re", testCase.ty) + checkType(t, "rstep", testCase.ty) + checkType(t, "contains_res", sema.BoolType) + }) + } + + // Run each test case with and without step. + for _, testCase := range validTestCases { + runValidCase(t, testCase, true) + runValidCase(t, testCase, false) + } +} + +func TestCheckInclusiveRangeConstructionInvalid(t *testing.T) { + t.Parallel() + + baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) + baseValueActivation.DeclareValue(stdlib.InclusiveRangeConstructorFunction) + + runInvalidCase := func(t *testing.T, label, code string, expectedErrorTypes []error) { + t.Run(label, func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheckWithOptions(t, code, + ParseAndCheckOptions{ + Config: &sema.Config{ + BaseValueActivation: baseValueActivation, + }, + }, + ) + + errs := RequireCheckerErrors(t, err, len(expectedErrorTypes)) + for i, err := range expectedErrorTypes { + assert.IsType(t, err, errs[i]) + } + }) + } + + for _, integerType := range sema.AllIntegerTypes { + // Only test leaf types + switch integerType { + case sema.IntegerType, sema.SignedIntegerType: + continue + } + + typeString := integerType.String() + + // Wrong type of arguments + + // Any integer type name other than the integerType is sufficient. + // There is nothing special about the Int128 and Int256 types here. + differentTypeString := sema.Int128TypeName + if typeString == differentTypeString { + differentTypeString = sema.Int256TypeName + } + + runInvalidCase( + t, + typeString, + fmt.Sprintf("let r = InclusiveRange(%s(1), %s(2))", typeString, differentTypeString), + []error{&sema.TypeParameterTypeMismatchError{}, &sema.TypeMismatchError{}}, + ) + runInvalidCase( + t, + typeString, + fmt.Sprintf("let r = InclusiveRange(%s(1), %s(10), step: %s(2))", typeString, typeString, differentTypeString), + []error{&sema.TypeParameterTypeMismatchError{}, &sema.TypeMismatchError{}}, + ) + + // Not enough arguments + runInvalidCase( + t, + typeString, + fmt.Sprintf("let r = InclusiveRange(%s(1))", typeString), + []error{&sema.InsufficientArgumentsError{}}, + ) + + // Label for step not provided + runInvalidCase( + t, + typeString, + fmt.Sprintf("let r = InclusiveRange(%s(1), %s(0), %s(10))", typeString, typeString, typeString), + []error{&sema.MissingArgumentLabelError{}}, + ) + + // Label for start and end provided + runInvalidCase( + t, + typeString, + fmt.Sprintf("let r = InclusiveRange(start: %s(1), %s(0))", typeString, typeString), + []error{&sema.IncorrectArgumentLabelError{}}, + ) + runInvalidCase( + t, + typeString, + fmt.Sprintf("let r = InclusiveRange(%s(1), end: %s(0))", typeString, typeString), + []error{&sema.IncorrectArgumentLabelError{}}, + ) + } + + runInvalidCase( + t, + "without_inner_type_annotation", + "let r: InclusiveRange<> = InclusiveRange(1, 10)", + []error{&sema.InvalidTypeArgumentCountError{}, &sema.MissingTypeArgumentError{}}, + ) + + runInvalidCase( + t, + "without instantiation type", + "let r: InclusiveRange = InclusiveRange(1, 10)", + []error{&sema.MissingTypeArgumentError{}}, + ) +} diff --git a/runtime/tests/checker/runtimetype_test.go b/runtime/tests/checker/runtimetype_test.go index a2aea9373f..b97e005474 100644 --- a/runtime/tests/checker/runtimetype_test.go +++ b/runtime/tests/checker/runtimetype_test.go @@ -852,3 +852,75 @@ func TestCheckCapabilityTypeConstructor(t *testing.T) { }) } } + +func TestCheckInclusiveRangeTypeConstructor(t *testing.T) { + + t.Parallel() + + cases := []struct { + name string + code string + expectedError error + }{ + { + name: "Int", + code: ` + let result = InclusiveRangeType(Type()) + `, + expectedError: nil, + }, + { + name: "UInt16", + code: ` + let result = InclusiveRangeType(Type()) + `, + expectedError: nil, + }, + { + name: "resource", + code: ` + resource R {} + let result = InclusiveRangeType(Type<@R>()) + `, + expectedError: nil, + }, + { + name: "type mismatch", + code: ` + let result = InclusiveRangeType(3) + `, + expectedError: &sema.TypeMismatchError{}, + }, + { + name: "too many args", + code: ` + let result = InclusiveRangeType(Type(), Type()) + `, + expectedError: &sema.ExcessiveArgumentsError{}, + }, + { + name: "too few args", + code: ` + let result = InclusiveRangeType() + `, + expectedError: &sema.InsufficientArgumentsError{}, + }, + } + + for _, testCase := range cases { + t.Run(testCase.name, func(t *testing.T) { + checker, err := ParseAndCheck(t, testCase.code) + + if testCase.expectedError == nil { + require.NoError(t, err) + assert.Equal(t, + &sema.OptionalType{Type: sema.MetaType}, + RequireGlobalValue(t, checker.Elaboration, "result"), + ) + } else { + errs := RequireCheckerErrors(t, err, 1) + assert.IsType(t, testCase.expectedError, errs[0]) + } + }) + } +} diff --git a/runtime/tests/checker/storable_test.go b/runtime/tests/checker/storable_test.go index 3f90ba4ecf..f256154bfa 100644 --- a/runtime/tests/checker/storable_test.go +++ b/runtime/tests/checker/storable_test.go @@ -133,6 +133,7 @@ func TestCheckStorable(t *testing.T) { sema.VoidType, sema.AuthAccountType, sema.PublicAccountType, + &sema.InclusiveRangeType{MemberType: sema.IntType}, } // Capabilities of non-storable types are storable diff --git a/runtime/tests/checker/typeargument_test.go b/runtime/tests/checker/typeargument_test.go index 0149708171..7167facadf 100644 --- a/runtime/tests/checker/typeargument_test.go +++ b/runtime/tests/checker/typeargument_test.go @@ -25,6 +25,7 @@ import ( "github.com/stretchr/testify/require" "github.com/onflow/cadence/runtime/sema" + "github.com/onflow/cadence/runtime/stdlib" ) func TestCheckTypeArguments(t *testing.T) { @@ -53,6 +54,53 @@ func TestCheckTypeArguments(t *testing.T) { require.Nil(t, capType.(*sema.CapabilityType).BorrowType) }) + t.Run("inclusive range, no instantiation", func(t *testing.T) { + + t.Parallel() + + baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) + baseValueActivation.DeclareValue(stdlib.InclusiveRangeConstructorFunction) + + _, err := ParseAndCheckWithOptions(t, + ` + let inclusiveRange: InclusiveRange = InclusiveRange(1, 10) + `, + ParseAndCheckOptions{ + Config: &sema.Config{ + BaseValueActivation: baseValueActivation, + }, + }, + ) + + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.MissingTypeArgumentError{}, errs[0]) + }) + + t.Run("inclusive range, instantiation with more than arguments", func(t *testing.T) { + + t.Parallel() + + baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) + baseValueActivation.DeclareValue(stdlib.InclusiveRangeConstructorFunction) + + _, err := ParseAndCheckWithOptions(t, + ` + let inclusiveRange: InclusiveRange = InclusiveRange(1, 10) + `, + ParseAndCheckOptions{ + Config: &sema.Config{ + BaseValueActivation: baseValueActivation, + }, + }, + ) + + errs := RequireCheckerErrors(t, err, 2) + + assert.IsType(t, &sema.InvalidTypeArgumentCountError{}, errs[0]) + assert.IsType(t, &sema.MissingTypeArgumentError{}, errs[1]) + }) + t.Run("capability, instantiation with no arguments", func(t *testing.T) { t.Parallel() diff --git a/runtime/tests/interpreter/account_test.go b/runtime/tests/interpreter/account_test.go index 67768198a8..caa6bf5087 100644 --- a/runtime/tests/interpreter/account_test.go +++ b/runtime/tests/interpreter/account_test.go @@ -89,6 +89,8 @@ func testAccount( accountValueDeclaration.Name = "account" valueDeclarations = append(valueDeclarations, accountValueDeclaration) + valueDeclarations = append(valueDeclarations, stdlib.InclusiveRangeConstructorFunction) + if checkerConfig.BaseValueActivation == nil { checkerConfig.BaseValueActivation = sema.BaseValueActivation } diff --git a/runtime/tests/interpreter/dynamic_casting_test.go b/runtime/tests/interpreter/dynamic_casting_test.go index ae820bfaf3..71cd10af7e 100644 --- a/runtime/tests/interpreter/dynamic_casting_test.go +++ b/runtime/tests/interpreter/dynamic_casting_test.go @@ -1467,6 +1467,126 @@ func TestInterpretDynamicCastingDictionary(t *testing.T) { } } +func TestInterpretDynamicCastingInclusiveRange(t *testing.T) { + + t.Parallel() + + types := []sema.Type{ + &sema.InclusiveRangeType{ + MemberType: sema.IntType, + }, + &sema.InclusiveRangeType{ + MemberType: sema.IntegerType, + }, + } + + baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) + baseValueActivation.DeclareValue(stdlib.InclusiveRangeConstructorFunction) + + baseActivation := activations.NewActivation(nil, interpreter.BaseActivation) + interpreter.Declare(baseActivation, stdlib.InclusiveRangeConstructorFunction) + + options := ParseCheckAndInterpretOptions{ + CheckerConfig: &sema.Config{ + BaseValueActivation: baseValueActivation, + }, + Config: &interpreter.Config{ + BaseActivation: baseActivation, + }, + } + + for operation, returnsOptional := range dynamicCastingOperations { + + t.Run(operation.Symbol(), func(t *testing.T) { + + for _, fromType := range types { + for _, targetType := range types { + + t.Run(fmt.Sprintf("valid: from %s to %s", fromType, targetType), func(t *testing.T) { + + inter, err := parseCheckAndInterpretWithOptions(t, + fmt.Sprintf( + ` + let x: InclusiveRange = InclusiveRange(10, 20) + let y: %[1]s = x + let z: %[2]s? = y %[3]s %[2]s + `, + fromType, + targetType, + operation.Symbol(), + ), + options, + ) + require.NoError(t, err) + + expectedInclusiveRange := interpreter.NewInclusiveRangeValue( + inter, + interpreter.EmptyLocationRange, + interpreter.NewUnmeteredIntValueFromInt64(10), + interpreter.NewUnmeteredIntValueFromInt64(20), + interpreter.InclusiveRangeStaticType{ + ElementType: interpreter.PrimitiveStaticTypeInt, + }, + sema.NewInclusiveRangeType(nil, sema.IntType), + ) + + AssertValuesEqual( + t, + inter, + expectedInclusiveRange, + inter.Globals.Get("y").GetValue(), + ) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredSomeValueNonCopying( + expectedInclusiveRange, + ), + inter.Globals.Get("z").GetValue(), + ) + }) + } + + // We cannot test for invalid casts for T since InclusiveRange has a type bound Integer on T. + } + + t.Run("invalid upcast", func(t *testing.T) { + + inter, err := parseCheckAndInterpretWithOptions(t, + fmt.Sprintf( + ` + fun test(): InclusiveRange? { + let x: InclusiveRange = InclusiveRange(10, 20) + return x %s InclusiveRange + } + `, + operation.Symbol(), + ), + options, + ) + require.NoError(t, err) + + result, err := inter.Invoke("test") + + if returnsOptional { + require.NoError(t, err) + AssertValuesEqual( + t, + inter, + interpreter.Nil, + result, + ) + } else { + RequireError(t, err) + + require.ErrorAs(t, err, &interpreter.ForceCastTypeMismatchError{}) + } + }) + }) + } +} + func TestInterpretDynamicCastingResourceType(t *testing.T) { t.Parallel() diff --git a/runtime/tests/interpreter/range_value_test.go b/runtime/tests/interpreter/range_value_test.go new file mode 100644 index 0000000000..de1ba55c32 --- /dev/null +++ b/runtime/tests/interpreter/range_value_test.go @@ -0,0 +1,613 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * 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_test + +import ( + "fmt" + "strings" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/onflow/cadence/runtime/activations" + "github.com/onflow/cadence/runtime/interpreter" + "github.com/onflow/cadence/runtime/sema" + "github.com/onflow/cadence/runtime/stdlib" + "github.com/onflow/cadence/runtime/tests/utils" + . "github.com/onflow/cadence/runtime/tests/utils" +) + +type containsTestCase struct { + param int64 + expectedWithoutStep bool + expectedWithStep bool +} + +type inclusiveRangeConstructionTest struct { + ty sema.Type + s, e, step int8 + containsTests []containsTestCase +} + +func TestInclusiveRange(t *testing.T) { + t.Parallel() + + baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) + baseValueActivation.DeclareValue(stdlib.InclusiveRangeConstructorFunction) + + baseActivation := activations.NewActivation(nil, interpreter.BaseActivation) + interpreter.Declare(baseActivation, stdlib.InclusiveRangeConstructorFunction) + + unsignedContainsTestCases := []containsTestCase{ + { + param: 1, + expectedWithoutStep: true, + expectedWithStep: false, + }, + { + param: 12, + expectedWithoutStep: false, + expectedWithStep: false, + }, + { + param: 10, + expectedWithoutStep: true, + expectedWithStep: true, + }, + { + param: 0, + expectedWithoutStep: true, + expectedWithStep: true, + }, + { + param: 2, + expectedWithoutStep: true, + expectedWithStep: true, + }, + } + + signedContainsTestCasesForward := []containsTestCase{ + { + param: 1, + expectedWithoutStep: true, + expectedWithStep: false, + }, + { + param: 100, + expectedWithoutStep: false, + expectedWithStep: false, + }, + { + param: -100, + expectedWithoutStep: false, + expectedWithStep: false, + }, + { + param: 0, + expectedWithoutStep: true, + expectedWithStep: true, + }, + { + param: 4, + expectedWithoutStep: true, + expectedWithStep: true, + }, + { + param: 10, + expectedWithoutStep: true, + expectedWithStep: true, + }, + } + signedContainsTestCasesBackward := []containsTestCase{ + { + param: 1, + expectedWithoutStep: true, + expectedWithStep: false, + }, + { + param: 12, + expectedWithoutStep: false, + expectedWithStep: false, + }, + { + param: -12, + expectedWithoutStep: false, + expectedWithStep: false, + }, + { + param: 10, + expectedWithoutStep: true, + expectedWithStep: true, + }, + { + param: -10, + expectedWithoutStep: true, + expectedWithStep: true, + }, + { + param: -8, + expectedWithoutStep: true, + expectedWithStep: true, + }, + } + + validTestCases := []inclusiveRangeConstructionTest{ + // Int* + { + ty: sema.IntType, + s: 0, + e: 10, + step: 2, + containsTests: signedContainsTestCasesForward, + }, + { + ty: sema.IntType, + s: 10, + e: -10, + step: -2, + containsTests: signedContainsTestCasesBackward, + }, + { + ty: sema.Int8Type, + s: 0, + e: 10, + step: 2, + containsTests: signedContainsTestCasesForward, + }, + { + ty: sema.Int8Type, + s: 10, + e: -10, + step: -2, + containsTests: signedContainsTestCasesBackward, + }, + { + ty: sema.Int16Type, + s: 0, + e: 10, + step: 2, + containsTests: signedContainsTestCasesForward, + }, + { + ty: sema.Int16Type, + s: 10, + e: -10, + step: -2, + containsTests: signedContainsTestCasesBackward, + }, + { + ty: sema.Int32Type, + s: 0, + e: 10, + step: 2, + containsTests: signedContainsTestCasesForward, + }, + { + ty: sema.Int32Type, + s: 10, + e: -10, + step: -2, + containsTests: signedContainsTestCasesBackward, + }, + { + ty: sema.Int64Type, + s: 0, + e: 10, + step: 2, + containsTests: signedContainsTestCasesForward, + }, + { + ty: sema.Int64Type, + s: 10, + e: -10, + step: -2, + containsTests: signedContainsTestCasesBackward, + }, + { + ty: sema.Int128Type, + s: 0, + e: 10, + step: 2, + containsTests: signedContainsTestCasesForward, + }, + { + ty: sema.Int128Type, + s: 10, + e: -10, + step: -2, + containsTests: signedContainsTestCasesBackward, + }, + { + ty: sema.Int256Type, + s: 0, + e: 10, + step: 2, + containsTests: signedContainsTestCasesForward, + }, + { + ty: sema.Int256Type, + s: 10, + e: -10, + step: -2, + containsTests: signedContainsTestCasesBackward, + }, + + // UInt* + { + ty: sema.UIntType, + s: 0, + e: 10, + step: 2, + containsTests: unsignedContainsTestCases, + }, + { + ty: sema.UInt8Type, + s: 0, + e: 10, + step: 2, + containsTests: unsignedContainsTestCases, + }, + { + ty: sema.UInt16Type, + s: 0, + e: 10, + step: 2, + containsTests: unsignedContainsTestCases, + }, + { + ty: sema.UInt32Type, + s: 0, + e: 10, + step: 2, + containsTests: unsignedContainsTestCases, + }, + { + ty: sema.UInt64Type, + s: 0, + e: 10, + step: 2, + containsTests: unsignedContainsTestCases, + }, + { + ty: sema.UInt128Type, + s: 0, + e: 10, + step: 2, + containsTests: unsignedContainsTestCases, + }, + { + ty: sema.UInt256Type, + s: 0, + e: 10, + step: 2, + containsTests: unsignedContainsTestCases, + }, + + // Word* + { + ty: sema.Word8Type, + s: 0, + e: 10, + step: 2, + containsTests: unsignedContainsTestCases, + }, + { + ty: sema.Word16Type, + s: 0, + e: 10, + step: 2, + containsTests: unsignedContainsTestCases, + }, + { + ty: sema.Word32Type, + s: 0, + e: 10, + step: 2, + containsTests: unsignedContainsTestCases, + }, + { + ty: sema.Word64Type, + s: 0, + e: 10, + step: 2, + containsTests: unsignedContainsTestCases, + }, + { + ty: sema.Word128Type, + s: 0, + e: 10, + step: 2, + containsTests: unsignedContainsTestCases, + }, + { + ty: sema.Word256Type, + s: 0, + e: 10, + step: 2, + containsTests: unsignedContainsTestCases, + }, + } + + runValidCase := func(t *testing.T, testCase inclusiveRangeConstructionTest, withStep bool) { + t.Run(testCase.ty.String(), func(t *testing.T) { + t.Parallel() + + // Generate code for the contains calls. + var containsCode string + for i, tc := range testCase.containsTests { + containsCode += fmt.Sprintf("\nlet c_%d = r.contains(%d)", i, tc.param) + } + + var code string + if withStep { + code = fmt.Sprintf( + ` + let s : %s = %d + let e : %s = %d + let step : %s = %d + let r: InclusiveRange<%s> = InclusiveRange(s, e, step: step) + + %s + `, + testCase.ty.String(), + testCase.s, + testCase.ty.String(), + testCase.e, + testCase.ty.String(), + testCase.step, + testCase.ty.String(), + containsCode, + ) + } else { + code = fmt.Sprintf( + ` + let s : %s = %d + let e : %s = %d + let r = InclusiveRange(s, e) + + %s + `, + testCase.ty.String(), + testCase.s, + testCase.ty.String(), + testCase.e, + containsCode, + ) + } + + inter, err := parseCheckAndInterpretWithOptions(t, code, + ParseCheckAndInterpretOptions{ + CheckerConfig: &sema.Config{ + BaseValueActivation: baseValueActivation, + }, + Config: &interpreter.Config{ + BaseActivation: baseActivation, + }, + }, + ) + + require.NoError(t, err) + + integerType := interpreter.ConvertSemaToStaticType( + nil, + testCase.ty, + ) + + rangeType := interpreter.NewInclusiveRangeStaticType(nil, integerType) + rangeSemaType := sema.NewInclusiveRangeType(nil, testCase.ty) + + var expectedRangeValue *interpreter.CompositeValue + + if withStep { + expectedRangeValue = interpreter.NewInclusiveRangeValueWithStep( + inter, + interpreter.EmptyLocationRange, + interpreter.GetSmallIntegerValue(testCase.s, integerType), + interpreter.GetSmallIntegerValue(testCase.e, integerType), + interpreter.GetSmallIntegerValue(testCase.step, integerType), + rangeType, + rangeSemaType, + ) + } else { + expectedRangeValue = interpreter.NewInclusiveRangeValue( + inter, + interpreter.EmptyLocationRange, + interpreter.GetSmallIntegerValue(testCase.s, integerType), + interpreter.GetSmallIntegerValue(testCase.e, integerType), + rangeType, + rangeSemaType, + ) + } + + utils.AssertValuesEqual( + t, + inter, + expectedRangeValue, + inter.Globals.Get("r").GetValue(), + ) + + // Check that contains returns correct information. + for i, tc := range testCase.containsTests { + var expectedValue interpreter.Value + if withStep { + expectedValue = interpreter.AsBoolValue(tc.expectedWithStep) + } else { + expectedValue = interpreter.AsBoolValue(tc.expectedWithoutStep) + } + + utils.AssertValuesEqual( + t, + inter, + expectedValue, + inter.Globals.Get(fmt.Sprintf("c_%d", i)).GetValue(), + ) + } + }) + } + + // Run each test case with and without step. + for _, testCase := range validTestCases { + runValidCase(t, testCase, true) + runValidCase(t, testCase, false) + } +} + +func TestGetValueForIntegerType(t *testing.T) { + + t.Parallel() + + // Ensure that GetValueForIntegerType handles every IntegerType + + for _, integerType := range sema.AllIntegerTypes { + switch integerType { + case sema.IntegerType, sema.SignedIntegerType: + continue + } + + integerStaticType := interpreter.ConvertSemaToStaticType(nil, integerType) + + // Panics if not handled. + _ = interpreter.GetSmallIntegerValue(int8(1), integerStaticType) + } +} + +func TestInclusiveRangeConstructionInvalid(t *testing.T) { + t.Parallel() + + baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) + baseValueActivation.DeclareValue(stdlib.InclusiveRangeConstructorFunction) + + baseActivation := activations.NewActivation(nil, interpreter.BaseActivation) + interpreter.Declare(baseActivation, stdlib.InclusiveRangeConstructorFunction) + + runInvalidCase := func(t *testing.T, label, code string, expectedError error, expectedMessage string) { + t.Run(label, func(t *testing.T) { + t.Parallel() + + _, err := parseCheckAndInterpretWithOptions(t, code, + ParseCheckAndInterpretOptions{ + CheckerConfig: &sema.Config{ + BaseValueActivation: baseValueActivation, + }, + Config: &interpreter.Config{ + BaseActivation: baseActivation, + }, + }, + ) + + RequireError(t, err) + + require.ErrorAs(t, err, expectedError) + require.True(t, strings.Contains(err.Error(), expectedMessage)) + }) + } + + for _, integerType := range sema.AllIntegerTypes { + // Only test leaf types + switch integerType { + case sema.IntegerType, sema.SignedIntegerType: + continue + } + + typeString := integerType.String() + + // step = 0. + runInvalidCase( + t, + typeString, + fmt.Sprintf("let r = InclusiveRange(%s(1), %s(2), step: %s(0))", typeString, typeString, typeString), + &interpreter.InclusiveRangeConstructionError{}, + "step value cannot be zero", + ) + + // step takes sequence away from end. + runInvalidCase( + t, + typeString, + fmt.Sprintf("let r = InclusiveRange(%s(40), %s(2), step: %s(2))", typeString, typeString, typeString), + &interpreter.InclusiveRangeConstructionError{}, + "sequence is moving away from end", + ) + } + + // Additional invalid cases for signed integer types + for _, integerType := range sema.AllSignedIntegerTypes { + // Only test leaf types + switch integerType { + case sema.SignedIntegerType: + continue + } + + typeString := integerType.String() + + // step takes sequence away from end with step being negative. + // This would be a checker error for unsigned integers but a + // runtime error in signed integers. + runInvalidCase( + t, + typeString, + fmt.Sprintf("let r = InclusiveRange(%s(4), %s(100), step: %s(-2))", typeString, typeString, typeString), + &interpreter.InclusiveRangeConstructionError{}, + "sequence is moving away from end", + ) + } + + // Additional invalid cases for unsigned integer types + for _, integerType := range sema.AllUnsignedIntegerTypes { + // Only test leaf types + switch integerType { + case sema.IntegerType: + continue + } + + typeString := integerType.String() + + runInvalidCase( + t, + typeString, + fmt.Sprintf("let r = InclusiveRange(%s(40), %s(1))", typeString, typeString), + &interpreter.InclusiveRangeConstructionError{}, + "step value cannot be negative for unsigned integer type", + ) + } + + runInvalidCase( + t, + "same_supertype_different_subtype_start_end", + ` + let a: Integer = UInt8(0) + let b: Integer = Int16(10) + let r = InclusiveRange(a, b) + `, + &interpreter.InclusiveRangeConstructionError{}, + "start and end are of different types", + ) + runInvalidCase( + t, + "same_supertype_different_subtype_start_step", + ` + let a: Integer = UInt8(0) + let b: Integer = UInt8(10) + let s: Integer = UInt16(2) + let r = InclusiveRange(a, b, step: s) + `, + &interpreter.InclusiveRangeConstructionError{}, + "step must be of the same type as start and end", + ) +} diff --git a/runtime/tests/interpreter/runtimetype_test.go b/runtime/tests/interpreter/runtimetype_test.go index ee7248906c..a4419c200a 100644 --- a/runtime/tests/interpreter/runtimetype_test.go +++ b/runtime/tests/interpreter/runtimetype_test.go @@ -679,3 +679,48 @@ func TestInterpretCapabilityType(t *testing.T) { inter.Globals.Get("e").GetValue(), ) } + +func TestInterpretInclusiveRangeType(t *testing.T) { + + t.Parallel() + + inter := parseCheckAndInterpret(t, ` + let a = InclusiveRangeType(Type())! + let b = InclusiveRangeType(Type<&Int>()) + + resource R {} + let c = InclusiveRangeType(Type<@R>()) + let d = InclusiveRangeType(Type()) + + let e = InclusiveRangeType(Type())! + `) + + assert.Equal(t, + interpreter.TypeValue{ + Type: interpreter.InclusiveRangeStaticType{ + ElementType: interpreter.PrimitiveStaticTypeInt, + }, + }, + inter.Globals.Get("a").GetValue(), + ) + + assert.Equal(t, + interpreter.Nil, + inter.Globals.Get("b").GetValue(), + ) + + assert.Equal(t, + interpreter.Nil, + inter.Globals.Get("c").GetValue(), + ) + + assert.Equal(t, + interpreter.Nil, + inter.Globals.Get("d").GetValue(), + ) + + assert.Equal(t, + inter.Globals.Get("a").GetValue(), + inter.Globals.Get("e").GetValue(), + ) +} diff --git a/types.go b/types.go index 8e97823068..f02fc080b2 100644 --- a/types.go +++ b/types.go @@ -1059,6 +1059,52 @@ func (t *DictionaryType) Equal(other Type) bool { t.ElementType.Equal(otherType.ElementType) } +// InclusiveRangeType + +type InclusiveRangeType struct { + ElementType Type + typeID string +} + +var _ Type = &InclusiveRangeType{} + +func NewInclusiveRangeType( + elementType Type, +) *InclusiveRangeType { + return &InclusiveRangeType{ + ElementType: elementType, + } +} + +func NewMeteredInclusiveRangeType( + gauge common.MemoryGauge, + elementType Type, +) *InclusiveRangeType { + common.UseMemory(gauge, common.CadenceInclusiveRangeTypeMemoryUsage) + return NewInclusiveRangeType(elementType) +} + +func (*InclusiveRangeType) isType() {} + +func (t *InclusiveRangeType) ID() string { + if t.typeID == "" { + t.typeID = fmt.Sprintf( + "InclusiveRange<%s>", + t.ElementType.ID(), + ) + } + return t.typeID +} + +func (t *InclusiveRangeType) Equal(other Type) bool { + otherType, ok := other.(*InclusiveRangeType) + if !ok { + return false + } + + return t.ElementType.Equal(otherType.ElementType) +} + // Field type Field struct { diff --git a/values.go b/values.go index d091806866..9e6eed9cac 100644 --- a/values.go +++ b/values.go @@ -2051,6 +2051,92 @@ func (v Contract) GetFieldValues() []Value { return v.Fields } +// InclusiveRange + +type InclusiveRange struct { + InclusiveRangeType *InclusiveRangeType + Start Value + End Value + Step Value + fields []Field +} + +var _ Value = &InclusiveRange{} + +func NewInclusiveRange(start, end, step Value) *InclusiveRange { + return &InclusiveRange{ + Start: start, + End: end, + Step: step, + } +} + +func NewMeteredInclusiveRange( + gauge common.MemoryGauge, + start, end, step Value, +) *InclusiveRange { + common.UseMemory(gauge, common.CadenceInclusiveRangeValueMemoryUsage) + return NewInclusiveRange(start, end, step) +} + +func (*InclusiveRange) isValue() {} + +func (v *InclusiveRange) Type() Type { + if v.InclusiveRangeType == nil { + // Return nil Type instead of Type referencing nil *InclusiveRangeType, + // so caller can check if v's type is nil and also prevent nil pointer dereference. + return nil + } + return v.InclusiveRangeType +} + +func (v *InclusiveRange) MeteredType(common.MemoryGauge) Type { + return v.Type() +} + +func (v *InclusiveRange) WithType(typ *InclusiveRangeType) *InclusiveRange { + v.InclusiveRangeType = typ + return v +} + +func (v *InclusiveRange) ToGoValue() any { + return []any{ + v.Start.ToGoValue(), + v.End.ToGoValue(), + v.Step.ToGoValue(), + } +} + +func (v *InclusiveRange) String() string { + if v.InclusiveRangeType == nil { + return "" + } + + if v.fields == nil { + elementType := v.InclusiveRangeType.ElementType + v.fields = []Field{ + { + Identifier: sema.InclusiveRangeTypeStartFieldName, + Type: elementType, + }, + { + Identifier: sema.InclusiveRangeTypeEndFieldName, + Type: elementType, + }, + { + Identifier: sema.InclusiveRangeTypeStepFieldName, + Type: elementType, + }, + } + } + + return formatComposite( + v.InclusiveRangeType.ID(), + v.fields, + []Value{v.Start, v.End, v.Step}, + ) +} + // PathLink type PathLink struct { diff --git a/values_test.go b/values_test.go index ff9d4937b5..d0e6d4e329 100644 --- a/values_test.go +++ b/values_test.go @@ -220,6 +220,14 @@ func newValueTestCases() map[string]valueTestCase { }, string: "{\"key\": \"value\"}", }, + "InclusiveRange": { + value: NewInclusiveRange(NewInt(85), NewInt(-85), NewInt(-2)), + exampleType: NewInclusiveRangeType(IntType{}), + withType: func(value Value, ty Type) Value { + return value.(*InclusiveRange).WithType(ty.(*InclusiveRangeType)) + }, + string: "InclusiveRange(start: 85, end: -85, step: -2)", + }, "Bytes": { value: NewBytes([]byte{0x1, 0x2}), string: "[0x1, 0x2]",