diff --git a/encoding/ccf/ccf_test.go b/encoding/ccf/ccf_test.go index 26cb346a64..b6647e9e15 100644 --- a/encoding/ccf/ccf_test.go +++ b/encoding/ccf/ccf_test.go @@ -5697,6 +5697,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.Int256Type)) + }(), + 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.Int8Type)) + }(), + 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() @@ -8127,6 +8229,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() @@ -13677,6 +13816,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{ @@ -13873,6 +14225,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 e7a0f5d30d..97cf6f2658 100644 --- a/encoding/ccf/consts.go +++ b/encoding/ccf/consts.go @@ -64,7 +64,7 @@ const ( CBORTagReferenceType CBORTagIntersectionType CBORTagCapabilityType - _ + CBORTagInclusiveRangeType _ _ _ @@ -120,7 +120,7 @@ const ( CBORTagIntersectionTypeValue CBORTagCapabilityTypeValue CBORTagFunctionTypeValue - _ + CBORTagInclusiveRangeTypeValue // InclusiveRange is stored as a composite value. _ _ _ diff --git a/encoding/ccf/decode.go b/encoding/ccf/decode.go index 3050bc337f..6a3df5b482 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 // @@ -549,6 +550,9 @@ func (d *Decoder) decodeValue(t cadence.Type, types *cadenceTypeByCCFTypeID) (ca // When static type is a reference type, encoded value is its deferenced type. return d.decodeValue(t.Type, types) + case *cadence.InclusiveRangeType: + return d.decodeInclusiveRange(t, types) + default: nt, err := d.dec.NextType() if err != nil { @@ -1311,6 +1315,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 = [ @@ -1423,6 +1473,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 @@ -1463,6 +1514,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 cd190c3086..6889e0f3df 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 // / intersection-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) @@ -273,6 +277,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 f3fa139021..3feb255e5a 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) @@ -974,6 +978,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)] @@ -1201,6 +1233,7 @@ func (e *Encoder) encodeFunction(typ *cadence.FunctionType, visited ccfTypeIDByC // / reference-type-value // / intersection-type-value // / capability-type-value +// / inclusiverange-type-value // / type-value-ref // // TypeValue is used differently from inline type or type definition. @@ -1251,6 +1284,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) @@ -1380,6 +1416,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 970784e846..fc3d3b8fd8 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 // / intersection-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 d35d497add..baed2c00ab 100644 --- a/encoding/ccf/traverse_value.go +++ b/encoding/ccf/traverse_value.go @@ -181,7 +181,8 @@ func (ct *compositeTypes) traverseType(typ cadence.Type) (checkRuntimeType bool) return true case cadence.BytesType, - *cadence.FunctionType: + *cadence.FunctionType, + *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 9f4fbfece6..20f2aa4af6 100644 --- a/encoding/json/decode.go +++ b/encoding/json/decode.go @@ -139,6 +139,10 @@ const ( typeBoundKey = "typeBound" purityKey = "purity" functionTypeKey = "functionType" + elementKey = "element" + startKey = "start" + endKey = "end" + stepKey = "step" ) func (d *Decoder) decodeJSON(v any) cadence.Value { @@ -225,6 +229,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: @@ -869,6 +875,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) @@ -1263,6 +1289,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 38c07d18b8..9cc042d636 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"` @@ -216,47 +222,53 @@ type jsonFunctionValue struct { FunctionType jsonValue `json:"functionType"` } +type jsonInclusiveRangeType struct { + ElementType jsonValue `json:"element"` + Kind string `json:"kind"` +} + 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" - 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" + pathTypeStr = "Path" + typeTypeStr = "Type" + capabilityTypeStr = "Capability" + enumTypeStr = "Enum" + functionTypeStr = "Function" + inclusiveRangeTypeStr = "InclusiveRange" ) // Prepare traverses the object graph of the provided value and constructs @@ -323,6 +335,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: @@ -578,6 +592,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) } @@ -791,6 +816,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 46cca7db4f..00b33f6822 100644 --- a/encoding/json/encoding_test.go +++ b/encoding/json/encoding_test.go @@ -1443,6 +1443,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.Int256Type)), + // 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() @@ -1828,6 +1864,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 5288bc4684..c3cdf5fc30 100644 --- a/runtime/common/memorykind.go +++ b/runtime/common/memorykind.go @@ -68,6 +68,7 @@ const ( MemoryKindVariableSizedStaticType MemoryKindConstantSizedStaticType MemoryKindDictionaryStaticType + MemoryKindInclusiveRangeStaticType MemoryKindOptionalStaticType MemoryKindIntersectionStaticType MemoryKindEntitlementSetStaticAccess @@ -88,6 +89,7 @@ const ( MemoryKindCadenceArrayValueBase MemoryKindCadenceArrayValueLength MemoryKindCadenceDictionaryValue + MemoryKindCadenceInclusiveRangeValue MemoryKindCadenceKeyValuePair MemoryKindCadenceStructValueBase MemoryKindCadenceStructValueSize @@ -111,6 +113,7 @@ const ( MemoryKindCadenceVariableSizedArrayType MemoryKindCadenceConstantSizedArrayType MemoryKindCadenceDictionaryType + MemoryKindCadenceInclusiveRangeType MemoryKindCadenceField MemoryKindCadenceParameter MemoryKindCadenceTypeParameter @@ -244,6 +247,7 @@ const ( MemoryKindEntitlementMapSemaType MemoryKindEntitlementRelationSemaType MemoryKindCapabilitySemaType + MemoryKindInclusiveRangeSemaType // ordered-map MemoryKindOrderedMap diff --git a/runtime/common/memorykind_string.go b/runtime/common/memorykind_string.go index 6790aa4a22..65ac875cbe 100644 --- a/runtime/common/memorykind_string.go +++ b/runtime/common/memorykind_string.go @@ -45,170 +45,174 @@ func _() { _ = x[MemoryKindVariableSizedStaticType-34] _ = x[MemoryKindConstantSizedStaticType-35] _ = x[MemoryKindDictionaryStaticType-36] - _ = x[MemoryKindOptionalStaticType-37] - _ = x[MemoryKindIntersectionStaticType-38] - _ = x[MemoryKindEntitlementSetStaticAccess-39] - _ = x[MemoryKindEntitlementMapStaticAccess-40] - _ = x[MemoryKindReferenceStaticType-41] - _ = x[MemoryKindCapabilityStaticType-42] - _ = x[MemoryKindFunctionStaticType-43] - _ = x[MemoryKindCadenceVoidValue-44] - _ = x[MemoryKindCadenceOptionalValue-45] - _ = x[MemoryKindCadenceBoolValue-46] - _ = x[MemoryKindCadenceStringValue-47] - _ = x[MemoryKindCadenceCharacterValue-48] - _ = x[MemoryKindCadenceAddressValue-49] - _ = x[MemoryKindCadenceIntValue-50] - _ = x[MemoryKindCadenceNumberValue-51] - _ = x[MemoryKindCadenceArrayValueBase-52] - _ = x[MemoryKindCadenceArrayValueLength-53] - _ = x[MemoryKindCadenceDictionaryValue-54] - _ = x[MemoryKindCadenceKeyValuePair-55] - _ = x[MemoryKindCadenceStructValueBase-56] - _ = x[MemoryKindCadenceStructValueSize-57] - _ = x[MemoryKindCadenceResourceValueBase-58] - _ = x[MemoryKindCadenceAttachmentValueBase-59] - _ = x[MemoryKindCadenceResourceValueSize-60] - _ = x[MemoryKindCadenceAttachmentValueSize-61] - _ = x[MemoryKindCadenceEventValueBase-62] - _ = x[MemoryKindCadenceEventValueSize-63] - _ = x[MemoryKindCadenceContractValueBase-64] - _ = x[MemoryKindCadenceContractValueSize-65] - _ = x[MemoryKindCadenceEnumValueBase-66] - _ = x[MemoryKindCadenceEnumValueSize-67] - _ = x[MemoryKindCadencePathValue-68] - _ = x[MemoryKindCadenceTypeValue-69] - _ = x[MemoryKindCadenceCapabilityValue-70] - _ = x[MemoryKindCadenceFunctionValue-71] - _ = x[MemoryKindCadenceOptionalType-72] - _ = x[MemoryKindCadenceVariableSizedArrayType-73] - _ = x[MemoryKindCadenceConstantSizedArrayType-74] - _ = x[MemoryKindCadenceDictionaryType-75] - _ = x[MemoryKindCadenceField-76] - _ = x[MemoryKindCadenceParameter-77] - _ = x[MemoryKindCadenceTypeParameter-78] - _ = x[MemoryKindCadenceStructType-79] - _ = x[MemoryKindCadenceResourceType-80] - _ = x[MemoryKindCadenceAttachmentType-81] - _ = x[MemoryKindCadenceEventType-82] - _ = x[MemoryKindCadenceContractType-83] - _ = x[MemoryKindCadenceStructInterfaceType-84] - _ = x[MemoryKindCadenceResourceInterfaceType-85] - _ = x[MemoryKindCadenceContractInterfaceType-86] - _ = x[MemoryKindCadenceFunctionType-87] - _ = x[MemoryKindCadenceEntitlementSetAccess-88] - _ = x[MemoryKindCadenceEntitlementMapAccess-89] - _ = x[MemoryKindCadenceReferenceType-90] - _ = x[MemoryKindCadenceIntersectionType-91] - _ = x[MemoryKindCadenceCapabilityType-92] - _ = x[MemoryKindCadenceEnumType-93] - _ = x[MemoryKindRawString-94] - _ = x[MemoryKindAddressLocation-95] - _ = x[MemoryKindBytes-96] - _ = x[MemoryKindVariable-97] - _ = x[MemoryKindCompositeTypeInfo-98] - _ = x[MemoryKindCompositeField-99] - _ = x[MemoryKindInvocation-100] - _ = x[MemoryKindStorageMap-101] - _ = x[MemoryKindStorageKey-102] - _ = x[MemoryKindTypeToken-103] - _ = x[MemoryKindErrorToken-104] - _ = x[MemoryKindSpaceToken-105] - _ = x[MemoryKindProgram-106] - _ = x[MemoryKindIdentifier-107] - _ = x[MemoryKindArgument-108] - _ = x[MemoryKindBlock-109] - _ = x[MemoryKindFunctionBlock-110] - _ = x[MemoryKindParameter-111] - _ = x[MemoryKindParameterList-112] - _ = x[MemoryKindTypeParameter-113] - _ = x[MemoryKindTypeParameterList-114] - _ = x[MemoryKindTransfer-115] - _ = x[MemoryKindMembers-116] - _ = x[MemoryKindTypeAnnotation-117] - _ = x[MemoryKindDictionaryEntry-118] - _ = x[MemoryKindFunctionDeclaration-119] - _ = x[MemoryKindCompositeDeclaration-120] - _ = x[MemoryKindAttachmentDeclaration-121] - _ = x[MemoryKindInterfaceDeclaration-122] - _ = x[MemoryKindEntitlementDeclaration-123] - _ = x[MemoryKindEntitlementMappingElement-124] - _ = x[MemoryKindEntitlementMappingDeclaration-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[MemoryKindIntersectionType-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[MemoryKindIntersectionSemaType-186] - _ = x[MemoryKindReferenceSemaType-187] - _ = x[MemoryKindEntitlementSemaType-188] - _ = x[MemoryKindEntitlementMapSemaType-189] - _ = x[MemoryKindEntitlementRelationSemaType-190] - _ = x[MemoryKindCapabilitySemaType-191] - _ = x[MemoryKindOrderedMap-192] - _ = x[MemoryKindOrderedMapEntryList-193] - _ = x[MemoryKindOrderedMapEntry-194] - _ = x[MemoryKindLast-195] + _ = x[MemoryKindInclusiveRangeStaticType-37] + _ = x[MemoryKindOptionalStaticType-38] + _ = x[MemoryKindIntersectionStaticType-39] + _ = x[MemoryKindEntitlementSetStaticAccess-40] + _ = x[MemoryKindEntitlementMapStaticAccess-41] + _ = x[MemoryKindReferenceStaticType-42] + _ = x[MemoryKindCapabilityStaticType-43] + _ = x[MemoryKindFunctionStaticType-44] + _ = x[MemoryKindCadenceVoidValue-45] + _ = x[MemoryKindCadenceOptionalValue-46] + _ = x[MemoryKindCadenceBoolValue-47] + _ = x[MemoryKindCadenceStringValue-48] + _ = x[MemoryKindCadenceCharacterValue-49] + _ = x[MemoryKindCadenceAddressValue-50] + _ = x[MemoryKindCadenceIntValue-51] + _ = x[MemoryKindCadenceNumberValue-52] + _ = x[MemoryKindCadenceArrayValueBase-53] + _ = x[MemoryKindCadenceArrayValueLength-54] + _ = x[MemoryKindCadenceDictionaryValue-55] + _ = x[MemoryKindCadenceInclusiveRangeValue-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[MemoryKindCadencePathValue-70] + _ = x[MemoryKindCadenceTypeValue-71] + _ = x[MemoryKindCadenceCapabilityValue-72] + _ = x[MemoryKindCadenceFunctionValue-73] + _ = x[MemoryKindCadenceOptionalType-74] + _ = x[MemoryKindCadenceVariableSizedArrayType-75] + _ = x[MemoryKindCadenceConstantSizedArrayType-76] + _ = x[MemoryKindCadenceDictionaryType-77] + _ = x[MemoryKindCadenceInclusiveRangeType-78] + _ = x[MemoryKindCadenceField-79] + _ = x[MemoryKindCadenceParameter-80] + _ = x[MemoryKindCadenceTypeParameter-81] + _ = x[MemoryKindCadenceStructType-82] + _ = x[MemoryKindCadenceResourceType-83] + _ = x[MemoryKindCadenceAttachmentType-84] + _ = x[MemoryKindCadenceEventType-85] + _ = x[MemoryKindCadenceContractType-86] + _ = x[MemoryKindCadenceStructInterfaceType-87] + _ = x[MemoryKindCadenceResourceInterfaceType-88] + _ = x[MemoryKindCadenceContractInterfaceType-89] + _ = x[MemoryKindCadenceFunctionType-90] + _ = x[MemoryKindCadenceEntitlementSetAccess-91] + _ = x[MemoryKindCadenceEntitlementMapAccess-92] + _ = x[MemoryKindCadenceReferenceType-93] + _ = x[MemoryKindCadenceIntersectionType-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[MemoryKindEntitlementDeclaration-126] + _ = x[MemoryKindEntitlementMappingElement-127] + _ = x[MemoryKindEntitlementMappingDeclaration-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[MemoryKindIntersectionType-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[MemoryKindIntersectionSemaType-189] + _ = x[MemoryKindReferenceSemaType-190] + _ = x[MemoryKindEntitlementSemaType-191] + _ = x[MemoryKindEntitlementMapSemaType-192] + _ = x[MemoryKindEntitlementRelationSemaType-193] + _ = x[MemoryKindCapabilitySemaType-194] + _ = x[MemoryKindInclusiveRangeSemaType-195] + _ = x[MemoryKindOrderedMap-196] + _ = x[MemoryKindOrderedMapEntryList-197] + _ = x[MemoryKindOrderedMapEntry-198] + _ = x[MemoryKindLast-199] } -const _MemoryKind_name = "UnknownAddressValueStringValueCharacterValueNumberValueArrayValueBaseDictionaryValueBaseCompositeValueBaseSimpleCompositeValueBaseOptionalValueTypeValuePathValueCapabilityValueStorageReferenceValueEphemeralReferenceValueInterpretedFunctionValueHostFunctionValueBoundFunctionValueBigIntSimpleCompositeValuePublishedValueStorageCapabilityControllerValueAccountCapabilityControllerValueAtreeArrayDataSlabAtreeArrayMetaDataSlabAtreeArrayElementOverheadAtreeMapDataSlabAtreeMapMetaDataSlabAtreeMapElementOverheadAtreeMapPreAllocatedElementAtreeEncodedSlabPrimitiveStaticTypeCompositeStaticTypeInterfaceStaticTypeVariableSizedStaticTypeConstantSizedStaticTypeDictionaryStaticTypeOptionalStaticTypeIntersectionStaticTypeEntitlementSetStaticAccessEntitlementMapStaticAccessReferenceStaticTypeCapabilityStaticTypeFunctionStaticTypeCadenceVoidValueCadenceOptionalValueCadenceBoolValueCadenceStringValueCadenceCharacterValueCadenceAddressValueCadenceIntValueCadenceNumberValueCadenceArrayValueBaseCadenceArrayValueLengthCadenceDictionaryValueCadenceKeyValuePairCadenceStructValueBaseCadenceStructValueSizeCadenceResourceValueBaseCadenceAttachmentValueBaseCadenceResourceValueSizeCadenceAttachmentValueSizeCadenceEventValueBaseCadenceEventValueSizeCadenceContractValueBaseCadenceContractValueSizeCadenceEnumValueBaseCadenceEnumValueSizeCadencePathValueCadenceTypeValueCadenceCapabilityValueCadenceFunctionValueCadenceOptionalTypeCadenceVariableSizedArrayTypeCadenceConstantSizedArrayTypeCadenceDictionaryTypeCadenceFieldCadenceParameterCadenceTypeParameterCadenceStructTypeCadenceResourceTypeCadenceAttachmentTypeCadenceEventTypeCadenceContractTypeCadenceStructInterfaceTypeCadenceResourceInterfaceTypeCadenceContractInterfaceTypeCadenceFunctionTypeCadenceEntitlementSetAccessCadenceEntitlementMapAccessCadenceReferenceTypeCadenceIntersectionTypeCadenceCapabilityTypeCadenceEnumTypeRawStringAddressLocationBytesVariableCompositeTypeInfoCompositeFieldInvocationStorageMapStorageKeyTypeTokenErrorTokenSpaceTokenProgramIdentifierArgumentBlockFunctionBlockParameterParameterListTypeParameterTypeParameterListTransferMembersTypeAnnotationDictionaryEntryFunctionDeclarationCompositeDeclarationAttachmentDeclarationInterfaceDeclarationEntitlementDeclarationEntitlementMappingElementEntitlementMappingDeclarationEnumCaseDeclarationFieldDeclarationTransactionDeclarationImportDeclarationVariableDeclarationSpecialFunctionDeclarationPragmaDeclarationAssignmentStatementBreakStatementContinueStatementEmitStatementExpressionStatementForStatementIfStatementReturnStatementSwapStatementSwitchStatementWhileStatementRemoveStatementBooleanExpressionVoidExpressionNilExpressionStringExpressionIntegerExpressionFixedPointExpressionArrayExpressionDictionaryExpressionIdentifierExpressionInvocationExpressionMemberExpressionIndexExpressionConditionalExpressionUnaryExpressionBinaryExpressionFunctionExpressionCastingExpressionCreateExpressionDestroyExpressionReferenceExpressionForceExpressionPathExpressionAttachExpressionConstantSizedTypeDictionaryTypeFunctionTypeInstantiationTypeNominalTypeOptionalTypeReferenceTypeIntersectionTypeVariableSizedTypePositionRangeElaborationActivationActivationEntriesVariableSizedSemaTypeConstantSizedSemaTypeDictionarySemaTypeOptionalSemaTypeIntersectionSemaTypeReferenceSemaTypeEntitlementSemaTypeEntitlementMapSemaTypeEntitlementRelationSemaTypeCapabilitySemaTypeOrderedMapOrderedMapEntryListOrderedMapEntryLast" +const _MemoryKind_name = "UnknownAddressValueStringValueCharacterValueNumberValueArrayValueBaseDictionaryValueBaseCompositeValueBaseSimpleCompositeValueBaseOptionalValueTypeValuePathValueCapabilityValueStorageReferenceValueEphemeralReferenceValueInterpretedFunctionValueHostFunctionValueBoundFunctionValueBigIntSimpleCompositeValuePublishedValueStorageCapabilityControllerValueAccountCapabilityControllerValueAtreeArrayDataSlabAtreeArrayMetaDataSlabAtreeArrayElementOverheadAtreeMapDataSlabAtreeMapMetaDataSlabAtreeMapElementOverheadAtreeMapPreAllocatedElementAtreeEncodedSlabPrimitiveStaticTypeCompositeStaticTypeInterfaceStaticTypeVariableSizedStaticTypeConstantSizedStaticTypeDictionaryStaticTypeInclusiveRangeStaticTypeOptionalStaticTypeIntersectionStaticTypeEntitlementSetStaticAccessEntitlementMapStaticAccessReferenceStaticTypeCapabilityStaticTypeFunctionStaticTypeCadenceVoidValueCadenceOptionalValueCadenceBoolValueCadenceStringValueCadenceCharacterValueCadenceAddressValueCadenceIntValueCadenceNumberValueCadenceArrayValueBaseCadenceArrayValueLengthCadenceDictionaryValueCadenceInclusiveRangeValueCadenceKeyValuePairCadenceStructValueBaseCadenceStructValueSizeCadenceResourceValueBaseCadenceAttachmentValueBaseCadenceResourceValueSizeCadenceAttachmentValueSizeCadenceEventValueBaseCadenceEventValueSizeCadenceContractValueBaseCadenceContractValueSizeCadenceEnumValueBaseCadenceEnumValueSizeCadencePathValueCadenceTypeValueCadenceCapabilityValueCadenceFunctionValueCadenceOptionalTypeCadenceVariableSizedArrayTypeCadenceConstantSizedArrayTypeCadenceDictionaryTypeCadenceInclusiveRangeTypeCadenceFieldCadenceParameterCadenceTypeParameterCadenceStructTypeCadenceResourceTypeCadenceAttachmentTypeCadenceEventTypeCadenceContractTypeCadenceStructInterfaceTypeCadenceResourceInterfaceTypeCadenceContractInterfaceTypeCadenceFunctionTypeCadenceEntitlementSetAccessCadenceEntitlementMapAccessCadenceReferenceTypeCadenceIntersectionTypeCadenceCapabilityTypeCadenceEnumTypeRawStringAddressLocationBytesVariableCompositeTypeInfoCompositeFieldInvocationStorageMapStorageKeyTypeTokenErrorTokenSpaceTokenProgramIdentifierArgumentBlockFunctionBlockParameterParameterListTypeParameterTypeParameterListTransferMembersTypeAnnotationDictionaryEntryFunctionDeclarationCompositeDeclarationAttachmentDeclarationInterfaceDeclarationEntitlementDeclarationEntitlementMappingElementEntitlementMappingDeclarationEnumCaseDeclarationFieldDeclarationTransactionDeclarationImportDeclarationVariableDeclarationSpecialFunctionDeclarationPragmaDeclarationAssignmentStatementBreakStatementContinueStatementEmitStatementExpressionStatementForStatementIfStatementReturnStatementSwapStatementSwitchStatementWhileStatementRemoveStatementBooleanExpressionVoidExpressionNilExpressionStringExpressionIntegerExpressionFixedPointExpressionArrayExpressionDictionaryExpressionIdentifierExpressionInvocationExpressionMemberExpressionIndexExpressionConditionalExpressionUnaryExpressionBinaryExpressionFunctionExpressionCastingExpressionCreateExpressionDestroyExpressionReferenceExpressionForceExpressionPathExpressionAttachExpressionConstantSizedTypeDictionaryTypeFunctionTypeInstantiationTypeNominalTypeOptionalTypeReferenceTypeIntersectionTypeVariableSizedTypePositionRangeElaborationActivationActivationEntriesVariableSizedSemaTypeConstantSizedSemaTypeDictionarySemaTypeOptionalSemaTypeIntersectionSemaTypeReferenceSemaTypeEntitlementSemaTypeEntitlementMapSemaTypeEntitlementRelationSemaTypeCapabilitySemaTypeInclusiveRangeSemaTypeOrderedMapOrderedMapEntryListOrderedMapEntryLast" -var _MemoryKind_index = [...]uint16{0, 7, 19, 30, 44, 55, 69, 88, 106, 130, 143, 152, 161, 176, 197, 220, 244, 261, 279, 285, 305, 319, 351, 383, 401, 423, 448, 464, 484, 507, 534, 550, 569, 588, 607, 630, 653, 673, 691, 713, 739, 765, 784, 804, 822, 838, 858, 874, 892, 913, 932, 947, 965, 986, 1009, 1031, 1050, 1072, 1094, 1118, 1144, 1168, 1194, 1215, 1236, 1260, 1284, 1304, 1324, 1340, 1356, 1378, 1398, 1417, 1446, 1475, 1496, 1508, 1524, 1544, 1561, 1580, 1601, 1617, 1636, 1662, 1690, 1718, 1737, 1764, 1791, 1811, 1834, 1855, 1870, 1879, 1894, 1899, 1907, 1924, 1938, 1948, 1958, 1968, 1977, 1987, 1997, 2004, 2014, 2022, 2027, 2040, 2049, 2062, 2075, 2092, 2100, 2107, 2121, 2136, 2155, 2175, 2196, 2216, 2238, 2263, 2292, 2311, 2327, 2349, 2366, 2385, 2411, 2428, 2447, 2461, 2478, 2491, 2510, 2522, 2533, 2548, 2561, 2576, 2590, 2605, 2622, 2636, 2649, 2665, 2682, 2702, 2717, 2737, 2757, 2777, 2793, 2808, 2829, 2844, 2860, 2878, 2895, 2911, 2928, 2947, 2962, 2976, 2992, 3009, 3023, 3035, 3052, 3063, 3075, 3088, 3104, 3121, 3129, 3134, 3145, 3155, 3172, 3193, 3214, 3232, 3248, 3268, 3285, 3304, 3326, 3353, 3371, 3381, 3400, 3415, 3419} +var _MemoryKind_index = [...]uint16{0, 7, 19, 30, 44, 55, 69, 88, 106, 130, 143, 152, 161, 176, 197, 220, 244, 261, 279, 285, 305, 319, 351, 383, 401, 423, 448, 464, 484, 507, 534, 550, 569, 588, 607, 630, 653, 673, 697, 715, 737, 763, 789, 808, 828, 846, 862, 882, 898, 916, 937, 956, 971, 989, 1010, 1033, 1055, 1081, 1100, 1122, 1144, 1168, 1194, 1218, 1244, 1265, 1286, 1310, 1334, 1354, 1374, 1390, 1406, 1428, 1448, 1467, 1496, 1525, 1546, 1571, 1583, 1599, 1619, 1636, 1655, 1676, 1692, 1711, 1737, 1765, 1793, 1812, 1839, 1866, 1886, 1909, 1930, 1945, 1954, 1969, 1974, 1982, 1999, 2013, 2023, 2033, 2043, 2052, 2062, 2072, 2079, 2089, 2097, 2102, 2115, 2124, 2137, 2150, 2167, 2175, 2182, 2196, 2211, 2230, 2250, 2271, 2291, 2313, 2338, 2367, 2386, 2402, 2424, 2441, 2460, 2486, 2503, 2522, 2536, 2553, 2566, 2585, 2597, 2608, 2623, 2636, 2651, 2665, 2680, 2697, 2711, 2724, 2740, 2757, 2777, 2792, 2812, 2832, 2852, 2868, 2883, 2904, 2919, 2935, 2953, 2970, 2986, 3003, 3022, 3037, 3051, 3067, 3084, 3098, 3110, 3127, 3138, 3150, 3163, 3179, 3196, 3204, 3209, 3220, 3230, 3247, 3268, 3289, 3307, 3323, 3343, 3360, 3379, 3401, 3428, 3446, 3468, 3478, 3497, 3512, 3516} 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 23abced4d3..529a03a7ef 100644 --- a/runtime/common/metering.go +++ b/runtime/common/metering.go @@ -170,6 +170,8 @@ var ( CapabilityStaticTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindCapabilityStaticType) FunctionStaticTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindFunctionStaticType) EntitlementMapStaticTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindEntitlementMapStaticAccess) + InclusiveRangeStaticTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindInclusiveRangeStaticType) + // Sema types VariableSizedSemaTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindVariableSizedSemaType) @@ -182,6 +184,7 @@ var ( EntitlementMapSemaTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindEntitlementMapSemaType) EntitlementRelationSemaTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindEntitlementRelationSemaType) CapabilitySemaTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindCapabilitySemaType) + InclusiveRangeSemaTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindInclusiveRangeSemaType) // Storage related memory usages @@ -193,6 +196,7 @@ var ( // Cadence external values CadenceDictionaryValueMemoryUsage = NewConstantMemoryUsage(MemoryKindCadenceDictionaryValue) + CadenceInclusiveRangeValueMemoryUsage = NewConstantMemoryUsage(MemoryKindCadenceInclusiveRangeValue) CadenceArrayValueBaseMemoryUsage = NewConstantMemoryUsage(MemoryKindCadenceArrayValueBase) CadenceStructValueBaseMemoryUsage = NewConstantMemoryUsage(MemoryKindCadenceStructValueBase) CadenceResourceValueBaseMemoryUsage = NewConstantMemoryUsage(MemoryKindCadenceResourceValueBase) @@ -218,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,6 +269,7 @@ var ( CapabilityStaticTypeStringMemoryUsage = NewRawStringMemoryUsage(12) // Capability<> IntersectionStaticTypeStringMemoryUsage = NewRawStringMemoryUsage(2) // {} IntersectionStaticTypeSeparatorStringMemoryUsage = NewRawStringMemoryUsage(2) // , + InclusiveRangeStaticTypeStringMemoryUsage = NewRawStringMemoryUsage(16) // InclusiveRange<> ) func UseMemory(gauge MemoryGauge, usage MemoryUsage) { diff --git a/runtime/convertTypes.go b/runtime/convertTypes.go index efa8d9115e..441eea8b07 100644 --- a/runtime/convertTypes.go +++ b/runtime/convertTypes.go @@ -258,6 +258,8 @@ func ExportMeteredType( return exportIntersectionType(gauge, t, results) case *sema.CapabilityType: return exportCapabilityType(gauge, t, results) + case *sema.InclusiveRangeType: + return exportInclusiveRangeType(gauge, t, results) } panic(fmt.Sprintf("cannot export type %s", t)) @@ -481,6 +483,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, @@ -691,7 +706,11 @@ 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 f67836cab9..72212ac085 100644 --- a/runtime/convertValues.go +++ b/runtime/convertValues.go @@ -372,6 +372,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()) @@ -596,6 +606,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 exportPathValue(gauge common.MemoryGauge, v interpreter.PathValue) (cadence.Path, error) { return cadence.NewMeteredPath( gauge, @@ -801,6 +868,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.Capability: @@ -1291,6 +1360,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 bb46a65e9e..572a0fb0d8 100644 --- a/runtime/convertValues_test.go +++ b/runtime/convertValues_test.go @@ -1283,9 +1283,8 @@ func TestRuntimeImportRuntimeType(t *testing.T) { ReferencedType: interpreter.PrimitiveStaticTypeInt, }, }, - { - label: "Entitlement Disjoint Set Reference", + label: "Reference", actual: &cadence.ReferenceType{ Authorization: cadence.EntitlementSetAuthorization{ Kind: cadence.Disjunction, @@ -1406,6 +1405,15 @@ func TestRuntimeImportRuntimeType(t *testing.T) { }, }, }, + { + label: "InclusiveRange", + actual: &cadence.InclusiveRangeType{ + ElementType: cadence.IntType, + }, + expected: interpreter.InclusiveRangeStaticType{ + ElementType: interpreter.PrimitiveStaticTypeInt, + }, + }, } { test(tt) } @@ -1498,6 +1506,185 @@ func TestRuntimeExportAddressValue(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 := ` + access(all) fun main(): InclusiveRange { + return InclusiveRange(10, 20, step: 2) + } + ` + + inclusiveRangeType := cadence.NewInclusiveRangeType(cadence.IntType) + + actual := exportValueFromScript(t, script) + expected := 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 := ` + access(all) fun main(): InclusiveRange { + return InclusiveRange(10, 20) + } + ` + + inclusiveRangeType := cadence.NewInclusiveRangeType(cadence.IntType) + + actual := exportValueFromScript(t, script) + expected := 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 TestRuntimeExportStructValue(t *testing.T) { t.Parallel() @@ -2565,6 +2752,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", @@ -3360,6 +3558,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 7b0eec28c0..eaad5f0d4b 100644 --- a/runtime/imported_values_memory_metering_test.go +++ b/runtime/imported_values_memory_metering_test.go @@ -412,6 +412,29 @@ func TestRuntimeImportedValueMemoryMetering(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(` + access(all) 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 3b86852520..d42782b16b 100644 --- a/runtime/interpreter/decode.go +++ b/runtime/interpreter/decode.go @@ -1442,6 +1442,9 @@ func (d TypeDecoder) DecodeStaticType() (StaticType, error) { case CBORTagCapabilityStaticType: return d.decodeCapabilityStaticType() + case CBORTagInclusiveRangeStaticType: + return d.decodeInclusiveRangeStaticType() + default: return nil, errors.NewUnexpectedError("invalid static type encoding tag: %d", number) } @@ -2026,6 +2029,17 @@ func (d TypeDecoder) decodeCompositeTypeInfo() (atree.TypeInfo, error) { ), 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 DecodeTypeInfo(decoder *cbor.StreamDecoder, memoryGauge common.MemoryGauge) (atree.TypeInfo, error) { d := NewTypeDecoder(decoder, memoryGauge) diff --git a/runtime/interpreter/encode.go b/runtime/interpreter/encode.go index 35c79df6cc..198ecd0e81 100644 --- a/runtime/interpreter/encode.go +++ b/runtime/interpreter/encode.go @@ -224,6 +224,7 @@ const ( CBORTagUnauthorizedStaticAuthorization CBORTagEntitlementMapStaticAuthorization CBORTagEntitlementSetStaticAuthorization + CBORTagInclusiveRangeStaticType // !!! *WARNING* !!! // ADD NEW TYPES *BEFORE* THIS WARNING. @@ -1447,6 +1448,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 ( // encodedIntersectionStaticTypeLegacyTypeFieldKey uint64 = 0 diff --git a/runtime/interpreter/encoding_test.go b/runtime/interpreter/encoding_test.go index 6ad603fddb..23b664a53a 100644 --- a/runtime/interpreter/encoding_test.go +++ b/runtime/interpreter/encoding_test.go @@ -3422,6 +3422,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() @@ -3525,7 +3587,7 @@ func TestCBORTagValue(t *testing.T) { t.Parallel() t.Run("No new types added in between", func(t *testing.T) { - require.Equal(t, byte(225), byte(CBORTag_Count)) + require.Equal(t, byte(226), byte(CBORTag_Count)) }) } diff --git a/runtime/interpreter/errors.go b/runtime/interpreter/errors.go index d3635be8dc..ed6aac5471 100644 --- a/runtime/interpreter/errors.go +++ b/runtime/interpreter/errors.go @@ -1032,3 +1032,22 @@ func (e NestedReferenceError) Error() string { e.Value.String(), ) } + +// 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 c698297e59..bf4da92c18 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -3848,6 +3848,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 081a7341a7..c993c9b983 100644 --- a/runtime/interpreter/statictype.go +++ b/runtime/interpreter/statictype.go @@ -269,6 +269,57 @@ func (t *VariableSizedStaticType) ID() TypeID { return sema.FormatVariableSizedTypeID(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 { @@ -886,6 +937,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) } @@ -1110,6 +1165,24 @@ func ConvertStaticToSemaType( valueType, ), nil + case InclusiveRangeStaticType: + elementType, err := ConvertStaticToSemaType( + memoryGauge, + t.ElementType, + getInterface, + getComposite, + getEntitlement, + getEntitlementMapType, + ) + if err != nil { + return nil, err + } + + return sema.NewInclusiveRangeType( + memoryGauge, + elementType, + ), nil + case *OptionalStaticType: ty, err := ConvertStaticToSemaType( memoryGauge, diff --git a/runtime/interpreter/statictype_test.go b/runtime/interpreter/statictype_test.go index 249a3739a4..5f7cd1382a 100644 --- a/runtime/interpreter/statictype_test.go +++ b/runtime/interpreter/statictype_test.go @@ -688,6 +688,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 TestIntersectionStaticType_Equal(t *testing.T) { t.Parallel() @@ -1616,6 +1666,15 @@ func TestStaticTypeConversion(t *testing.T) { staticType: PrimitiveStaticTypeAccountKey, semaType: nil, }, + { + name: "InclusiveRange", + semaType: &sema.InclusiveRangeType{ + MemberType: sema.IntType, + }, + staticType: InclusiveRangeStaticType{ + ElementType: PrimitiveStaticTypeInt, + }, + }, } test := func(test testCase) { diff --git a/runtime/interpreter/storage_test.go b/runtime/interpreter/storage_test.go index b231df6658..d793f6493f 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 acb95d0040..61497423ef 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -236,7 +236,7 @@ type ContractValue interface { // IterableValue is a value which can be iterated over, e.g. with a for-loop type IterableValue interface { Value - Iterator(interpreter *Interpreter) ValueIterator + Iterator(interpreter *Interpreter, locationRange LocationRange) ValueIterator ForEach( interpreter *Interpreter, elementType sema.Type, @@ -248,7 +248,7 @@ type IterableValue interface { // ValueIterator is an iterator which returns values. // When Next returns nil, it signals the end of the iterator. type ValueIterator interface { - Next(interpreter *Interpreter) Value + Next(interpreter *Interpreter, locationRange LocationRange) Value } func safeAdd(a, b int, locationRange LocationRange) int { @@ -1588,7 +1588,7 @@ func (v *StringValue) ConformsToStaticType( return true } -func (v *StringValue) Iterator(_ *Interpreter) ValueIterator { +func (v *StringValue) Iterator(_ *Interpreter, _ LocationRange) ValueIterator { return StringValueIterator{ graphemes: uniseg.NewGraphemes(v.Str), } @@ -1598,11 +1598,11 @@ func (v *StringValue) ForEach( interpreter *Interpreter, _ sema.Type, function func(value Value) (resume bool), - _ LocationRange, + locationRange LocationRange, ) { - iterator := v.Iterator(interpreter) + iterator := v.Iterator(interpreter, locationRange) for { - value := iterator.Next(interpreter) + value := iterator.Next(interpreter, locationRange) if value == nil { return } @@ -1619,7 +1619,7 @@ type StringValueIterator struct { var _ ValueIterator = StringValueIterator{} -func (i StringValueIterator) Next(_ *Interpreter) Value { +func (i StringValueIterator) Next(_ *Interpreter, _ LocationRange) Value { if !i.graphemes.Next() { return nil } @@ -1641,7 +1641,7 @@ type ArrayValueIterator struct { atreeIterator *atree.ArrayIterator } -func (v *ArrayValue) Iterator(_ *Interpreter) ValueIterator { +func (v *ArrayValue) Iterator(_ *Interpreter, _ LocationRange) ValueIterator { arrayIterator, err := v.array.Iterator() if err != nil { panic(errors.NewExternalError(err)) @@ -1653,7 +1653,7 @@ func (v *ArrayValue) Iterator(_ *Interpreter) ValueIterator { var _ ValueIterator = ArrayValueIterator{} -func (i ArrayValueIterator) Next(interpreter *Interpreter) Value { +func (i ArrayValueIterator) Next(interpreter *Interpreter, _ LocationRange) Value { atreeValue, err := i.atreeIterator.Next() if err != nil { panic(errors.NewExternalError(err)) @@ -16291,8 +16291,13 @@ func (UFix64Value) Scale() int { type FunctionOrderedMap = orderedmap.OrderedMap[string, FunctionValue] type CompositeValue struct { - Location common.Location - staticType StaticType + 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 computedFields map[string]ComputedField @@ -16323,6 +16328,7 @@ const unrepresentableNamePrefix = "$" const resourceDefaultDestroyEventPrefix = ast.ResourceDestructionDefaultEventName + unrepresentableNamePrefix var _ TypeIndexableValue = &CompositeValue{} +var _ IterableValue = &CompositeValue{} func NewCompositeField(memoryGauge common.MemoryGauge, name string, value Value) CompositeField { common.UseMemory(memoryGauge, common.CompositeFieldMemoryUsage) @@ -16336,6 +16342,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, @@ -17154,10 +17187,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 || @@ -17224,6 +17276,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) FieldCount() int { return int(v.dictionary.Count()) } @@ -17902,6 +17990,93 @@ func (v *CompositeValue) RemoveTypeKey( return v.RemoveMember(interpreter, locationRange, attachmentMemberName(attachmentType)) } +func (v *CompositeValue) Iterator(interpreter *Interpreter, locationRange LocationRange) ValueIterator { + staticType := v.StaticType(interpreter) + + switch typ := staticType.(type) { + case InclusiveRangeStaticType: + return NewInclusiveRangeIterator(interpreter, locationRange, v, typ) + + default: + // Must be caught in the checker. + panic(errors.NewUnreachableError()) + } +} + +func (v *CompositeValue) ForEach( + interpreter *Interpreter, + _ sema.Type, + function func(value Value) (resume bool), + locationRange LocationRange, +) { + iterator := v.Iterator(interpreter, locationRange) + for { + value := iterator.Next(interpreter, locationRange) + if value == nil { + return + } + + if !function(value) { + return + } + } +} + +type InclusiveRangeIterator struct { + rangeValue *CompositeValue + next IntegerValue + + // Cached values + stepNegative bool + step IntegerValue + end IntegerValue +} + +var _ ValueIterator = &InclusiveRangeIterator{} + +func NewInclusiveRangeIterator( + interpreter *Interpreter, + locationRange LocationRange, + v *CompositeValue, + typ InclusiveRangeStaticType, +) *InclusiveRangeIterator { + startValue := getFieldAsIntegerValue(interpreter, v, locationRange, sema.InclusiveRangeTypeStartFieldName) + + zeroValue := GetSmallIntegerValue(0, typ.ElementType) + endValue := getFieldAsIntegerValue(interpreter, v, locationRange, sema.InclusiveRangeTypeEndFieldName) + + stepValue := getFieldAsIntegerValue(interpreter, v, locationRange, sema.InclusiveRangeTypeStepFieldName) + stepNegative := stepValue.Less(interpreter, zeroValue, locationRange) + + return &InclusiveRangeIterator{ + rangeValue: v, + next: startValue, + stepNegative: bool(stepNegative), + step: stepValue, + end: endValue, + } +} + +func (i *InclusiveRangeIterator) Next(interpreter *Interpreter, locationRange LocationRange) Value { + valueToReturn := i.next + + // Ensure that valueToReturn is within the bounds. + if i.stepNegative && bool(valueToReturn.Less(interpreter, i.end, locationRange)) { + return nil + } else if !i.stepNegative && bool(valueToReturn.Greater(interpreter, i.end, locationRange)) { + return nil + } + + // Update the next value. + nextValueToReturn, ok := valueToReturn.Plus(interpreter, i.step, locationRange).(IntegerValue) + if !ok { + panic(errors.NewUnreachableError()) + } + + i.next = nextValueToReturn + return valueToReturn +} + // DictionaryValue type DictionaryValue struct { @@ -20132,7 +20307,7 @@ func (*StorageReferenceValue) DeepRemove(_ *Interpreter) { func (*StorageReferenceValue) isReference() {} -func (v *StorageReferenceValue) Iterator(_ *Interpreter) ValueIterator { +func (v *StorageReferenceValue) Iterator(_ *Interpreter, _ LocationRange) ValueIterator { // Not used for now panic(errors.NewUnreachableError()) } @@ -20496,7 +20671,7 @@ func (*EphemeralReferenceValue) DeepRemove(_ *Interpreter) { func (*EphemeralReferenceValue) isReference() {} -func (v *EphemeralReferenceValue) Iterator(_ *Interpreter) ValueIterator { +func (v *EphemeralReferenceValue) Iterator(_ *Interpreter, _ LocationRange) ValueIterator { // Not used for now panic(errors.NewUnreachableError()) } diff --git a/runtime/interpreter/value_range.go b/runtime/interpreter/value_range.go new file mode 100644 index 0000000000..4174e76440 --- /dev/null +++ b/runtime/interpreter/value_range.go @@ -0,0 +1,273 @@ +/* + * 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/common/orderedmap" + "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 = orderedmap.New[FunctionOrderedMap](1) + + rangeValue.Functions.Set( + 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(interpreter, rangeValue, locationRange, sema.InclusiveRangeTypeStartFieldName) + end := getFieldAsIntegerValue(interpreter, rangeValue, locationRange, sema.InclusiveRangeTypeEndFieldName) + step := getFieldAsIntegerValue(interpreter, rangeValue, 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( + interpreter *Interpreter, + rangeValue *CompositeValue, + 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 2a2f5a86bf..f9a2b693db 100644 --- a/runtime/program_params_validation_test.go +++ b/runtime/program_params_validation_test.go @@ -292,6 +292,104 @@ func TestRuntimeScriptParameterTypeValidation(t *testing.T) { assert.NoError(t, err) }) + t.Run("InclusiveRange", func(t *testing.T) { + t.Parallel() + + script := ` + access(all) 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 := ` + access(all) fun main(arg: AnyStruct) { + } + ` + + err := executeScript( + t, + script, + cadence.NewInclusiveRange( + cadence.NewUInt16(1), + cadence.NewUInt16(2), + cadence.NewUInt16(1), + ), + ) + + assert.NoError(t, err) + }) + + t.Run("Invalid InclusiveRange", func(t *testing.T) { + t.Parallel() + + script := ` + access(all) fun main(arg: InclusiveRange) { + } + ` + + err := executeScript( + t, + script, + cadence.NewInclusiveRange(cadence.NewInt16(1), cadence.NewInt16(2), cadence.NewInt16(1)), + ) + + var checkerError *sema.CheckerError + require.ErrorAs(t, err, &checkerError) + + errs := checker.RequireCheckerErrors(t, checkerError, 1) + assert.IsType(t, &sema.InvalidTypeArgumentError{}, errs[0]) + }) + + t.Run("Invalid InclusiveRange with mixed value types", func(t *testing.T) { + t.Parallel() + + script := ` + access(all) 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 := ` + access(all) fun main(arg: InclusiveRange) { + } + ` + + err := executeScript( + t, + script, + cadence.NewInclusiveRange(cadence.NewInt16(1), cadence.NewUInt(2), cadence.NewUInt(1)), + ) + + var checkerError *sema.CheckerError + require.ErrorAs(t, err, &checkerError) + + errs := checker.RequireCheckerErrors(t, checkerError, 1) + assert.IsType(t, &sema.InvalidTypeArgumentError{}, errs[0]) + }) + t.Run("Capability", func(t *testing.T) { t.Parallel() diff --git a/runtime/runtime_test.go b/runtime/runtime_test.go index 09c3cd8737..2d713abda7 100644 --- a/runtime/runtime_test.go +++ b/runtime/runtime_test.go @@ -1676,6 +1676,69 @@ func TestRuntimeCompositeFunctionInvocationFromImportingProgram(t *testing.T) { require.NoError(t, err) } +func TestRuntimeStorageMultipleTransactionsInclusiveRangeFunction(t *testing.T) { + + t.Parallel() + + runtime := NewTestInterpreterRuntime() + + inclusiveRangeCreation := []byte(` + access(all) fun createInclusiveRange(): InclusiveRange { + return InclusiveRange(10, 20) + } + `) + + script1 := []byte(` + import "inclusive-range-creation" + transaction { + prepare(signer: auth(Storage) &Account) { + let ir = createInclusiveRange() + signer.storage.save(ir, to: /storage/inclusiveRange) + } + } + `) + + ledger := NewTestLedger(nil, nil) + + runtimeInterface := &TestRuntimeInterface{ + OnGetCode: 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, + OnGetSigningAccounts: 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() @@ -8846,6 +8909,10 @@ func TestRuntimeTypesAndConversions(t *testing.T) { } for name, ty := range checker.AllBaseSemaTypes() { + // Inclusive range is a dynamically created type. + if _, isInclusiveRange := ty.(*sema.InclusiveRangeType); isInclusiveRange { + continue + } test(name, ty) } } diff --git a/runtime/sema/check_for.go b/runtime/sema/check_for.go index da3fe156fb..31c9e1d92a 100644 --- a/runtime/sema/check_for.go +++ b/runtime/sema/check_for.go @@ -154,8 +154,11 @@ func (checker *Checker) loopVariableType(valueType Type, hasPosition ast.HasPosi } func (checker *Checker) iterableElementType(valueType Type, hasPosition ast.HasPosition) Type { - if arrayType, ok := valueType.(ArrayType); ok { - return arrayType.ElementType(false) + switch valueType := valueType.(type) { + case ArrayType: + return valueType.ElementType(false) + case *InclusiveRangeType: + return valueType.MemberType } if valueType == StringType { diff --git a/runtime/sema/check_invocation_expression.go b/runtime/sema/check_invocation_expression.go index 7bdf8c2c25..df25379fad 100644 --- a/runtime/sema/check_invocation_expression.go +++ b/runtime/sema/check_invocation_expression.go @@ -464,6 +464,20 @@ func (checker *Checker) checkInvocation( } } + // Compute the invocation range, once, if needed + getInvocationRange := func() func() ast.Range { + var invocationRange ast.Range + return func() ast.Range { + if invocationRange == ast.EmptyRange { + invocationRange = ast.NewRangeFromPositioned( + checker.memoryGauge, + invocationExpression, + ) + } + return invocationRange + } + }() + // The invokable type might have special checks for the arguments if functionType.ArgumentExpressionsCheck != nil && argumentCount > 0 { @@ -472,15 +486,10 @@ func (checker *Checker) checkInvocation( argumentExpressions[i] = argument.Expression } - invocationRange := ast.NewRangeFromPositioned( - checker.memoryGauge, - invocationExpression, - ) - functionType.ArgumentExpressionsCheck( checker, argumentExpressions, - invocationRange, + getInvocationRange(), ) } @@ -498,6 +507,18 @@ func (checker *Checker) checkInvocation( invocationExpression, ) + // The invokable type might have special checks for the type parameters. + + if functionType.TypeArgumentsCheck != nil { + functionType.TypeArgumentsCheck( + checker.memoryGauge, + typeArguments, + invocationExpression.TypeArguments, + getInvocationRange(), + checker.report, + ) + } + // Save types in the elaboration checker.Elaboration.SetInvocationExpressionTypes( @@ -557,21 +578,46 @@ func (checker *Checker) checkInvocationRequiredArgument( var argumentType Type - if len(functionType.TypeParameters) == 0 { - // If the function doesn't use generic types, then the - // param types can be used to infer the types for arguments. + typeParameterCount := len(functionType.TypeParameters) + + // If all type parameters have been bound to a type, + // then resolve the parameter type with the type arguments, + // and propose the parameter type as the expected type for the argument. + if typeParameters.Len() == typeParameterCount { + + // Optimization: only resolve if there are type parameters. + // This avoids unnecessary work for non-generic functions. + if typeParameterCount > 0 { + parameterType = parameterType.Resolve(typeParameters) + // If the type parameter could not be resolved, use the invalid type. + if parameterType == nil { + parameterType = InvalidType + } + } + argumentType = checker.VisitExpression(argument.Expression, parameterType) + } else { - // TODO: pass the expected type to support for parameters + // If there are still type parameters that have not been bound to a type, + // then check the argument without an expected type. + // + // We will then have to manually check that the argument type is compatible + // with the parameter type (see below). + argumentType = checker.VisitExpression(argument.Expression, nil) // Try to unify the parameter type with the argument type. // If unification fails, fall back to the parameter type for now. - argumentRange := ast.NewRangeFromPositioned(checker.memoryGauge, argument.Expression) - - if parameterType.Unify(argumentType, typeParameters, checker.report, argumentRange) { + if parameterType.Unify( + argumentType, + typeParameters, + checker.report, + checker.memoryGauge, + argument.Expression, + ) { parameterType = parameterType.Resolve(typeParameters) + // If the type parameter could not be resolved, use the invalid type. if parameterType == nil { parameterType = InvalidType } @@ -579,7 +625,6 @@ func (checker *Checker) checkInvocationRequiredArgument( // Check that the type of the argument matches the type of the parameter. - // TODO: remove this once type inferring support for parameters is added checker.checkInvocationArgumentParameterTypeCompatibility( argument.Expression, argumentType, @@ -674,7 +719,7 @@ func (checker *Checker) checkAndBindGenericTypeParameterTypeArguments( // If the type parameter corresponding to the type argument has a type bound, // then check that the argument is a subtype of the type bound. - err := typeParameter.checkTypeBound(ty, ast.NewRangeFromPositioned(checker.memoryGauge, rawTypeArgument)) + err := typeParameter.checkTypeBound(ty, checker.memoryGauge, rawTypeArgument) checker.report(err) // Bind the type argument to the type parameter diff --git a/runtime/sema/checker.go b/runtime/sema/checker.go index 4dcce1b959..c1331d1044 100644 --- a/runtime/sema/checker.go +++ b/runtime/sema/checker.go @@ -2350,6 +2350,7 @@ func (checker *Checker) checkTypeAnnotation(typeAnnotation TypeAnnotation, pos a } checker.checkInvalidInterfaceAsType(typeAnnotation.Type, pos) + typeAnnotation.Type.CheckInstantiated(pos, checker.memoryGauge, checker.report) } func (checker *Checker) checkInvalidInterfaceAsType(ty Type, pos ast.HasPosition) { @@ -2474,7 +2475,8 @@ func (checker *Checker) convertInstantiationType(t *ast.InstantiationType) Type err := typeParameter.checkTypeBound( typeArgument, - ast.NewRangeFromPositioned(checker.memoryGauge, rawTypeArgument), + checker.memoryGauge, + rawTypeArgument, ) checker.report(err) } @@ -2502,7 +2504,12 @@ func (checker *Checker) convertInstantiationType(t *ast.InstantiationType) Type return ty } - return parameterizedType.Instantiate(typeArguments, checker.report) + return parameterizedType.Instantiate( + checker.memoryGauge, + typeArguments, + t.TypeArguments, + checker.report, + ) } func (checker *Checker) VisitExpression(expr ast.Expression, expectedType Type) Type { diff --git a/runtime/sema/errors.go b/runtime/sema/errors.go index b0f8bc3123..46d28926d1 100644 --- a/runtime/sema/errors.go +++ b/runtime/sema/errors.go @@ -3797,6 +3797,47 @@ 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) +} + +// InvalidTypeArgumentError + +type InvalidTypeArgumentError struct { + TypeArgumentName string + Details string + ast.Range +} + +var _ SemanticError = &InvalidTypeArgumentError{} +var _ errors.UserError = &InvalidTypeArgumentError{} + +func (*InvalidTypeArgumentError) isSemanticError() {} + +func (*InvalidTypeArgumentError) IsUserError() {} + +func (e *InvalidTypeArgumentError) Error() string { + return fmt.Sprintf("type argument %s invalid", e.TypeArgumentName) +} + +func (e *InvalidTypeArgumentError) SecondaryError() string { + return e.Details +} + // TypeParameterTypeInferenceError type TypeParameterTypeInferenceError struct { diff --git a/runtime/sema/runtime_type_constructors.go b/runtime/sema/runtime_type_constructors.go index 86bec84979..7b46c6213c 100644 --- a/runtime/sema/runtime_type_constructors.go +++ b/runtime/sema/runtime_type_constructors.go @@ -197,6 +197,21 @@ var CapabilityTypeFunctionType = NewSimpleFunctionType( OptionalMetaTypeAnnotation, ) +var InclusiveRangeTypeFunctionType = &FunctionType{ + Parameters: []Parameter{ + { + Label: ArgumentLabelNotRequired, + Identifier: "type", + TypeAnnotation: NewTypeAnnotation(MetaType), + }, + }, + ReturnTypeAnnotation: NewTypeAnnotation( + &OptionalType{ + Type: MetaType, + }, + ), +} + var runtimeTypeConstructors = []*RuntimeTypeConstructor{ { Name: OptionalTypeFunctionName, @@ -268,4 +283,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/simple_type.go b/runtime/sema/simple_type.go index 40c0b531ec..9f79f305aa 100644 --- a/runtime/sema/simple_type.go +++ b/runtime/sema/simple_type.go @@ -123,7 +123,13 @@ func (t *SimpleType) RewriteWithIntersectionTypes() (Type, bool) { return t, false } -func (*SimpleType) Unify(_ Type, _ *TypeParameterTypeOrderedMap, _ func(err error), _ ast.Range) bool { +func (*SimpleType) Unify( + _ Type, + _ *TypeParameterTypeOrderedMap, + _ func(err error), + _ common.MemoryGauge, + _ ast.HasPosition, +) bool { return false } @@ -181,3 +187,7 @@ func (t *SimpleType) CompositeKind() common.CompositeKind { return common.CompositeKindStructure } } + +func (t *SimpleType) CheckInstantiated(_ ast.HasPosition, _ common.MemoryGauge, _ func(err error)) { + // NO-OP +} diff --git a/runtime/sema/type.go b/runtime/sema/type.go index 0d0030c989..9419b7f759 100644 --- a/runtime/sema/type.go +++ b/runtime/sema/type.go @@ -191,7 +191,8 @@ type Type interface { other Type, typeParameters *TypeParameterTypeOrderedMap, report func(err error), - outerRange ast.Range, + memoryGauge common.MemoryGauge, + outerRange ast.HasPosition, ) bool // Resolve returns a type that is free of generic types (see `GenericType`), @@ -208,6 +209,8 @@ type Type interface { // i.e. `[T]` would map to `f([f(T)])`, but the internals of composite types are not // inspected, as they appear simply as nominal types in annotations Map(memoryGauge common.MemoryGauge, typeParamMap map[*TypeParameter]*TypeParameter, f func(Type) Type) Type + + CheckInstantiated(pos ast.HasPosition, memoryGauge common.MemoryGauge, report func(err error)) } // ValueIndexableType is a type which can be indexed into using a value @@ -323,20 +326,50 @@ type LocatedType interface { type ParameterizedType interface { Type TypeParameters() []*TypeParameter - Instantiate(typeArguments []Type, report func(err error)) Type + Instantiate(memoryGauge common.MemoryGauge, typeArguments []Type, astTypeArguments []*ast.TypeAnnotation, report func(err error)) Type BaseType() Type TypeArguments() []Type } func MustInstantiate(t ParameterizedType, typeArguments ...Type) Type { return t.Instantiate( + nil, /* memoryGauge */ typeArguments, + nil, /* astTypeArguments */ func(err error) { panic(errors.NewUnexpectedErrorFromCause(err)) }, ) } +func CheckParameterizedTypeInstantiated( + t ParameterizedType, + pos ast.HasPosition, + memoryGauge common.MemoryGauge, + report func(err error), +) { + typeArgs := t.TypeArguments() + typeParameters := t.TypeParameters() + + // The check for the argument and parameter count already happens in the checker, so we skip that here. + + // Ensure that each non-optional typeparameter is non-nil. + for index, typeParam := range typeParameters { + if !typeParam.Optional && typeArgs[index] == nil { + report( + &MissingTypeArgumentError{ + TypeArgumentName: typeParam.Name, + Range: ast.NewRange( + memoryGauge, + pos.StartPosition(), + pos.EndPosition(memoryGauge), + ), + }, + ) + } + } +} + // TypeAnnotation type TypeAnnotation struct { @@ -726,7 +759,8 @@ func (t *OptionalType) Unify( other Type, typeParameters *TypeParameterTypeOrderedMap, report func(err error), - outerRange ast.Range, + memoryGauge common.MemoryGauge, + outerRange ast.HasPosition, ) bool { otherOptional, ok := other.(*OptionalType) @@ -734,7 +768,13 @@ func (t *OptionalType) Unify( return false } - return t.Type.Unify(otherOptional.Type, typeParameters, report, outerRange) + return t.Type.Unify( + otherOptional.Type, + typeParameters, + report, + memoryGauge, + outerRange, + ) } func (t *OptionalType) Resolve(typeArguments *TypeParameterTypeOrderedMap) Type { @@ -756,6 +796,10 @@ func (t *OptionalType) SupportedEntitlements() *EntitlementOrderedSet { return nil } +func (t *OptionalType) CheckInstantiated(pos ast.HasPosition, memoryGauge common.MemoryGauge, report func(err error)) { + t.Type.CheckInstantiated(pos, memoryGauge, report) +} + const optionalTypeMapFunctionDocString = ` Returns an optional of the result of calling the given function with the value of this optional when it is not nil. @@ -932,7 +976,8 @@ func (t *GenericType) Unify( other Type, typeParameters *TypeParameterTypeOrderedMap, report func(err error), - outerRange ast.Range, + memoryGauge common.MemoryGauge, + outerRange ast.HasPosition, ) bool { if unifiedType, ok := typeParameters.Get(t.TypeParameter); ok { @@ -947,7 +992,7 @@ func (t *GenericType) Unify( TypeParameter: t.TypeParameter, ExpectedType: unifiedType, ActualType: other, - Range: outerRange, + Range: ast.NewRangeFromPositioned(memoryGauge, outerRange), }, ) } @@ -960,7 +1005,7 @@ func (t *GenericType) Unify( // If the type parameter corresponding to the type argument has a type bound, // then check that the argument's type is a subtype of the type bound. - err := t.TypeParameter.checkTypeBound(other, outerRange) + err := t.TypeParameter.checkTypeBound(other, memoryGauge, outerRange) if err != nil { report(err) } @@ -990,6 +1035,12 @@ func (t *GenericType) GetMembers() map[string]MemberResolver { return withBuiltinMembers(t, nil) } +func (t *GenericType) CheckInstantiated(pos ast.HasPosition, memoryGauge common.MemoryGauge, report func(err error)) { + if t.TypeParameter.TypeBound != nil { + t.TypeParameter.TypeBound.CheckInstantiated(pos, memoryGauge, report) + } +} + // IntegerRangedType type IntegerRangedType interface { @@ -1240,7 +1291,13 @@ func (t *NumericType) MaxInt() *big.Int { return t.maxInt } -func (*NumericType) Unify(_ Type, _ *TypeParameterTypeOrderedMap, _ func(err error), _ ast.Range) bool { +func (*NumericType) Unify( + _ Type, + _ *TypeParameterTypeOrderedMap, + _ func(err error), + _ common.MemoryGauge, + _ ast.HasPosition, +) bool { return false } @@ -1276,6 +1333,10 @@ func (t *NumericType) IsSuperType() bool { return t.isSuperType } +func (*NumericType) CheckInstantiated(_ ast.HasPosition, _ common.MemoryGauge, _ func(err error)) { + // NO-OP +} + // FixedPointNumericType represents all the types in the fixed-point range. type FixedPointNumericType struct { maxFractional *big.Int @@ -1445,7 +1506,13 @@ func (t *FixedPointNumericType) Scale() uint { return t.scale } -func (*FixedPointNumericType) Unify(_ Type, _ *TypeParameterTypeOrderedMap, _ func(err error), _ ast.Range) bool { +func (*FixedPointNumericType) Unify( + _ Type, + _ *TypeParameterTypeOrderedMap, + _ func(err error), + _ common.MemoryGauge, + _ ast.HasPosition, +) bool { return false } @@ -1481,6 +1548,10 @@ func (t *FixedPointNumericType) IsSuperType() bool { return t.isSuperType } +func (*FixedPointNumericType) CheckInstantiated(_ ast.HasPosition, _ common.MemoryGauge, _ func(err error)) { + // NO-OP +} + // Numeric types var ( @@ -2756,7 +2827,8 @@ func (t *VariableSizedType) Unify( other Type, typeParameters *TypeParameterTypeOrderedMap, report func(err error), - outerRange ast.Range, + memoryGauge common.MemoryGauge, + outerRange ast.HasPosition, ) bool { otherArray, ok := other.(*VariableSizedType) @@ -2764,7 +2836,13 @@ func (t *VariableSizedType) Unify( return false } - return t.Type.Unify(otherArray.Type, typeParameters, report, outerRange) + return t.Type.Unify( + otherArray.Type, + typeParameters, + report, + memoryGauge, + outerRange, + ) } func (t *VariableSizedType) Resolve(typeArguments *TypeParameterTypeOrderedMap) Type { @@ -2790,6 +2868,10 @@ var arrayDictionaryEntitlements = func() *EntitlementOrderedSet { return set }() +func (t *VariableSizedType) CheckInstantiated(pos ast.HasPosition, memoryGauge common.MemoryGauge, report func(err error)) { + t.ElementType(false).CheckInstantiated(pos, memoryGauge, report) +} + // ConstantSizedType is a constant sized array type type ConstantSizedType struct { Type Type @@ -2932,7 +3014,8 @@ func (t *ConstantSizedType) Unify( other Type, typeParameters *TypeParameterTypeOrderedMap, report func(err error), - outerRange ast.Range, + memoryGauge common.MemoryGauge, + outerRange ast.HasPosition, ) bool { otherArray, ok := other.(*ConstantSizedType) @@ -2944,7 +3027,13 @@ func (t *ConstantSizedType) Unify( return false } - return t.Type.Unify(otherArray.Type, typeParameters, report, outerRange) + return t.Type.Unify( + otherArray.Type, + typeParameters, + report, + memoryGauge, + outerRange, + ) } func (t *ConstantSizedType) Resolve(typeArguments *TypeParameterTypeOrderedMap) Type { @@ -2963,6 +3052,10 @@ func (t *ConstantSizedType) SupportedEntitlements() *EntitlementOrderedSet { return arrayDictionaryEntitlements } +func (t *ConstantSizedType) CheckInstantiated(pos ast.HasPosition, memoryGauge common.MemoryGauge, report func(err error)) { + t.ElementType(false).CheckInstantiated(pos, memoryGauge, report) +} + // Parameter func formatParameter(spaces bool, label, identifier, typeAnnotation string) string { @@ -3074,7 +3167,7 @@ func (p TypeParameter) Equal(other *TypeParameter) bool { return p.Optional == other.Optional } -func (p TypeParameter) checkTypeBound(ty Type, typeRange ast.Range) error { +func (p TypeParameter) checkTypeBound(ty Type, memoryGauge common.MemoryGauge, typeRange ast.HasPosition) error { if p.TypeBound == nil || p.TypeBound.IsInvalidType() || ty.IsInvalidType() { @@ -3086,7 +3179,7 @@ func (p TypeParameter) checkTypeBound(ty Type, typeRange ast.Range) error { return &TypeMismatchError{ ExpectedType: p.TypeBound, ActualType: ty, - Range: typeRange, + Range: ast.NewRangeFromPositioned(memoryGauge, typeRange), } } @@ -3192,6 +3285,7 @@ type FunctionType struct { ReturnTypeAnnotation TypeAnnotation Arity *Arity ArgumentExpressionsCheck ArgumentExpressionsCheck + TypeArgumentsCheck TypeArgumentsCheck Members *StringMemberOrderedMap TypeParameters []*TypeParameter Parameters []Parameter @@ -3595,7 +3689,8 @@ func (t *FunctionType) Unify( other Type, typeParameters *TypeParameterTypeOrderedMap, report func(err error), - outerRange ast.Range, + memoryGauge common.MemoryGauge, + outerRange ast.HasPosition, ) ( result bool, ) { @@ -3625,6 +3720,7 @@ func (t *FunctionType) Unify( otherParameter.TypeAnnotation.Type, typeParameters, report, + memoryGauge, outerRange, ) result = result || parameterUnified @@ -3636,6 +3732,7 @@ func (t *FunctionType) Unify( otherFunction.ReturnTypeAnnotation.Type, typeParameters, report, + memoryGauge, outerRange, ) @@ -3756,12 +3853,32 @@ func (t *FunctionType) initializeMemberResolvers() { }) } +func (t *FunctionType) CheckInstantiated(pos ast.HasPosition, memoryGauge common.MemoryGauge, report func(err error)) { + for _, tyParam := range t.TypeParameters { + tyParam.TypeBound.CheckInstantiated(pos, memoryGauge, report) + } + + for _, param := range t.Parameters { + param.TypeAnnotation.Type.CheckInstantiated(pos, memoryGauge, report) + } + + t.ReturnTypeAnnotation.Type.CheckInstantiated(pos, memoryGauge, report) +} + type ArgumentExpressionsCheck func( checker *Checker, argumentExpressions []ast.Expression, invocationRange ast.Range, ) +type TypeArgumentsCheck func( + memoryGauge common.MemoryGauge, + typeArguments *TypeParameterTypeOrderedMap, + astTypeArguments []*ast.TypeAnnotation, + astInvocationRange ast.Range, + report func(err error), +) + // BaseTypeActivation is the base activation that contains // the types available in programs var BaseTypeActivation = NewVariableActivation(nil) @@ -3799,6 +3916,7 @@ func init() { AccountCapabilityControllerType, DeploymentResultType, HashableStructType, + &InclusiveRangeType{}, }, ) @@ -3909,14 +4027,16 @@ var AllUnsignedIntegerTypes = common.Concat( }, ) +var AllNonLeafIntegerTypes = []Type{ + IntegerType, + SignedIntegerType, + FixedSizeUnsignedIntegerType, +} + var AllIntegerTypes = common.Concat( AllUnsignedIntegerTypes, AllSignedIntegerTypes, - []Type{ - IntegerType, - SignedIntegerType, - FixedSizeUnsignedIntegerType, - }, + AllNonLeafIntegerTypes, ) var AllNumberTypes = common.Concat( @@ -4716,7 +4836,13 @@ func (t *CompositeType) RewriteWithIntersectionTypes() (result Type, rewritten b return t, false } -func (*CompositeType) Unify(_ Type, _ *TypeParameterTypeOrderedMap, _ func(err error), _ ast.Range) bool { +func (*CompositeType) Unify( + _ Type, + _ *TypeParameterTypeOrderedMap, + _ func(err error), + _ common.MemoryGauge, + _ ast.HasPosition, +) bool { // TODO: return false } @@ -4915,6 +5041,20 @@ func (t *CompositeType) InitializerEffectiveArgumentLabels() []string { return argumentLabels } +func (t *CompositeType) CheckInstantiated(pos ast.HasPosition, memoryGauge common.MemoryGauge, report func(err error)) { + if t.EnumRawType != nil { + t.EnumRawType.CheckInstantiated(pos, memoryGauge, report) + } + + if t.baseType != nil { + t.baseType.CheckInstantiated(pos, memoryGauge, report) + } + + for _, typ := range t.ExplicitInterfaceConformances { + typ.CheckInstantiated(pos, memoryGauge, report) + } +} + // Member type Member struct { @@ -5482,7 +5622,13 @@ func (t *InterfaceType) RewriteWithIntersectionTypes() (Type, bool) { } -func (*InterfaceType) Unify(_ Type, _ *TypeParameterTypeOrderedMap, _ func(err error), _ ast.Range) bool { +func (*InterfaceType) Unify( + _ Type, + _ *TypeParameterTypeOrderedMap, + _ func(err error), + _ common.MemoryGauge, + _ ast.HasPosition, +) bool { // TODO: return false } @@ -5579,6 +5725,12 @@ func distinctConformances( return collectedConformances } +func (t *InterfaceType) CheckInstantiated(pos ast.HasPosition, memoryGauge common.MemoryGauge, report func(err error)) { + for _, param := range t.InitializerParameters { + param.TypeAnnotation.Type.CheckInstantiated(pos, memoryGauge, report) + } +} + // DictionaryType consists of the key and value type // for all key-value pairs in the dictionary: // All keys have to be a subtype of the key type, @@ -5720,6 +5872,11 @@ func (t *DictionaryType) RewriteWithIntersectionTypes() (Type, bool) { } } +func (t *DictionaryType) CheckInstantiated(pos ast.HasPosition, memoryGauge common.MemoryGauge, report func(err error)) { + t.KeyType.CheckInstantiated(pos, memoryGauge, report) + t.ValueType.CheckInstantiated(pos, memoryGauge, report) +} + const dictionaryTypeContainsKeyFunctionDocString = ` Returns true if the given key is in the dictionary ` @@ -6005,7 +6162,8 @@ func (t *DictionaryType) Unify( other Type, typeParameters *TypeParameterTypeOrderedMap, report func(err error), - outerRange ast.Range, + memoryGauge common.MemoryGauge, + outerRange ast.HasPosition, ) bool { otherDictionary, ok := other.(*DictionaryType) @@ -6013,8 +6171,22 @@ func (t *DictionaryType) Unify( return false } - keyUnified := t.KeyType.Unify(otherDictionary.KeyType, typeParameters, report, outerRange) - valueUnified := t.ValueType.Unify(otherDictionary.ValueType, typeParameters, report, outerRange) + keyUnified := t.KeyType.Unify( + otherDictionary.KeyType, + typeParameters, + report, + memoryGauge, + outerRange, + ) + + valueUnified := t.ValueType.Unify( + otherDictionary.ValueType, + typeParameters, + report, + memoryGauge, + outerRange, + ) + return keyUnified || valueUnified } @@ -6039,6 +6211,345 @@ func (t *DictionaryType) SupportedEntitlements() *EntitlementOrderedSet { return arrayDictionaryEntitlements } +// 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) RewriteWithIntersectionTypes() (Type, bool) { + if t.MemberType == nil { + return t, false + } + rewrittenMemberType, rewritten := t.MemberType.RewriteWithIntersectionTypes() + 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( + memoryGauge common.MemoryGauge, + typeArguments []Type, + astTypeArguments []*ast.TypeAnnotation, + report func(err error), +) Type { + memberType := typeArguments[0] + + if astTypeArguments == nil || astTypeArguments[0] == nil { + panic(errors.NewUnreachableError()) + } + paramAstRange := ast.NewRangeFromPositioned(memoryGauge, astTypeArguments[0]) + + // memberType must only be a leaf integer type. + for _, ty := range AllNonLeafIntegerTypes { + if memberType == ty { + report(&InvalidTypeArgumentError{ + TypeArgumentName: inclusiveRangeTypeParameter.Name, + Range: paramAstRange, + Details: fmt.Sprintf("Creation of InclusiveRange<%s> is disallowed", memberType), + }) + } + } + + return &InclusiveRangeType{ + MemberType: memberType, + } +} + +func (t *InclusiveRangeType) TypeArguments() []Type { + memberType := t.MemberType + return []Type{ + memberType, + } +} + +func (t *InclusiveRangeType) CheckInstantiated(pos ast.HasPosition, memoryGauge common.MemoryGauge, report func(err error)) { + CheckParameterizedTypeInstantiated(t, pos, memoryGauge, report) +} + +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), + memoryGauge common.MemoryGauge, + outerRange ast.HasPosition, +) bool { + otherRange, ok := other.(*InclusiveRangeType) + if !ok { + return false + } + + return t.MemberType.Unify( + otherRange.MemberType, + typeParameters, + report, + memoryGauge, + outerRange, + ) +} + +func (t *InclusiveRangeType) Resolve(typeArguments *TypeParameterTypeOrderedMap) Type { + memberType := t.MemberType.Resolve(typeArguments) + if memberType == nil { + return nil + } + + return &InclusiveRangeType{ + MemberType: memberType, + } +} + +func (t *InclusiveRangeType) IsPrimitiveType() bool { + return false +} + +func (t *InclusiveRangeType) ContainFieldsOrElements() bool { + return false +} + +func (t *InclusiveRangeType) Map( + gauge common.MemoryGauge, + typeParamMap map[*TypeParameter]*TypeParameter, + f func(Type) Type, +) Type { + mappedMemberType := t.MemberType.Map(gauge, typeParamMap, f) + return f(NewInclusiveRangeType(gauge, mappedMemberType)) +} + // ReferenceType represents the reference to a value type ReferenceType struct { Type Type @@ -6287,14 +6798,21 @@ func (t *ReferenceType) Unify( other Type, typeParameters *TypeParameterTypeOrderedMap, report func(err error), - outerRange ast.Range, + memoryGauge common.MemoryGauge, + outerRange ast.HasPosition, ) bool { otherReference, ok := other.(*ReferenceType) if !ok { return false } - return t.Type.Unify(otherReference.Type, typeParameters, report, outerRange) + return t.Type.Unify( + otherReference.Type, + typeParameters, + report, + memoryGauge, + outerRange, + ) } func (t *ReferenceType) Resolve(typeArguments *TypeParameterTypeOrderedMap) Type { @@ -6309,6 +6827,10 @@ func (t *ReferenceType) Resolve(typeArguments *TypeParameterTypeOrderedMap) Type } } +func (t *ReferenceType) CheckInstantiated(pos ast.HasPosition, memoryGauge common.MemoryGauge, report func(err error)) { + t.Type.CheckInstantiated(pos, memoryGauge, report) +} + const AddressTypeName = "Address" // AddressType represents the address type @@ -6405,7 +6927,13 @@ func (*AddressType) IsSuperType() bool { return false } -func (*AddressType) Unify(_ Type, _ *TypeParameterTypeOrderedMap, _ func(err error), _ ast.Range) bool { +func (*AddressType) Unify( + _ Type, + _ *TypeParameterTypeOrderedMap, + _ func(err error), + _ common.MemoryGauge, + _ ast.HasPosition, +) bool { return false } @@ -6413,6 +6941,10 @@ func (t *AddressType) Resolve(_ *TypeParameterTypeOrderedMap) Type { return t } +func (*AddressType) CheckInstantiated(_ ast.HasPosition, _ common.MemoryGauge, _ func(err error)) { + // NO-OP +} + const AddressTypeToBytesFunctionName = `toBytes` var AddressTypeToBytesFunctionType = NewSimpleFunctionType( @@ -7049,7 +7581,13 @@ func (t *TransactionType) initializeMemberResolvers() { }) } -func (*TransactionType) Unify(_ Type, _ *TypeParameterTypeOrderedMap, _ func(err error), _ ast.Range) bool { +func (*TransactionType) Unify( + _ Type, + _ *TypeParameterTypeOrderedMap, + _ func(err error), + _ common.MemoryGauge, + _ ast.HasPosition, +) bool { return false } @@ -7057,6 +7595,16 @@ func (t *TransactionType) Resolve(_ *TypeParameterTypeOrderedMap) Type { return t } +func (t *TransactionType) CheckInstantiated(pos ast.HasPosition, memoryGauge common.MemoryGauge, report func(err error)) { + for _, param := range t.PrepareParameters { + param.TypeAnnotation.Type.CheckInstantiated(pos, memoryGauge, report) + } + + for _, param := range t.Parameters { + param.TypeAnnotation.Type.CheckInstantiated(pos, memoryGauge, report) + } +} + // IntersectionType type IntersectionType struct { @@ -7321,7 +7869,13 @@ func (t *IntersectionType) SupportedEntitlements() (set *EntitlementOrderedSet) return set } -func (*IntersectionType) Unify(_ Type, _ *TypeParameterTypeOrderedMap, _ func(err error), _ ast.Range) bool { +func (*IntersectionType) Unify( + _ Type, + _ *TypeParameterTypeOrderedMap, + _ func(err error), + _ common.MemoryGauge, + _ ast.HasPosition, +) bool { // TODO: how do we unify the intersection sets? return false } @@ -7366,6 +7920,10 @@ func (t *IntersectionType) IsValidIndexingType(ty Type) bool { attachmentType.IsResourceType() == t.IsResourceType() } +func (t *IntersectionType) CheckInstantiated(_ ast.HasPosition, _ common.MemoryGauge, _ func(err error)) { + // No-OP +} + // CapabilityType type CapabilityType struct { @@ -7508,7 +8066,8 @@ func (t *CapabilityType) Unify( other Type, typeParameters *TypeParameterTypeOrderedMap, report func(err error), - outerRange ast.Range, + memoryGauge common.MemoryGauge, + outerRange ast.HasPosition, ) bool { otherCap, ok := other.(*CapabilityType) if !ok { @@ -7519,7 +8078,13 @@ func (t *CapabilityType) Unify( return false } - return t.BorrowType.Unify(otherCap.BorrowType, typeParameters, report, outerRange) + return t.BorrowType.Unify( + otherCap.BorrowType, + typeParameters, + report, + memoryGauge, + outerRange, + ) } func (t *CapabilityType) Resolve(typeArguments *TypeParameterTypeOrderedMap) Type { @@ -7547,7 +8112,12 @@ func (t *CapabilityType) TypeParameters() []*TypeParameter { } } -func (t *CapabilityType) Instantiate(typeArguments []Type, _ func(err error)) Type { +func (t *CapabilityType) Instantiate( + memoryGauge common.MemoryGauge, + typeArguments []Type, + _ []*ast.TypeAnnotation, + _ func(err error), +) Type { borrowType := typeArguments[0] return &CapabilityType{ BorrowType: borrowType, @@ -7574,6 +8144,10 @@ func (t *CapabilityType) TypeArguments() []Type { } } +func (t *CapabilityType) CheckInstantiated(pos ast.HasPosition, memoryGauge common.MemoryGauge, report func(err error)) { + CheckParameterizedTypeInstantiated(t, pos, memoryGauge, report) +} + func CapabilityTypeBorrowFunctionType(borrowType Type) *FunctionType { var typeParameters []*TypeParameter @@ -8059,7 +8633,13 @@ func (t *EntitlementType) RewriteWithIntersectionTypes() (Type, bool) { return t, false } -func (*EntitlementType) Unify(_ Type, _ *TypeParameterTypeOrderedMap, _ func(err error), _ ast.Range) bool { +func (*EntitlementType) Unify( + _ Type, + _ *TypeParameterTypeOrderedMap, + _ func(err error), + _ common.MemoryGauge, + _ ast.HasPosition, +) bool { return false } @@ -8067,6 +8647,10 @@ func (t *EntitlementType) Resolve(_ *TypeParameterTypeOrderedMap) Type { return t } +func (t *EntitlementType) CheckInstantiated(_ ast.HasPosition, _ common.MemoryGauge, _ func(err error)) { + // No-OP +} + // EntitlementMapType type EntitlementRelation struct { @@ -8206,7 +8790,13 @@ func (t *EntitlementMapType) RewriteWithIntersectionTypes() (Type, bool) { return t, false } -func (*EntitlementMapType) Unify(_ Type, _ *TypeParameterTypeOrderedMap, _ func(err error), _ ast.Range) bool { +func (*EntitlementMapType) Unify( + _ Type, + _ *TypeParameterTypeOrderedMap, + _ func(err error), + _ common.MemoryGauge, + _ ast.HasPosition, +) bool { return false } @@ -8283,6 +8873,10 @@ func (t *EntitlementMapType) resolveEntitlementMappingInclusions( }) } +func (t *EntitlementMapType) CheckInstantiated(_ ast.HasPosition, _ common.MemoryGauge, _ func(err error)) { + // NO-OP +} + var NativeCompositeTypes = map[string]*CompositeType{} func init() { diff --git a/runtime/sema/type_tags.go b/runtime/sema/type_tags.go index 88e8deca96..596d301047 100644 --- a/runtime/sema/type_tags.go +++ b/runtime/sema/type_tags.go @@ -225,8 +225,8 @@ const ( interfaceTypeMask functionTypeMask - hashableStructMask + inclusiveRangeTypeMask invalidTypeMask ) @@ -345,6 +345,7 @@ var ( IntersectionTypeTag = newTypeTagFromUpperMask(intersectionTypeMask) CapabilityTypeTag = newTypeTagFromUpperMask(capabilityTypeMask) + InclusiveRangeTypeTag = newTypeTagFromUpperMask(inclusiveRangeTypeMask) InvalidTypeTag = newTypeTagFromUpperMask(invalidTypeMask) TransactionTypeTag = newTypeTagFromUpperMask(transactionTypeMask) AnyResourceAttachmentTypeTag = newTypeTagFromUpperMask(anyResourceAttachmentMask) @@ -386,7 +387,8 @@ var ( Or(FunctionTypeTag). Or(StorageCapabilityControllerTypeTag). Or(AccountCapabilityControllerTypeTag). - Or(HashableStructTypeTag) + Or(HashableStructTypeTag). + Or(InclusiveRangeTypeTag) AnyResourceTypeTag = newTypeTagFromLowerMask(anyResourceTypeMask). Or(AnyResourceAttachmentTypeTag) @@ -690,7 +692,8 @@ func findSuperTypeFromUpperMask(joinedTypeTag TypeTag, types []Type) Type { intersectionTypeMask, transactionTypeMask, interfaceTypeMask, - functionTypeMask: + functionTypeMask, + inclusiveRangeTypeMask: return getSuperTypeOfDerivedTypes(types) case hashableStructMask: diff --git a/runtime/stdlib/builtin.go b/runtime/stdlib/builtin.go index 26ed8399e6..ea4adbb0a9 100644 --- a/runtime/stdlib/builtin.go +++ b/runtime/stdlib/builtin.go @@ -44,6 +44,7 @@ func DefaultStandardLibraryValues(handler StandardLibraryHandler) []StandardLibr PanicFunction, SignatureAlgorithmConstructor, RLPContract, + InclusiveRangeConstructorFunction, NewLogFunction(handler), NewRevertibleRandomFunction(handler), NewGetBlockFunction(handler), diff --git a/runtime/stdlib/range.go b/runtime/stdlib/range.go new file mode 100644 index 0000000000..9d493ccdaf --- /dev/null +++ b/runtime/stdlib/range.go @@ -0,0 +1,181 @@ +/* + * 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/ast" + "github.com/onflow/cadence/runtime/common" + "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}, + TypeArgumentsCheck: func( + memoryGauge common.MemoryGauge, + typeArguments *sema.TypeParameterTypeOrderedMap, + astTypeArguments []*ast.TypeAnnotation, + astInvocationRange ast.Range, + report func(error), + ) { + memberType, ok := typeArguments.Get(typeParameter) + if !ok || memberType == nil { + // checker should prevent this + panic(errors.NewUnreachableError()) + } + + paramAstRange := astInvocationRange + // If type argument was provided, use its range otherwise fallback to invocation range. + if len(astTypeArguments) > 0 { + paramAstRange = ast.NewRangeFromPositioned(memoryGauge, astTypeArguments[0]) + } + + // memberType must only be a leaf integer type. + for _, ty := range sema.AllNonLeafIntegerTypes { + if memberType == ty { + report(&sema.InvalidTypeArgumentError{ + TypeArgumentName: typeParameter.Name, + Range: paramAstRange, + Details: fmt.Sprintf("Creation of InclusiveRange<%s> is disallowed", memberType), + }) + } + } + }, + } +}() + +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/stdlib/test_test.go b/runtime/stdlib/test_test.go index c390d0616d..ae6a81585b 100644 --- a/runtime/stdlib/test_test.go +++ b/runtime/stdlib/test_test.go @@ -334,9 +334,9 @@ func TestTestNewMatcher(t *testing.T) { _, err := newTestContractInterpreter(t, script) - errs := checker.RequireCheckerErrors(t, err, 2) - assert.IsType(t, &sema.TypeParameterTypeMismatchError{}, errs[0]) - assert.IsType(t, &sema.TypeMismatchError{}, errs[1]) + errs := checker.RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.TypeMismatchError{}, errs[0]) }) t.Run("combined matcher mismatching types", func(t *testing.T) { @@ -499,9 +499,9 @@ func TestTestEqualMatcher(t *testing.T) { _, err := newTestContractInterpreter(t, script) - errs := checker.RequireCheckerErrors(t, err, 2) - assert.IsType(t, &sema.TypeParameterTypeMismatchError{}, errs[0]) - assert.IsType(t, &sema.TypeMismatchError{}, errs[1]) + errs := checker.RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.TypeMismatchError{}, errs[0]) }) t.Run("matcher or", func(t *testing.T) { @@ -1904,9 +1904,9 @@ func TestTestExpect(t *testing.T) { _, err := newTestContractInterpreter(t, script) - errs := checker.RequireCheckerErrors(t, err, 2) - assert.IsType(t, &sema.TypeParameterTypeMismatchError{}, errs[0]) - assert.IsType(t, &sema.TypeMismatchError{}, errs[1]) + errs := checker.RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.TypeMismatchError{}, errs[0]) }) t.Run("resource with resource matcher", func(t *testing.T) { diff --git a/runtime/tests/checker/account_test.go b/runtime/tests/checker/account_test.go index f0cf63e6bf..046bc5f6f8 100644 --- a/runtime/tests/checker/account_test.go +++ b/runtime/tests/checker/account_test.go @@ -219,16 +219,14 @@ func TestCheckAccountStorageSave(t *testing.T) { if domain == common.PathDomainStorage { - errs := RequireCheckerErrors(t, err, 2) + errs := RequireCheckerErrors(t, err, 1) - require.IsType(t, &sema.TypeParameterTypeMismatchError{}, errs[0]) - require.IsType(t, &sema.TypeMismatchError{}, errs[1]) + require.IsType(t, &sema.TypeMismatchError{}, errs[0]) } else { - errs := RequireCheckerErrors(t, err, 3) + errs := RequireCheckerErrors(t, err, 2) - require.IsType(t, &sema.TypeParameterTypeMismatchError{}, errs[0]) + require.IsType(t, &sema.TypeMismatchError{}, errs[0]) require.IsType(t, &sema.TypeMismatchError{}, errs[1]) - require.IsType(t, &sema.TypeMismatchError{}, errs[2]) } }) @@ -254,16 +252,14 @@ func TestCheckAccountStorageSave(t *testing.T) { if domain == common.PathDomainStorage { - errs := RequireCheckerErrors(t, err, 2) + errs := RequireCheckerErrors(t, err, 1) - require.IsType(t, &sema.TypeParameterTypeMismatchError{}, errs[0]) - require.IsType(t, &sema.TypeMismatchError{}, errs[1]) + require.IsType(t, &sema.TypeMismatchError{}, errs[0]) } else { - errs := RequireCheckerErrors(t, err, 3) + errs := RequireCheckerErrors(t, err, 2) - require.IsType(t, &sema.TypeParameterTypeMismatchError{}, errs[0]) + require.IsType(t, &sema.TypeMismatchError{}, errs[0]) require.IsType(t, &sema.TypeMismatchError{}, errs[1]) - require.IsType(t, &sema.TypeMismatchError{}, errs[2]) } }) } diff --git a/runtime/tests/checker/builtinfunctions_test.go b/runtime/tests/checker/builtinfunctions_test.go index 1bff1ba334..eede313982 100644 --- a/runtime/tests/checker/builtinfunctions_test.go +++ b/runtime/tests/checker/builtinfunctions_test.go @@ -292,7 +292,7 @@ func TestCheckRevertibleRandom(t *testing.T) { } } - runCase := func(t *testing.T, ty sema.Type, code string) { + runValidCase := func(t *testing.T, ty sema.Type, code string) { checker, err := ParseAndCheckWithOptions(t, code, newOptions()) @@ -307,7 +307,7 @@ func TestCheckRevertibleRandom(t *testing.T) { t.Parallel() code := fmt.Sprintf("let rand = revertibleRandom<%s>()", ty) - runCase(t, ty, code) + runValidCase(t, ty, code) }) } @@ -316,7 +316,7 @@ func TestCheckRevertibleRandom(t *testing.T) { t.Parallel() code := fmt.Sprintf("let rand = revertibleRandom<%[1]s>(modulo: %[1]s(1))", ty) - runCase(t, ty, code) + runValidCase(t, ty, code) }) } @@ -394,7 +394,6 @@ func TestCheckRevertibleRandom(t *testing.T) { "modulo type mismatch", "let rand = revertibleRandom(modulo: UInt128(1))", []error{ - &sema.TypeParameterTypeMismatchError{}, &sema.TypeMismatchError{}, }, ) @@ -404,18 +403,17 @@ func TestCheckRevertibleRandom(t *testing.T) { "string modulo", `let rand = revertibleRandom(modulo: "abcd")`, []error{ - &sema.TypeParameterTypeMismatchError{}, &sema.TypeMismatchError{}, }, ) - // This is an error since we do not support type inference of function arguments. - runInvalidCase( - t, - "missing type inference", - "let rand = revertibleRandom(modulo: 1)", - []error{ - &sema.TypeParameterTypeMismatchError{}, - }, - ) + t.Run("type parameter used for argument", func(t *testing.T) { + t.Parallel() + + runValidCase( + t, + sema.UInt256Type, + "let rand = revertibleRandom(modulo: 1)", + ) + }) } diff --git a/runtime/tests/checker/for_test.go b/runtime/tests/checker/for_test.go index edb5148290..495d30fe7b 100644 --- a/runtime/tests/checker/for_test.go +++ b/runtime/tests/checker/for_test.go @@ -19,12 +19,15 @@ package checker import ( + "fmt" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/sema" + "github.com/onflow/cadence/runtime/stdlib" ) func TestCheckForVariableSized(t *testing.T) { @@ -77,6 +80,49 @@ func TestCheckForString(t *testing.T) { assert.NoError(t, err) } +func TestCheckForInclusiveRange(t *testing.T) { + + t.Parallel() + + baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) + baseValueActivation.DeclareValue(stdlib.InclusiveRangeConstructorFunction) + + for _, typ := range sema.AllIntegerTypes { + // Only test leaf integer types + switch typ { + case sema.IntegerType, + sema.SignedIntegerType, + sema.FixedSizeUnsignedIntegerType: + continue + } + + code := fmt.Sprintf(` + fun test() { + let start : %[1]s = 1 + let end : %[1]s = 2 + let step : %[1]s = 1 + let range: InclusiveRange<%[1]s> = InclusiveRange(start, end, step: step) + + for value in range { + var typedValue: %[1]s = value + } + } + `, typ.String()) + + _, err := ParseAndCheckWithOptions(t, code, + ParseAndCheckOptions{ + Config: &sema.Config{ + BaseValueActivationHandler: func(common.Location) *sema.VariableActivation { + return baseValueActivation + }, + }, + }, + ) + + assert.NoError(t, err) + } +} + func TestCheckForEmpty(t *testing.T) { t.Parallel() diff --git a/runtime/tests/checker/genericfunction_test.go b/runtime/tests/checker/genericfunction_test.go index 00ccd2fc5a..22559c9acf 100644 --- a/runtime/tests/checker/genericfunction_test.go +++ b/runtime/tests/checker/genericfunction_test.go @@ -283,10 +283,9 @@ func TestCheckGenericFunctionInvocation(t *testing.T) { }, ) - errs := RequireCheckerErrors(t, err, 2) + errs := RequireCheckerErrors(t, err, 1) - assert.IsType(t, &sema.TypeParameterTypeMismatchError{}, errs[0]) - assert.IsType(t, &sema.TypeMismatchError{}, errs[1]) + assert.IsType(t, &sema.TypeMismatchError{}, errs[0]) }) t.Run("valid: one type parameter, one type argument, one parameter, one arguments", func(t *testing.T) { @@ -423,10 +422,9 @@ func TestCheckGenericFunctionInvocation(t *testing.T) { }, ) - errs := RequireCheckerErrors(t, err, 2) + errs := RequireCheckerErrors(t, err, 1) - assert.IsType(t, &sema.TypeParameterTypeMismatchError{}, errs[0]) - assert.IsType(t, &sema.TypeMismatchError{}, errs[1]) + assert.IsType(t, &sema.TypeMismatchError{}, errs[0]) }) t.Run("invalid: one type parameter, no type argument, no parameters, no arguments, return type", func(t *testing.T) { diff --git a/runtime/tests/checker/operations_test.go b/runtime/tests/checker/operations_test.go index 1716b73437..38c02bcc7f 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,13 @@ func TestCheckNonIntegerComparisonOperations(t *testing.T) { }`, test.ty, test.leftType, test.left, test.rightType, test.right, operation.Symbol(), ), + ParseAndCheckOptions{ + Config: &sema.Config{ + BaseValueActivationHandler: func(common.Location) *sema.VariableActivation { + return 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..4e64dd56fc --- /dev/null +++ b/runtime/tests/checker/range_value_test.go @@ -0,0 +1,479 @@ +/* + * 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/common" + "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{ + BaseValueActivationHandler: func(common.Location) *sema.VariableActivation { + return 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{ + BaseValueActivationHandler: func(common.Location) *sema.VariableActivation { + return 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, + sema.FixedSizeUnsignedIntegerType: + 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.TypeMismatchError{}, + }, + ) + runInvalidCase( + t, + typeString, + fmt.Sprintf("let r = InclusiveRange(%s(1), %s(10), step: %s(2))", typeString, typeString, differentTypeString), + []error{ + &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{}}, + ) + + runInvalidCase( + t, + "same_supertype_different_subtype_start_end", + ` + let a: Integer = UInt8(0) + let b: Integer = Int16(10) + let r = InclusiveRange(a, b) + `, + []error{&sema.InvalidTypeArgumentError{}}, + ) + 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) + `, + []error{&sema.InvalidTypeArgumentError{}}, + ) +} + +func TestInclusiveRangeNonLeafIntegerTypes(t *testing.T) { + + t.Parallel() + + newOptions := func() ParseAndCheckOptions { + baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) + baseValueActivation.DeclareValue(stdlib.InclusiveRangeConstructorFunction) + + return ParseAndCheckOptions{ + Config: &sema.Config{ + BaseValueActivationHandler: func(common.Location) *sema.VariableActivation { + return baseValueActivation + }, + }, + } + } + + test := func(t *testing.T, ty sema.Type) { + t.Run(fmt.Sprintf("InclusiveRange<%s> infer from args", ty), func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheckWithOptions(t, fmt.Sprintf(` + let a: %[1]s = 0 + let b: %[1]s = 10 + var range = InclusiveRange<%[1]s>(a, b) + `, ty), newOptions()) + + errs := RequireCheckerErrors(t, err, 1) + assert.IsType(t, &sema.InvalidTypeArgumentError{}, errs[0]) + }) + + t.Run(fmt.Sprintf("InclusiveRange<%s> infer from lhs", ty), func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheckWithOptions(t, fmt.Sprintf(` + let a: %[1]s = 0 + let b: %[1]s = 10 + var range: InclusiveRange<%[1]s> = InclusiveRange<%[1]s>(a, b) + `, ty), newOptions()) + + // One for the invocation and another for the type. + errs := RequireCheckerErrors(t, err, 2) + assert.IsType(t, &sema.InvalidTypeArgumentError{}, errs[0]) + assert.IsType(t, &sema.InvalidTypeArgumentError{}, errs[1]) + }) + + t.Run(fmt.Sprintf("InclusiveRange<%s> assignment", ty), func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheckWithOptions(t, fmt.Sprintf(` + let a: InclusiveRange = InclusiveRange(0, 10) + let b: InclusiveRange<%s> = a + `, ty), newOptions()) + + errs := RequireCheckerErrors(t, err, 1) + assert.IsType(t, &sema.InvalidTypeArgumentError{}, errs[0]) + }) + } + + for _, ty := range []sema.Type{ + sema.IntegerType, + sema.SignedIntegerType, + } { + test(t, ty) + } +} diff --git a/runtime/tests/checker/runtimetype_test.go b/runtime/tests/checker/runtimetype_test.go index 3942dd2ff7..796b771a0c 100644 --- a/runtime/tests/checker/runtimetype_test.go +++ b/runtime/tests/checker/runtimetype_test.go @@ -831,3 +831,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 0a330b4d69..58b0f5cfc1 100644 --- a/runtime/tests/checker/storable_test.go +++ b/runtime/tests/checker/storable_test.go @@ -136,6 +136,7 @@ func TestCheckStorable(t *testing.T) { sema.NeverType, sema.VoidType, sema.AccountType, + &sema.InclusiveRangeType{MemberType: sema.IntType}, } // Capabilities of non-storable types are storable diff --git a/runtime/tests/checker/type_inference_test.go b/runtime/tests/checker/type_inference_test.go index 6347f8083d..d8b2298cb7 100644 --- a/runtime/tests/checker/type_inference_test.go +++ b/runtime/tests/checker/type_inference_test.go @@ -335,7 +335,7 @@ func TestCheckFunctionArgumentTypeInference(t *testing.T) { require.NoError(t, err) }) - t.Run("with generics", func(t *testing.T) { + t.Run("with generics, void return type", func(t *testing.T) { t.Parallel() @@ -367,46 +367,146 @@ func TestCheckFunctionArgumentTypeInference(t *testing.T) { }, ) - errs := RequireCheckerErrors(t, err, 2) + require.NoError(t, err) + }) - require.IsType(t, &sema.TypeParameterTypeMismatchError{}, errs[0]) - typeParamMismatchErr := errs[0].(*sema.TypeParameterTypeMismatchError) - assert.Equal( - t, - &sema.VariableSizedType{ - Type: sema.Int8Type, - }, - typeParamMismatchErr.ExpectedType, - ) + t.Run("with generics, generic return type", func(t *testing.T) { - assert.Equal( - t, - &sema.VariableSizedType{ - Type: sema.IntType, + t.Parallel() + + typeParameter := &sema.TypeParameter{ + Name: "T", + TypeBound: nil, + } + + _, err := parseAndCheckWithTestValue(t, + ` + let res: [Int8] = test<[Int8]>([1, 2, 3]) + `, + &sema.FunctionType{ + TypeParameters: []*sema.TypeParameter{ + typeParameter, + }, + Parameters: []sema.Parameter{ + { + Label: sema.ArgumentLabelNotRequired, + Identifier: "value", + TypeAnnotation: sema.NewTypeAnnotation( + &sema.GenericType{ + TypeParameter: typeParameter, + }, + ), + }, + }, + ReturnTypeAnnotation: sema.NewTypeAnnotation( + &sema.GenericType{ + TypeParameter: typeParameter, + }, + ), }, - typeParamMismatchErr.ActualType, ) - require.IsType(t, &sema.TypeMismatchError{}, errs[1]) - typeMismatchErr := errs[1].(*sema.TypeMismatchError) + require.NoError(t, err) + }) + + t.Run("with generics, argument type propagation, simple", func(t *testing.T) { - assert.Equal( - t, - &sema.VariableSizedType{ - Type: sema.Int8Type, + t.Parallel() + + typeParameter := &sema.TypeParameter{ + Name: "T", + TypeBound: nil, + } + + _, err := parseAndCheckWithTestValue(t, + ` + let res: UInt8 = test(1 as UInt8, 2) + `, + &sema.FunctionType{ + TypeParameters: []*sema.TypeParameter{ + typeParameter, + }, + Parameters: []sema.Parameter{ + { + Label: sema.ArgumentLabelNotRequired, + Identifier: "a", + TypeAnnotation: sema.NewTypeAnnotation( + &sema.GenericType{ + TypeParameter: typeParameter, + }, + ), + }, + { + Label: sema.ArgumentLabelNotRequired, + Identifier: "b", + TypeAnnotation: sema.NewTypeAnnotation( + &sema.GenericType{ + TypeParameter: typeParameter, + }, + ), + }, + }, + ReturnTypeAnnotation: sema.NewTypeAnnotation( + &sema.GenericType{ + TypeParameter: typeParameter, + }, + ), }, - typeMismatchErr.ExpectedType, ) - assert.Equal( - t, - &sema.VariableSizedType{ - Type: sema.IntType, + require.NoError(t, err) + }) + + t.Run("with generics, argument type propagation, nested", func(t *testing.T) { + + t.Parallel() + + typeParameter := &sema.TypeParameter{ + Name: "T", + TypeBound: nil, + } + + _, err := parseAndCheckWithTestValue(t, + ` + let res: UInt8 = test(1 as UInt8, [2]) + `, + &sema.FunctionType{ + TypeParameters: []*sema.TypeParameter{ + typeParameter, + }, + Parameters: []sema.Parameter{ + { + Label: sema.ArgumentLabelNotRequired, + Identifier: "a", + TypeAnnotation: sema.NewTypeAnnotation( + &sema.GenericType{ + TypeParameter: typeParameter, + }, + ), + }, + { + Label: sema.ArgumentLabelNotRequired, + Identifier: "b", + TypeAnnotation: sema.NewTypeAnnotation( + &sema.VariableSizedType{ + Type: &sema.GenericType{ + TypeParameter: typeParameter, + }, + }, + ), + }, + }, + ReturnTypeAnnotation: sema.NewTypeAnnotation( + &sema.GenericType{ + TypeParameter: typeParameter, + }, + ), }, - typeMismatchErr.ActualType, ) + require.NoError(t, err) }) + } func TestCheckBinaryExpressionTypeInference(t *testing.T) { diff --git a/runtime/tests/checker/typeargument_test.go b/runtime/tests/checker/typeargument_test.go index c78ff6f160..060c983961 100644 --- a/runtime/tests/checker/typeargument_test.go +++ b/runtime/tests/checker/typeargument_test.go @@ -24,7 +24,9 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/sema" + "github.com/onflow/cadence/runtime/stdlib" ) func TestCheckTypeArguments(t *testing.T) { @@ -53,6 +55,32 @@ func TestCheckTypeArguments(t *testing.T) { require.Nil(t, capType.(*sema.CapabilityType).BorrowType) }) + 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{ + BaseValueActivationHandler: func(_ common.Location) *sema.VariableActivation { + return 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() @@ -132,6 +160,796 @@ func TestCheckTypeArguments(t *testing.T) { }) } +func TestCheckParameterizedTypeIsInstantiated(t *testing.T) { + + t.Parallel() + + test := func(t *testing.T, code string) error { + baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) + baseValueActivation.DeclareValue(stdlib.InclusiveRangeConstructorFunction) + options := ParseAndCheckOptions{ + Config: &sema.Config{ + BaseValueActivationHandler: func(_ common.Location) *sema.VariableActivation { + return baseValueActivation + }, + }, + } + + _, err := ParseAndCheckWithOptions(t, code, options) + return err + } + + t.Run("InclusiveRange", func(t *testing.T) { + + t.Parallel() + + err := test(t, + "let inclusiveRange: InclusiveRange = InclusiveRange(1, 10)", + ) + + require.NoError(t, err) + }) + + t.Run("InclusiveRange", func(t *testing.T) { + + t.Parallel() + + err := test(t, + "let inclusiveRange: InclusiveRange = InclusiveRange(1, 10)", + ) + + errs := RequireCheckerErrors(t, err, 2) + + assert.IsType(t, &sema.InvalidTypeArgumentCountError{}, errs[0]) + assert.IsType(t, &sema.MissingTypeArgumentError{}, errs[1]) + }) + + t.Run("InclusiveRange", func(t *testing.T) { + + t.Parallel() + + err := test(t, + "let inclusiveRange: InclusiveRange = InclusiveRange(1, 10)", + ) + + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.MissingTypeArgumentError{}, errs[0]) + }) + + t.Run("VariableSizedArray with InclusiveRange", func(t *testing.T) { + + t.Parallel() + + err := test(t, + "let r: [InclusiveRange] = []", + ) + + require.NoError(t, err) + }) + + t.Run("VariableSizedArray with InclusiveRange", func(t *testing.T) { + + t.Parallel() + + err := test(t, + "let r: [InclusiveRange] = []", + ) + + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.MissingTypeArgumentError{}, errs[0]) + }) + + t.Run("ConstantSizedType with InclusiveRange", func(t *testing.T) { + + t.Parallel() + + err := test(t, + "let r: [InclusiveRange; 2] = [InclusiveRange(1, 2), InclusiveRange(3, 4)]", + ) + + require.NoError(t, err) + }) + + t.Run("ConstantSizedType with InclusiveRange", func(t *testing.T) { + + t.Parallel() + + err := test(t, + "let r: [InclusiveRange; 2] = [InclusiveRange(1, 2), InclusiveRange(3, 4)]", + ) + + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.MissingTypeArgumentError{}, errs[0]) + }) + + t.Run("OptionalType with InclusiveRange", func(t *testing.T) { + + t.Parallel() + + err := test(t, + "let r: InclusiveRange? = nil", + ) + + require.NoError(t, err) + }) + + t.Run("OptionalType with InclusiveRange", func(t *testing.T) { + + t.Parallel() + + err := test(t, + "let r: InclusiveRange? = nil", + ) + + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.MissingTypeArgumentError{}, errs[0]) + }) + + t.Run("DictionaryType with InclusiveRange", func(t *testing.T) { + + t.Parallel() + + err := test(t, + "let r: {Int: InclusiveRange} = {}", + ) + + require.NoError(t, err) + }) + + t.Run("DictionaryType with InclusiveRange", func(t *testing.T) { + + t.Parallel() + + err := test(t, + "let r: {Int: InclusiveRange} = {}", + ) + + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.MissingTypeArgumentError{}, errs[0]) + }) + + t.Run("Struct with InclusiveRange", func(t *testing.T) { + + t.Parallel() + + err := test(t, + ` + struct Foo { + let a: InclusiveRange + + init() { + self.a = InclusiveRange(1, 10) + } + } + `, + ) + + require.NoError(t, err) + }) + + t.Run("Struct with InclusiveRange", func(t *testing.T) { + + t.Parallel() + + err := test(t, + ` + struct Foo { + let a: InclusiveRange + + init() { + self.a = InclusiveRange(1, 10) + } + } + `, + ) + + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.MissingTypeArgumentError{}, errs[0]) + }) + + t.Run("Nested Struct with InclusiveRange", func(t *testing.T) { + + t.Parallel() + + err := test(t, + ` + struct Bar { + let a: InclusiveRange + + init() { + self.a = InclusiveRange(1, 10) + } + } + + struct Foo { + let bar: Bar + + init(b : Bar) { + self.bar = b + } + } + `, + ) + + require.NoError(t, err) + }) + + t.Run("Nested Struct with InclusiveRange", func(t *testing.T) { + + t.Parallel() + + err := test(t, + ` + struct Bar { + let a: InclusiveRange + + init() { + self.a = InclusiveRange(1, 10) + } + } + + struct Foo { + let bar: Bar + + init(b : Bar) { + self.bar = b + } + } + `, + ) + + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.MissingTypeArgumentError{}, errs[0]) + }) + + t.Run("Contract with Struct with InclusiveRange", func(t *testing.T) { + + t.Parallel() + + err := test(t, + ` + contract C { + struct Foo { + let a: InclusiveRange + + init() { + self.a = InclusiveRange(1, 10) + } + } + } + `, + ) + + require.NoError(t, err) + }) + + t.Run("Contract with Struct with InclusiveRange", func(t *testing.T) { + + t.Parallel() + + err := test(t, + ` + contract C { + struct Foo { + let a: InclusiveRange + + init() { + self.a = InclusiveRange(1, 10) + } + } + } + `, + ) + + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.MissingTypeArgumentError{}, errs[0]) + }) + + t.Run("Struct with function returning InclusiveRange", func(t *testing.T) { + + t.Parallel() + + err := test(t, + ` + struct Bar { + let f: (fun(): InclusiveRange) + + init() { + self.f = fun(): InclusiveRange { + return InclusiveRange(1, 10) + } + } + } + `, + ) + + require.NoError(t, err) + }) + + t.Run("Struct with function returning InclusiveRange", func(t *testing.T) { + + t.Parallel() + + err := test(t, + ` + struct Bar { + let f: (fun(): InclusiveRange) + + init() { + self.f = fun(): InclusiveRange { + return InclusiveRange(1, 10) + } + } + } + `, + ) + + errs := RequireCheckerErrors(t, err, 2) + + // 2 errors for the two occurrences of InclusiveRange. + assert.IsType(t, &sema.MissingTypeArgumentError{}, errs[0]) + assert.IsType(t, &sema.MissingTypeArgumentError{}, errs[1]) + }) + + t.Run("StructInterface with function returning InclusiveRange", func(t *testing.T) { + + t.Parallel() + + err := test(t, + ` + struct interface Bar { + fun getRange(): InclusiveRange + } + `, + ) + + require.NoError(t, err) + }) + + t.Run("StructInterface with function returning InclusiveRange", func(t *testing.T) { + + t.Parallel() + + err := test(t, + ` + struct interface Bar { + fun getRange(): InclusiveRange + } + `, + ) + + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.MissingTypeArgumentError{}, errs[0]) + }) + + t.Run("Resource with InclusiveRange", func(t *testing.T) { + + t.Parallel() + + err := test(t, + ` + resource Foo { + let a: InclusiveRange + + init() { + self.a = InclusiveRange(1, 10) + } + } + `, + ) + + require.NoError(t, err) + }) + + t.Run("Contract with StructInterface with InclusiveRange", func(t *testing.T) { + + t.Parallel() + + err := test(t, + ` + contract C { + struct interface Foo { + fun getRange(): InclusiveRange + } + } + `, + ) + + require.NoError(t, err) + }) + + t.Run("Contract with StructInterface with InclusiveRange", func(t *testing.T) { + + t.Parallel() + + err := test(t, + ` + contract C { + struct interface Foo { + fun getRange(): InclusiveRange + } + } + `, + ) + + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.MissingTypeArgumentError{}, errs[0]) + }) + + t.Run("Resource with InclusiveRange", func(t *testing.T) { + + t.Parallel() + + err := test(t, + ` + resource Foo { + let a: InclusiveRange + + init() { + self.a = InclusiveRange(1, 10) + } + } + `, + ) + + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.MissingTypeArgumentError{}, errs[0]) + }) + + t.Run("Nested Resource with InclusiveRange", func(t *testing.T) { + + t.Parallel() + + err := test(t, + ` + resource Bar { + let a: InclusiveRange + + init() { + self.a = InclusiveRange(1, 10) + } + } + + resource Foo { + let bar: @Bar + + init(b : @Bar) { + self.bar <- b + } + } + `, + ) + + require.NoError(t, err) + }) + + t.Run("Nested Resource with InclusiveRange", func(t *testing.T) { + + t.Parallel() + + err := test(t, + ` + resource Bar { + let a: InclusiveRange + + init() { + self.a = InclusiveRange(1, 10) + } + } + + resource Foo { + let bar: @Bar + + init(b : @Bar) { + self.bar <- b + } + } + `, + ) + + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.MissingTypeArgumentError{}, errs[0]) + }) + + t.Run("ResourceInterface with function returning InclusiveRange", func(t *testing.T) { + + t.Parallel() + + err := test(t, + ` + resource interface Bar { + fun getRange(): InclusiveRange + } + `, + ) + + require.NoError(t, err) + }) + + t.Run("ResourceInterface with function returning InclusiveRange", func(t *testing.T) { + + t.Parallel() + + err := test(t, + ` + resource interface Bar { + fun getRange(): InclusiveRange + } + `, + ) + + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.MissingTypeArgumentError{}, errs[0]) + }) + + t.Run("Contract with ResourceInterface with InclusiveRange", func(t *testing.T) { + + t.Parallel() + + err := test(t, + ` + contract C { + resource interface Foo { + fun getRange(): InclusiveRange + } + } + `, + ) + + require.NoError(t, err) + }) + + t.Run("Contract with ResourceInterface with InclusiveRange", func(t *testing.T) { + + t.Parallel() + + err := test(t, + ` + contract C { + resource interface Foo { + fun getRange(): InclusiveRange + } + } + `, + ) + + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.MissingTypeArgumentError{}, errs[0]) + }) + + t.Run("Type with InclusiveRange", func(t *testing.T) { + + t.Parallel() + + err := test(t, + ` + access(all) fun main(): Type { + return Type>() + } + `, + ) + + require.NoError(t, err) + }) + + t.Run("Type with InclusiveRange", func(t *testing.T) { + + t.Parallel() + + err := test(t, + ` + access(all) fun main(): Type { + return Type() + } + `, + ) + + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.MissingTypeArgumentError{}, errs[0]) + }) + + t.Run("Event with InclusiveRange", func(t *testing.T) { + + t.Parallel() + + err := test(t, + "event Foo(bar: InclusiveRange)", + ) + + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.InvalidEventParameterTypeError{}, errs[0]) + }) + + t.Run("Event with InclusiveRange", func(t *testing.T) { + + t.Parallel() + + err := test(t, + "event Foo(bar: InclusiveRange)", + ) + + errs := RequireCheckerErrors(t, err, 2) + + assert.IsType(t, &sema.MissingTypeArgumentError{}, errs[0]) + assert.IsType(t, &sema.InvalidEventParameterTypeError{}, errs[1]) + }) + + t.Run("Enum Declaration", func(t *testing.T) { + + t.Parallel() + + err := test(t, + ` + access(all) fun main(): Direction { + return Direction.RIGHT + } + + access(all) enum Direction: Int { + access(all) case UP + access(all) case DOWN + access(all) case LEFT + access(all) case RIGHT + } + `, + ) + + require.NoError(t, err) + }) + + testFunctionTpe := func(t *testing.T, functionType *sema.FunctionType) error { + baseTypeActivation := sema.NewVariableActivation(sema.BaseTypeActivation) + baseTypeActivation.DeclareType(stdlib.StandardLibraryType{ + Name: "TestFunc", + Kind: common.DeclarationKindType, + Type: functionType, + }) + + options := ParseAndCheckOptions{ + Config: &sema.Config{ + BaseTypeActivationHandler: func(_ common.Location) *sema.VariableActivation { + return baseTypeActivation + }, + }, + } + + _, err := ParseAndCheckWithOptions(t, "fun test(testFunc: TestFunc) {}", options) + return err + } + + t.Run("Function type, return type not instantiated", func(t *testing.T) { + + t.Parallel() + err := testFunctionTpe(t, + &sema.FunctionType{ + ReturnTypeAnnotation: sema.NewTypeAnnotation( + &sema.InclusiveRangeType{}, + ), + }, + ) + + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.MissingTypeArgumentError{}, errs[0]) + }) + + t.Run("Function type, return type instantiated", func(t *testing.T) { + + t.Parallel() + err := testFunctionTpe(t, + &sema.FunctionType{ + ReturnTypeAnnotation: sema.NewTypeAnnotation( + &sema.InclusiveRangeType{ + MemberType: sema.IntType, + }, + ), + }, + ) + + require.NoError(t, err) + }) + + t.Run("Function type, parameter type not instantiated", func(t *testing.T) { + + t.Parallel() + err := testFunctionTpe(t, + &sema.FunctionType{ + Parameters: []sema.Parameter{ + { + Identifier: "a", + TypeAnnotation: sema.NewTypeAnnotation( + &sema.InclusiveRangeType{}, + ), + }, + }, + ReturnTypeAnnotation: sema.VoidTypeAnnotation, + }, + ) + + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.MissingTypeArgumentError{}, errs[0]) + }) + + t.Run("Function type, parameter type instantiated", func(t *testing.T) { + + t.Parallel() + err := testFunctionTpe(t, + &sema.FunctionType{ + Parameters: []sema.Parameter{ + { + Identifier: "a", + TypeAnnotation: sema.NewTypeAnnotation( + &sema.InclusiveRangeType{ + MemberType: sema.IntType, + }, + ), + }, + }, + ReturnTypeAnnotation: sema.VoidTypeAnnotation, + }, + ) + + require.NoError(t, err) + }) + + t.Run("Function type, type parameter type bound not instantiated", func(t *testing.T) { + + t.Parallel() + err := testFunctionTpe(t, + &sema.FunctionType{ + TypeParameters: []*sema.TypeParameter{ + { + Name: "T", + TypeBound: &sema.InclusiveRangeType{}, + }, + }, + ReturnTypeAnnotation: sema.VoidTypeAnnotation, + }, + ) + + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.MissingTypeArgumentError{}, errs[0]) + }) + + t.Run("Function type,type parameter type bound instantiated", func(t *testing.T) { + + t.Parallel() + err := testFunctionTpe(t, + &sema.FunctionType{ + TypeParameters: []*sema.TypeParameter{ + { + Name: "T", + TypeBound: &sema.InclusiveRangeType{ + MemberType: sema.IntType, + }, + }, + }, + ReturnTypeAnnotation: sema.VoidTypeAnnotation, + }, + ) + + require.NoError(t, err) + }) + +} + func TestCheckTypeArgumentSubtyping(t *testing.T) { t.Parallel() @@ -250,7 +1068,6 @@ func TestCheckTypeArgumentSubtyping(t *testing.T) { ) errs := RequireCheckerErrors(t, err, 1) - assert.IsType(t, &sema.TypeMismatchError{}, errs[0]) }) @@ -293,7 +1110,6 @@ func TestCheckTypeArgumentSubtyping(t *testing.T) { ) errs := RequireCheckerErrors(t, err, 1) - assert.IsType(t, &sema.TypeMismatchError{}, errs[0]) }) } diff --git a/runtime/tests/interpreter/account_test.go b/runtime/tests/interpreter/account_test.go index 1086b4ca6f..3a51b84a23 100644 --- a/runtime/tests/interpreter/account_test.go +++ b/runtime/tests/interpreter/account_test.go @@ -444,6 +444,8 @@ func testAccountWithErrorHandler( accountValueDeclaration.Name = "account" valueDeclarations = append(valueDeclarations, accountValueDeclaration) + valueDeclarations = append(valueDeclarations, stdlib.InclusiveRangeConstructorFunction) + baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) for _, valueDeclaration := range valueDeclarations { baseValueActivation.DeclareValue(valueDeclaration) diff --git a/runtime/tests/interpreter/dynamic_casting_test.go b/runtime/tests/interpreter/dynamic_casting_test.go index 36d0dd48cf..119b22458b 100644 --- a/runtime/tests/interpreter/dynamic_casting_test.go +++ b/runtime/tests/interpreter/dynamic_casting_test.go @@ -1467,6 +1467,68 @@ func TestInterpretDynamicCastingDictionary(t *testing.T) { } } +func TestInterpretDynamicCastingInclusiveRange(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) + + options := ParseCheckAndInterpretOptions{ + CheckerConfig: &sema.Config{ + BaseValueActivationHandler: func(common.Location) *sema.VariableActivation { + return baseValueActivation + }, + }, + Config: &interpreter.Config{ + BaseActivationHandler: func(common.Location) *interpreter.VariableActivation { + return baseActivation + }, + }, + } + + for operation, returnsOptional := range dynamicCastingOperations { + + t.Run(operation.Symbol(), func(t *testing.T) { + t.Run("invalid cast", 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/for_test.go b/runtime/tests/interpreter/for_test.go index ab14bd90d8..6763c8f89c 100644 --- a/runtime/tests/interpreter/for_test.go +++ b/runtime/tests/interpreter/for_test.go @@ -19,12 +19,16 @@ package interpreter_test import ( + "fmt" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/onflow/cadence/runtime/activations" "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/sema" + "github.com/onflow/cadence/runtime/stdlib" . "github.com/onflow/cadence/runtime/tests/utils" "github.com/onflow/cadence/runtime/interpreter" @@ -734,3 +738,142 @@ func TestInterpretStorageReferencesInForLoop(t *testing.T) { require.ErrorAs(t, err, &interpreter.DereferenceError{}) }) } + +type inclusiveRangeForInLoopTest struct { + start, end, step int8 + loopElements []int +} + +func TestInclusiveRangeForInLoop(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) + + unsignedTestCases := []inclusiveRangeForInLoopTest{ + { + start: 0, + end: 10, + step: 1, + loopElements: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, + }, + { + start: 0, + end: 10, + step: 2, + loopElements: []int{0, 2, 4, 6, 8, 10}, + }, + } + + signedTestCases := []inclusiveRangeForInLoopTest{ + { + start: 10, + end: -10, + step: -2, + loopElements: []int{10, 8, 6, 4, 2, 0, -2, -4, -6, -8, -10}, + }, + } + + runTestCase := func(t *testing.T, typ sema.Type, testCase inclusiveRangeForInLoopTest) { + t.Run(typ.String(), func(t *testing.T) { + t.Parallel() + + code := fmt.Sprintf( + ` + fun test(): [%[1]s] { + let start : %[1]s = %[2]d + let end : %[1]s = %[3]d + let step : %[1]s = %[4]d + let range: InclusiveRange<%[1]s> = InclusiveRange(start, end, step: step) + + var elements : [%[1]s] = [] + for element in range { + elements.append(element) + } + return elements + } + `, + typ.String(), + testCase.start, + testCase.end, + testCase.step, + ) + + inter, err := parseCheckAndInterpretWithOptions(t, code, + ParseCheckAndInterpretOptions{ + CheckerConfig: &sema.Config{ + BaseValueActivationHandler: func(common.Location) *sema.VariableActivation { + return baseValueActivation + }, + }, + Config: &interpreter.Config{ + BaseActivationHandler: func(common.Location) *interpreter.VariableActivation { + return baseActivation + }, + }, + }, + ) + + require.NoError(t, err) + loopElements, err := inter.Invoke("test") + require.NoError(t, err) + + integerStaticType := interpreter.ConvertSemaToStaticType( + nil, + typ, + ) + + count := 0 + iterator := (loopElements).(*interpreter.ArrayValue).Iterator(inter, interpreter.EmptyLocationRange) + for { + elem := iterator.Next(inter, interpreter.EmptyLocationRange) + if elem == nil { + break + } + + AssertValuesEqual( + t, + inter, + interpreter.GetSmallIntegerValue( + int8(testCase.loopElements[count]), + integerStaticType, + ), + elem, + ) + + count += 1 + } + + assert.Equal(t, len(testCase.loopElements), count) + }) + } + + for _, typ := range sema.AllIntegerTypes { + // Only test leaf types + switch typ { + case sema.IntegerType, + sema.SignedIntegerType, + sema.FixedSizeUnsignedIntegerType: + continue + } + + for _, testCase := range unsignedTestCases { + runTestCase(t, typ, testCase) + } + } + + for _, typ := range sema.AllSignedIntegerTypes { + // Only test leaf types + switch typ { + case sema.SignedIntegerType: + continue + } + + for _, testCase := range signedTestCases { + runTestCase(t, typ, testCase) + } + } +} diff --git a/runtime/tests/interpreter/interface_test.go b/runtime/tests/interpreter/interface_test.go index a66b429611..ebaf634612 100644 --- a/runtime/tests/interpreter/interface_test.go +++ b/runtime/tests/interpreter/interface_test.go @@ -653,7 +653,7 @@ func TestInterpretInterfaceFunctionConditionsInheritance(t *testing.T) { }, }, Config: &interpreter.Config{ - BaseActivationHandler: func(location common.Location) *interpreter.VariableActivation { + BaseActivationHandler: func(common.Location) *interpreter.VariableActivation { return baseActivation }, }, @@ -765,7 +765,7 @@ func TestInterpretInterfaceFunctionConditionsInheritance(t *testing.T) { }, }, Config: &interpreter.Config{ - BaseActivationHandler: func(location common.Location) *interpreter.VariableActivation { + BaseActivationHandler: func(common.Location) *interpreter.VariableActivation { return baseActivation }, }, @@ -856,7 +856,7 @@ func TestInterpretInterfaceFunctionConditionsInheritance(t *testing.T) { }, }, Config: &interpreter.Config{ - BaseActivationHandler: func(location common.Location) *interpreter.VariableActivation { + BaseActivationHandler: func(common.Location) *interpreter.VariableActivation { return baseActivation }, ContractValueHandler: makeContractValueHandler(nil, nil, nil), diff --git a/runtime/tests/interpreter/range_value_test.go b/runtime/tests/interpreter/range_value_test.go new file mode 100644 index 0000000000..93bb45c80b --- /dev/null +++ b/runtime/tests/interpreter/range_value_test.go @@ -0,0 +1,602 @@ +/* + * 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/common" + "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{ + BaseValueActivationHandler: func(common.Location) *sema.VariableActivation { + return baseValueActivation + }, + }, + Config: &interpreter.Config{ + BaseActivationHandler: func(common.Location) *interpreter.VariableActivation { + return 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, + sema.FixedSizeUnsignedIntegerType: + 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{ + BaseValueActivationHandler: func(common.Location) *sema.VariableActivation { + return baseValueActivation + }, + }, + Config: &interpreter.Config{ + BaseActivationHandler: func(common.Location) *interpreter.VariableActivation { + return 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, + sema.FixedSizeUnsignedIntegerType: + 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", + ) + } +} diff --git a/runtime/tests/interpreter/runtimetype_test.go b/runtime/tests/interpreter/runtimetype_test.go index d3411a8880..1950c6eef5 100644 --- a/runtime/tests/interpreter/runtimetype_test.go +++ b/runtime/tests/interpreter/runtimetype_test.go @@ -670,3 +670,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 2bfc7c101c..ee604c1671 100644 --- a/types.go +++ b/types.go @@ -363,6 +363,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 b3bcc0aed4..01b893706e 100644 --- a/values.go +++ b/values.go @@ -2071,6 +2071,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}, + ) +} + // Path type Path struct { diff --git a/values_test.go b/values_test.go index 66f90fc4a6..29ed541ad6 100644 --- a/values_test.go +++ b/values_test.go @@ -221,6 +221,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]",