diff --git a/encoding/ccf/ccf_test.go b/encoding/ccf/ccf_test.go index cd3b97a32d..b40f8fbe3e 100644 --- a/encoding/ccf/ccf_test.go +++ b/encoding/ccf/ccf_test.go @@ -10426,7 +10426,7 @@ func TestEncodeType(t *testing.T) { cadence.TypeValue{ StaticType: &cadence.FunctionType{ TypeParameters: []cadence.TypeParameter{ - {Name: "T", TypeBound: cadence.AnyStructType}, + {Name: "T", TypeBound: cadence.NewSubtypeTypeBound(cadence.AnyStructType)}, }, Parameters: []cadence.Parameter{ {Label: "qux", Identifier: "baz", Type: cadence.StringType}, diff --git a/encoding/ccf/decode.go b/encoding/ccf/decode.go index da42ea7df3..582f545c47 100644 --- a/encoding/ccf/decode.go +++ b/encoding/ccf/decode.go @@ -2182,9 +2182,15 @@ func (d *Decoder) decodeTypeParameterTypeValue(visited *cadenceTypeByCCFTypeID) return cadence.TypeParameter{}, err } + var typeBound cadence.TypeBound + if t != nil { + typeBound = cadence.NewSubtypeTypeBound(t) + } + // Unmetered because decodeTypeParamTypeValue is metered in decodeTypeParamTypeValues and called nowhere else // Type is metered. - return cadence.NewTypeParameter(name, t), nil + // TODO: implement this for generalized bounds + return cadence.NewTypeParameter(name, typeBound), nil } // decodeParameterTypeValues decodes composite initializer parameter types as diff --git a/encoding/ccf/encode.go b/encoding/ccf/encode.go index e9d755dd3d..0d4b55deef 100644 --- a/encoding/ccf/encode.go +++ b/encoding/ccf/encode.go @@ -1945,8 +1945,18 @@ func (e *Encoder) encodeTypeParameterTypeValue(typeParameter cadence.TypeParamet return err } + // TODO: perhaps generalize this? + if typeParameter.TypeBound == nil { + return e.encodeNullableTypeValue(nil, visited) + } + + subTypeBound, isSubtypeBound := typeParameter.TypeBound.(cadence.SubtypeTypeBound) + if !isSubtypeBound { + panic(cadenceErrors.NewUnexpectedError("cannot store non-subtype bounded function type parameter %s", typeParameter.TypeBound)) + } + // element 1: type as type-bound. - return e.encodeNullableTypeValue(typeParameter.TypeBound, visited) + return e.encodeNullableTypeValue(subTypeBound.Type, visited) } // encodeParameterTypeValues encodes composite initializer parameter types as diff --git a/encoding/json/decode.go b/encoding/json/decode.go index 6f60ef6e5f..5afed8a7a9 100644 --- a/encoding/json/decode.go +++ b/encoding/json/decode.go @@ -928,13 +928,47 @@ func (d *Decoder) decodeFunction(valueJSON any) cadence.Function { ) } +func (d *Decoder) decodeTypeBound(valueJSON any, results typeDecodingResults) cadence.TypeBound { + obj := toObject(valueJSON) + + kind := obj.Get("kind") + + switch kind { + case "subtype": + ty := obj.Get("type") + decodedType := d.decodeType(ty, results) + return cadence.NewSubtypeTypeBound(decodedType) + case "supertype": + ty := obj.Get("type") + decodedType := d.decodeType(ty, results) + return cadence.NewSupertypeTypeBound(decodedType) + case "equal": + ty := obj.Get("type") + decodedType := d.decodeType(ty, results) + return cadence.NewEqualTypeBound(decodedType) + case "negation": + bounds := toSlice(obj.Get("bounds")) + decodedBound := d.decodeTypeBound(bounds[0], results) + return cadence.NewNegationTypeBound(decodedBound) + case "conjunction": + bounds := toSlice(obj.Get("bounds")) + var decodedBounds []cadence.TypeBound + for _, bound := range bounds { + decodedBounds = append(decodedBounds, d.decodeTypeBound(bound, results)) + } + return cadence.NewConjunctionTypeBound(decodedBounds) + } + + panic(errors.NewDefaultUserError("invalid kind in type bound: %s", kind)) +} + func (d *Decoder) decodeTypeParameter(valueJSON any, results typeDecodingResults) cadence.TypeParameter { obj := toObject(valueJSON) // Unmetered because decodeTypeParameter is metered in decodeTypeParameters and called nowhere else typeBoundObj, ok := obj[typeBoundKey] - var typeBound cadence.Type + var typeBound cadence.TypeBound if ok { - typeBound = d.decodeType(typeBoundObj, results) + typeBound = d.decodeTypeBound(typeBoundObj, results) } return cadence.NewTypeParameter( diff --git a/encoding/json/encode.go b/encoding/json/encode.go index f8e9189f9f..708daee489 100644 --- a/encoding/json/encode.go +++ b/encoding/json/encode.go @@ -189,8 +189,14 @@ type jsonIntersectionType struct { } type jsonTypeParameter struct { - Name string `json:"name"` - TypeBound jsonValue `json:"typeBound"` + Name string `json:"name"` + TypeBound jsonTypeBound `json:"typeBound"` +} + +type jsonTypeBound struct { + Kind string `json:"kind"` + Type jsonValue `json:"type"` + Bounds []jsonTypeBound `json:"bounds"` } type jsonParameterType struct { @@ -710,11 +716,54 @@ func preparePath(x cadence.Path) jsonValue { } } +func prepareTypeBound(typeBound cadence.TypeBound, results TypePreparationResults) jsonTypeBound { + switch bound := typeBound.(type) { + case cadence.SubtypeTypeBound: + preparedType := PrepareType(bound.Type, results) + return jsonTypeBound{ + Kind: "subtype", + Type: preparedType, + } + case cadence.EqualTypeBound: + preparedType := PrepareType(bound.Type, results) + return jsonTypeBound{ + Kind: "equal", + Type: preparedType, + } + case cadence.SupertypeTypeBound: + preparedType := PrepareType(bound.Type, results) + return jsonTypeBound{ + Kind: "supertype", + Type: preparedType, + } + case cadence.NegationTypeBound: + preparedBound := prepareTypeBound(bound.NegatedBound, results) + return jsonTypeBound{ + Kind: "negation", + Bounds: []jsonTypeBound{preparedBound}, + } + case cadence.ConjunctionTypeBound: + var preparedBounds []jsonTypeBound + + for _, typeBound := range bound.TypeBounds { + preparedBounds = append(preparedBounds, prepareTypeBound(typeBound, results)) + + } + + return jsonTypeBound{ + Kind: "conjunction", + Bounds: preparedBounds, + } + } + + panic(fmt.Errorf("failed to prepare type: unsupported type bound: %T", typeBound)) +} + func prepareTypeParameter(typeParameter cadence.TypeParameter, results TypePreparationResults) jsonTypeParameter { typeBound := typeParameter.TypeBound - var preparedTypeBound jsonValue + var preparedTypeBound jsonTypeBound if typeBound != nil { - preparedTypeBound = PrepareType(typeBound, results) + preparedTypeBound = prepareTypeBound(typeBound, results) } return jsonTypeParameter{ Name: typeParameter.Name, diff --git a/encoding/json/encoding_test.go b/encoding/json/encoding_test.go index 0e35f762b8..ad6d64b762 100644 --- a/encoding/json/encoding_test.go +++ b/encoding/json/encoding_test.go @@ -2557,7 +2557,7 @@ func TestEncodeType(t *testing.T) { cadence.TypeValue{ StaticType: &cadence.FunctionType{ TypeParameters: []cadence.TypeParameter{ - {Name: "T", TypeBound: cadence.AnyStructType}, + {Name: "T", TypeBound: cadence.NewSubtypeTypeBound(cadence.AnyStructType)}, }, Parameters: []cadence.Parameter{ {Label: "qux", Identifier: "baz", Type: cadence.StringType}, @@ -2581,8 +2581,12 @@ func TestEncodeType(t *testing.T) { { "name": "T", "typeBound": { - "kind": "AnyStruct" - } + "kind": "subtype", + "type": { + "kind": "AnyStruct" + }, + "bounds": null + } } ], "parameters": [ @@ -2602,6 +2606,211 @@ func TestEncodeType(t *testing.T) { }) + t.Run("with equal type bound", func(t *testing.T) { + + testEncodeAndDecode( + t, + cadence.TypeValue{ + StaticType: &cadence.FunctionType{ + TypeParameters: []cadence.TypeParameter{ + {Name: "T", TypeBound: cadence.NewEqualTypeBound(cadence.AnyStructType)}, + }, + Parameters: []cadence.Parameter{}, + ReturnType: cadence.IntType, + }, + }, + // language=json + ` + { + "type": "Type", + "value": { + "staticType": { + "kind": "Function", + "purity": "", + "typeID": "fun():Int", + "return": { + "kind": "Int" + }, + "typeParameters": [ + { + "name": "T", + "typeBound": { + "kind": "equal", + "type": { + "kind": "AnyStruct" + }, + "bounds": null + } + } + ], + "parameters": [] + } + } + } + `, + ) + + }) + + t.Run("with supertype type bound", func(t *testing.T) { + + testEncodeAndDecode( + t, + cadence.TypeValue{ + StaticType: &cadence.FunctionType{ + TypeParameters: []cadence.TypeParameter{ + {Name: "T", TypeBound: cadence.NewSupertypeTypeBound(cadence.AnyStructType)}, + }, + Parameters: []cadence.Parameter{}, + ReturnType: cadence.IntType, + }, + }, + // language=json + ` + { + "type": "Type", + "value": { + "staticType": { + "kind": "Function", + "purity": "", + "typeID": "fun():Int", + "return": { + "kind": "Int" + }, + "typeParameters": [ + { + "name": "T", + "typeBound": { + "kind": "supertype", + "type": { + "kind": "AnyStruct" + }, + "bounds": null + } + } + ], + "parameters": [] + } + } + } + `, + ) + + }) + + t.Run("with negated type bound", func(t *testing.T) { + + testEncodeAndDecode( + t, + cadence.TypeValue{ + StaticType: &cadence.FunctionType{ + TypeParameters: []cadence.TypeParameter{ + {Name: "T", TypeBound: cadence.NewNegationTypeBound(cadence.NewEqualTypeBound(cadence.AnyStructType))}, + }, + Parameters: []cadence.Parameter{}, + ReturnType: cadence.IntType, + }, + }, + // language=json + ` + { + "type": "Type", + "value": { + "staticType": { + "kind": "Function", + "purity": "", + "typeID": "fun():Int", + "return": { + "kind": "Int" + }, + "typeParameters": [ + { + "name": "T", + "typeBound": { + "kind": "negation", + "type": null, + "bounds": [ + { + "kind": "equal", + "type": { + "kind": "AnyStruct" + }, + "bounds": null + } + ] + } + } + ], + "parameters": [] + } + } + } + `, + ) + }) + + t.Run("with conjunction type bound", func(t *testing.T) { + + testEncodeAndDecode( + t, + cadence.TypeValue{ + StaticType: &cadence.FunctionType{ + TypeParameters: []cadence.TypeParameter{ + {Name: "T", TypeBound: cadence.NewConjunctionTypeBound([]cadence.TypeBound{ + cadence.NewEqualTypeBound(cadence.AnyStructType), + cadence.NewSubtypeTypeBound(cadence.AnyResourceType), + })}, + }, + Parameters: []cadence.Parameter{}, + ReturnType: cadence.IntType, + }, + }, + // language=json + ` + { + "type": "Type", + "value": { + "staticType": { + "kind": "Function", + "purity": "", + "typeID": "fun():Int", + "return": { + "kind": "Int" + }, + "typeParameters": [ + { + "name": "T", + "typeBound": { + "kind": "conjunction", + "type": null, + "bounds": [ + { + "kind": "equal", + "type": { + "kind": "AnyStruct" + }, + "bounds": null + }, + { + "kind": "subtype", + "type": { + "kind": "AnyResource" + }, + "bounds": null + } + ] + } + } + ], + "parameters": [] + } + } + } + `, + ) + + }) + t.Run("with view static function", func(t *testing.T) { testEncodeAndDecode( diff --git a/runtime/account_test.go b/runtime/account_test.go index 54d37cbf53..05f307bfae 100644 --- a/runtime/account_test.go +++ b/runtime/account_test.go @@ -184,7 +184,7 @@ func TestRuntimeStoreAccountAPITypes(t *testing.T) { RequireError(t, err) - assert.Contains(t, err.Error(), "expected `Storable`") + assert.Contains(t, err.Error(), "expected type satisfying <=: Storable") } } diff --git a/runtime/common/memorykind.go b/runtime/common/memorykind.go index c3cdf5fc30..71367ecb35 100644 --- a/runtime/common/memorykind.go +++ b/runtime/common/memorykind.go @@ -133,6 +133,12 @@ const ( MemoryKindCadenceCapabilityType MemoryKindCadenceEnumType + MemoryKindCadenceSubtypeBound + MemoryKindCadenceSupertypeBound + MemoryKindCadenceEqualBound + MemoryKindCadenceNegationBound + MemoryKindCadenceConjunctionBound + // Misc MemoryKindRawString diff --git a/runtime/common/memorykind_string.go b/runtime/common/memorykind_string.go index 65ac875cbe..5b3c709560 100644 --- a/runtime/common/memorykind_string.go +++ b/runtime/common/memorykind_string.go @@ -105,114 +105,119 @@ func _() { _ = 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] + _ = x[MemoryKindCadenceSubtypeBound-97] + _ = x[MemoryKindCadenceSupertypeBound-98] + _ = x[MemoryKindCadenceEqualBound-99] + _ = x[MemoryKindCadenceNegationBound-100] + _ = x[MemoryKindCadenceConjunctionBound-101] + _ = x[MemoryKindRawString-102] + _ = x[MemoryKindAddressLocation-103] + _ = x[MemoryKindBytes-104] + _ = x[MemoryKindVariable-105] + _ = x[MemoryKindCompositeTypeInfo-106] + _ = x[MemoryKindCompositeField-107] + _ = x[MemoryKindInvocation-108] + _ = x[MemoryKindStorageMap-109] + _ = x[MemoryKindStorageKey-110] + _ = x[MemoryKindTypeToken-111] + _ = x[MemoryKindErrorToken-112] + _ = x[MemoryKindSpaceToken-113] + _ = x[MemoryKindProgram-114] + _ = x[MemoryKindIdentifier-115] + _ = x[MemoryKindArgument-116] + _ = x[MemoryKindBlock-117] + _ = x[MemoryKindFunctionBlock-118] + _ = x[MemoryKindParameter-119] + _ = x[MemoryKindParameterList-120] + _ = x[MemoryKindTypeParameter-121] + _ = x[MemoryKindTypeParameterList-122] + _ = x[MemoryKindTransfer-123] + _ = x[MemoryKindMembers-124] + _ = x[MemoryKindTypeAnnotation-125] + _ = x[MemoryKindDictionaryEntry-126] + _ = x[MemoryKindFunctionDeclaration-127] + _ = x[MemoryKindCompositeDeclaration-128] + _ = x[MemoryKindAttachmentDeclaration-129] + _ = x[MemoryKindInterfaceDeclaration-130] + _ = x[MemoryKindEntitlementDeclaration-131] + _ = x[MemoryKindEntitlementMappingElement-132] + _ = x[MemoryKindEntitlementMappingDeclaration-133] + _ = x[MemoryKindEnumCaseDeclaration-134] + _ = x[MemoryKindFieldDeclaration-135] + _ = x[MemoryKindTransactionDeclaration-136] + _ = x[MemoryKindImportDeclaration-137] + _ = x[MemoryKindVariableDeclaration-138] + _ = x[MemoryKindSpecialFunctionDeclaration-139] + _ = x[MemoryKindPragmaDeclaration-140] + _ = x[MemoryKindAssignmentStatement-141] + _ = x[MemoryKindBreakStatement-142] + _ = x[MemoryKindContinueStatement-143] + _ = x[MemoryKindEmitStatement-144] + _ = x[MemoryKindExpressionStatement-145] + _ = x[MemoryKindForStatement-146] + _ = x[MemoryKindIfStatement-147] + _ = x[MemoryKindReturnStatement-148] + _ = x[MemoryKindSwapStatement-149] + _ = x[MemoryKindSwitchStatement-150] + _ = x[MemoryKindWhileStatement-151] + _ = x[MemoryKindRemoveStatement-152] + _ = x[MemoryKindBooleanExpression-153] + _ = x[MemoryKindVoidExpression-154] + _ = x[MemoryKindNilExpression-155] + _ = x[MemoryKindStringExpression-156] + _ = x[MemoryKindIntegerExpression-157] + _ = x[MemoryKindFixedPointExpression-158] + _ = x[MemoryKindArrayExpression-159] + _ = x[MemoryKindDictionaryExpression-160] + _ = x[MemoryKindIdentifierExpression-161] + _ = x[MemoryKindInvocationExpression-162] + _ = x[MemoryKindMemberExpression-163] + _ = x[MemoryKindIndexExpression-164] + _ = x[MemoryKindConditionalExpression-165] + _ = x[MemoryKindUnaryExpression-166] + _ = x[MemoryKindBinaryExpression-167] + _ = x[MemoryKindFunctionExpression-168] + _ = x[MemoryKindCastingExpression-169] + _ = x[MemoryKindCreateExpression-170] + _ = x[MemoryKindDestroyExpression-171] + _ = x[MemoryKindReferenceExpression-172] + _ = x[MemoryKindForceExpression-173] + _ = x[MemoryKindPathExpression-174] + _ = x[MemoryKindAttachExpression-175] + _ = x[MemoryKindConstantSizedType-176] + _ = x[MemoryKindDictionaryType-177] + _ = x[MemoryKindFunctionType-178] + _ = x[MemoryKindInstantiationType-179] + _ = x[MemoryKindNominalType-180] + _ = x[MemoryKindOptionalType-181] + _ = x[MemoryKindReferenceType-182] + _ = x[MemoryKindIntersectionType-183] + _ = x[MemoryKindVariableSizedType-184] + _ = x[MemoryKindPosition-185] + _ = x[MemoryKindRange-186] + _ = x[MemoryKindElaboration-187] + _ = x[MemoryKindActivation-188] + _ = x[MemoryKindActivationEntries-189] + _ = x[MemoryKindVariableSizedSemaType-190] + _ = x[MemoryKindConstantSizedSemaType-191] + _ = x[MemoryKindDictionarySemaType-192] + _ = x[MemoryKindOptionalSemaType-193] + _ = x[MemoryKindIntersectionSemaType-194] + _ = x[MemoryKindReferenceSemaType-195] + _ = x[MemoryKindEntitlementSemaType-196] + _ = x[MemoryKindEntitlementMapSemaType-197] + _ = x[MemoryKindEntitlementRelationSemaType-198] + _ = x[MemoryKindCapabilitySemaType-199] + _ = x[MemoryKindInclusiveRangeSemaType-200] + _ = x[MemoryKindOrderedMap-201] + _ = x[MemoryKindOrderedMapEntryList-202] + _ = x[MemoryKindOrderedMapEntry-203] + _ = x[MemoryKindLast-204] } -const _MemoryKind_name = "UnknownAddressValueStringValueCharacterValueNumberValueArrayValueBaseDictionaryValueBaseCompositeValueBaseSimpleCompositeValueBaseOptionalValueTypeValuePathValueCapabilityValueStorageReferenceValueEphemeralReferenceValueInterpretedFunctionValueHostFunctionValueBoundFunctionValueBigIntSimpleCompositeValuePublishedValueStorageCapabilityControllerValueAccountCapabilityControllerValueAtreeArrayDataSlabAtreeArrayMetaDataSlabAtreeArrayElementOverheadAtreeMapDataSlabAtreeMapMetaDataSlabAtreeMapElementOverheadAtreeMapPreAllocatedElementAtreeEncodedSlabPrimitiveStaticTypeCompositeStaticTypeInterfaceStaticTypeVariableSizedStaticTypeConstantSizedStaticTypeDictionaryStaticTypeInclusiveRangeStaticTypeOptionalStaticTypeIntersectionStaticTypeEntitlementSetStaticAccessEntitlementMapStaticAccessReferenceStaticTypeCapabilityStaticTypeFunctionStaticTypeCadenceVoidValueCadenceOptionalValueCadenceBoolValueCadenceStringValueCadenceCharacterValueCadenceAddressValueCadenceIntValueCadenceNumberValueCadenceArrayValueBaseCadenceArrayValueLengthCadenceDictionaryValueCadenceInclusiveRangeValueCadenceKeyValuePairCadenceStructValueBaseCadenceStructValueSizeCadenceResourceValueBaseCadenceAttachmentValueBaseCadenceResourceValueSizeCadenceAttachmentValueSizeCadenceEventValueBaseCadenceEventValueSizeCadenceContractValueBaseCadenceContractValueSizeCadenceEnumValueBaseCadenceEnumValueSizeCadencePathValueCadenceTypeValueCadenceCapabilityValueCadenceFunctionValueCadenceOptionalTypeCadenceVariableSizedArrayTypeCadenceConstantSizedArrayTypeCadenceDictionaryTypeCadenceInclusiveRangeTypeCadenceFieldCadenceParameterCadenceTypeParameterCadenceStructTypeCadenceResourceTypeCadenceAttachmentTypeCadenceEventTypeCadenceContractTypeCadenceStructInterfaceTypeCadenceResourceInterfaceTypeCadenceContractInterfaceTypeCadenceFunctionTypeCadenceEntitlementSetAccessCadenceEntitlementMapAccessCadenceReferenceTypeCadenceIntersectionTypeCadenceCapabilityTypeCadenceEnumTypeRawStringAddressLocationBytesVariableCompositeTypeInfoCompositeFieldInvocationStorageMapStorageKeyTypeTokenErrorTokenSpaceTokenProgramIdentifierArgumentBlockFunctionBlockParameterParameterListTypeParameterTypeParameterListTransferMembersTypeAnnotationDictionaryEntryFunctionDeclarationCompositeDeclarationAttachmentDeclarationInterfaceDeclarationEntitlementDeclarationEntitlementMappingElementEntitlementMappingDeclarationEnumCaseDeclarationFieldDeclarationTransactionDeclarationImportDeclarationVariableDeclarationSpecialFunctionDeclarationPragmaDeclarationAssignmentStatementBreakStatementContinueStatementEmitStatementExpressionStatementForStatementIfStatementReturnStatementSwapStatementSwitchStatementWhileStatementRemoveStatementBooleanExpressionVoidExpressionNilExpressionStringExpressionIntegerExpressionFixedPointExpressionArrayExpressionDictionaryExpressionIdentifierExpressionInvocationExpressionMemberExpressionIndexExpressionConditionalExpressionUnaryExpressionBinaryExpressionFunctionExpressionCastingExpressionCreateExpressionDestroyExpressionReferenceExpressionForceExpressionPathExpressionAttachExpressionConstantSizedTypeDictionaryTypeFunctionTypeInstantiationTypeNominalTypeOptionalTypeReferenceTypeIntersectionTypeVariableSizedTypePositionRangeElaborationActivationActivationEntriesVariableSizedSemaTypeConstantSizedSemaTypeDictionarySemaTypeOptionalSemaTypeIntersectionSemaTypeReferenceSemaTypeEntitlementSemaTypeEntitlementMapSemaTypeEntitlementRelationSemaTypeCapabilitySemaTypeInclusiveRangeSemaTypeOrderedMapOrderedMapEntryListOrderedMapEntryLast" +const _MemoryKind_name = "UnknownAddressValueStringValueCharacterValueNumberValueArrayValueBaseDictionaryValueBaseCompositeValueBaseSimpleCompositeValueBaseOptionalValueTypeValuePathValueCapabilityValueStorageReferenceValueEphemeralReferenceValueInterpretedFunctionValueHostFunctionValueBoundFunctionValueBigIntSimpleCompositeValuePublishedValueStorageCapabilityControllerValueAccountCapabilityControllerValueAtreeArrayDataSlabAtreeArrayMetaDataSlabAtreeArrayElementOverheadAtreeMapDataSlabAtreeMapMetaDataSlabAtreeMapElementOverheadAtreeMapPreAllocatedElementAtreeEncodedSlabPrimitiveStaticTypeCompositeStaticTypeInterfaceStaticTypeVariableSizedStaticTypeConstantSizedStaticTypeDictionaryStaticTypeInclusiveRangeStaticTypeOptionalStaticTypeIntersectionStaticTypeEntitlementSetStaticAccessEntitlementMapStaticAccessReferenceStaticTypeCapabilityStaticTypeFunctionStaticTypeCadenceVoidValueCadenceOptionalValueCadenceBoolValueCadenceStringValueCadenceCharacterValueCadenceAddressValueCadenceIntValueCadenceNumberValueCadenceArrayValueBaseCadenceArrayValueLengthCadenceDictionaryValueCadenceInclusiveRangeValueCadenceKeyValuePairCadenceStructValueBaseCadenceStructValueSizeCadenceResourceValueBaseCadenceAttachmentValueBaseCadenceResourceValueSizeCadenceAttachmentValueSizeCadenceEventValueBaseCadenceEventValueSizeCadenceContractValueBaseCadenceContractValueSizeCadenceEnumValueBaseCadenceEnumValueSizeCadencePathValueCadenceTypeValueCadenceCapabilityValueCadenceFunctionValueCadenceOptionalTypeCadenceVariableSizedArrayTypeCadenceConstantSizedArrayTypeCadenceDictionaryTypeCadenceInclusiveRangeTypeCadenceFieldCadenceParameterCadenceTypeParameterCadenceStructTypeCadenceResourceTypeCadenceAttachmentTypeCadenceEventTypeCadenceContractTypeCadenceStructInterfaceTypeCadenceResourceInterfaceTypeCadenceContractInterfaceTypeCadenceFunctionTypeCadenceEntitlementSetAccessCadenceEntitlementMapAccessCadenceReferenceTypeCadenceIntersectionTypeCadenceCapabilityTypeCadenceEnumTypeCadenceSubtypeBoundCadenceSupertypeBoundCadenceEqualBoundCadenceNegationBoundCadenceConjunctionBoundRawStringAddressLocationBytesVariableCompositeTypeInfoCompositeFieldInvocationStorageMapStorageKeyTypeTokenErrorTokenSpaceTokenProgramIdentifierArgumentBlockFunctionBlockParameterParameterListTypeParameterTypeParameterListTransferMembersTypeAnnotationDictionaryEntryFunctionDeclarationCompositeDeclarationAttachmentDeclarationInterfaceDeclarationEntitlementDeclarationEntitlementMappingElementEntitlementMappingDeclarationEnumCaseDeclarationFieldDeclarationTransactionDeclarationImportDeclarationVariableDeclarationSpecialFunctionDeclarationPragmaDeclarationAssignmentStatementBreakStatementContinueStatementEmitStatementExpressionStatementForStatementIfStatementReturnStatementSwapStatementSwitchStatementWhileStatementRemoveStatementBooleanExpressionVoidExpressionNilExpressionStringExpressionIntegerExpressionFixedPointExpressionArrayExpressionDictionaryExpressionIdentifierExpressionInvocationExpressionMemberExpressionIndexExpressionConditionalExpressionUnaryExpressionBinaryExpressionFunctionExpressionCastingExpressionCreateExpressionDestroyExpressionReferenceExpressionForceExpressionPathExpressionAttachExpressionConstantSizedTypeDictionaryTypeFunctionTypeInstantiationTypeNominalTypeOptionalTypeReferenceTypeIntersectionTypeVariableSizedTypePositionRangeElaborationActivationActivationEntriesVariableSizedSemaTypeConstantSizedSemaTypeDictionarySemaTypeOptionalSemaTypeIntersectionSemaTypeReferenceSemaTypeEntitlementSemaTypeEntitlementMapSemaTypeEntitlementRelationSemaTypeCapabilitySemaTypeInclusiveRangeSemaTypeOrderedMapOrderedMapEntryListOrderedMapEntryLast" -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} +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, 1964, 1985, 2002, 2022, 2045, 2054, 2069, 2074, 2082, 2099, 2113, 2123, 2133, 2143, 2152, 2162, 2172, 2179, 2189, 2197, 2202, 2215, 2224, 2237, 2250, 2267, 2275, 2282, 2296, 2311, 2330, 2350, 2371, 2391, 2413, 2438, 2467, 2486, 2502, 2524, 2541, 2560, 2586, 2603, 2622, 2636, 2653, 2666, 2685, 2697, 2708, 2723, 2736, 2751, 2765, 2780, 2797, 2811, 2824, 2840, 2857, 2877, 2892, 2912, 2932, 2952, 2968, 2983, 3004, 3019, 3035, 3053, 3070, 3086, 3103, 3122, 3137, 3151, 3167, 3184, 3198, 3210, 3227, 3238, 3250, 3263, 3279, 3296, 3304, 3309, 3320, 3330, 3347, 3368, 3389, 3407, 3423, 3443, 3460, 3479, 3501, 3528, 3546, 3568, 3578, 3597, 3612, 3616} 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 529a03a7ef..bbb1530a0a 100644 --- a/runtime/common/metering.go +++ b/runtime/common/metering.go @@ -235,6 +235,12 @@ var ( CadenceStructTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindCadenceStructType) CadenceAttachmentTypeMemoryUsage = NewConstantMemoryUsage(MemoryKindCadenceAttachmentType) + CadenceSubtypeBoundMemoryUsage = NewConstantMemoryUsage(MemoryKindCadenceSubtypeBound) + CadenceEqualBoundMemoryUsage = NewConstantMemoryUsage(MemoryKindCadenceEqualBound) + CadenceSupertypeBoundMemoryUsage = NewConstantMemoryUsage(MemoryKindCadenceSupertypeBound) + CadenceNegationBoundMemoryUsage = NewConstantMemoryUsage(MemoryKindCadenceNegationBound) + CadenceConjunctionBoundMemoryUsage = NewConstantMemoryUsage(MemoryKindCadenceConjunctionBound) + // Following are the known memory usage amounts for string representation of interpreter values. // Same as `len(format.X)`. However, values are hard-coded to avoid the circular dependency. diff --git a/runtime/convertTypes.go b/runtime/convertTypes.go index 45237b995e..35c90d75db 100644 --- a/runtime/convertTypes.go +++ b/runtime/convertTypes.go @@ -496,6 +496,42 @@ func exportInclusiveRangeType( ) } +func exportTypeBound( + gauge common.MemoryGauge, + bound sema.TypeBound, + results map[sema.TypeID]cadence.Type, +) cadence.TypeBound { + switch bound := bound.(type) { + case sema.SubtypeTypeBound: + common.UseMemory(gauge, common.CadenceSubtypeBoundMemoryUsage) + ty := ExportMeteredType(gauge, bound.Type, results) + return cadence.NewSubtypeTypeBound(ty) + case sema.SupertypeTypeBound: + common.UseMemory(gauge, common.CadenceEqualBoundMemoryUsage) + ty := ExportMeteredType(gauge, bound.Type, results) + return cadence.NewSupertypeTypeBound(ty) + case sema.EqualTypeBound: + common.UseMemory(gauge, common.CadenceEqualBoundMemoryUsage) + ty := ExportMeteredType(gauge, bound.Type, results) + return cadence.NewEqualTypeBound(ty) + case sema.ConjunctionTypeBound: + common.UseMemory(gauge, common.CadenceNegationBoundMemoryUsage) + + var joined []cadence.TypeBound + for _, typeBound := range bound.TypeBounds { + joined = append(joined, exportTypeBound(gauge, typeBound, results)) + } + return cadence.NewConjunctionTypeBound(joined) + + case sema.NegationTypeBound: + common.UseMemory(gauge, common.CadenceConjunctionBoundMemoryUsage) + negated := exportTypeBound(gauge, bound.NegatedBound, results) + return cadence.NewNegationTypeBound(negated) + } + + panic(errors.NewUnreachableError()) +} + func exportFunctionType( gauge common.MemoryGauge, t *sema.FunctionType, @@ -514,9 +550,9 @@ func exportFunctionType( for i, typeParameter := range t.TypeParameters { typeBound := typeParameter.TypeBound - var convertedParameterTypeBound cadence.Type + var convertedParameterTypeBound cadence.TypeBound if typeBound != nil { - convertedParameterTypeBound = ExportMeteredType(gauge, typeBound, results) + convertedParameterTypeBound = exportTypeBound(gauge, typeBound, results) } // Metered above diff --git a/runtime/interpreter/div_mod_test.go b/runtime/interpreter/div_mod_test.go index eeb2ca6d38..8b5354d5cd 100644 --- a/runtime/interpreter/div_mod_test.go +++ b/runtime/interpreter/div_mod_test.go @@ -3388,7 +3388,7 @@ func TestNegativeMod(t *testing.T) { }, } - for _, integerType := range sema.AllSignedIntegerTypes { + for _, integerType := range sema.AllLeafSignedIntegerTypes { if _, ok := tests[integerType.String()]; !ok { panic(fmt.Sprintf("broken test: missing %s", integerType)) } @@ -3414,7 +3414,7 @@ func TestNegativeMod(t *testing.T) { }, } - for _, integerType := range sema.AllSignedFixedPointTypes { + for _, integerType := range sema.AllLeafSignedFixedPointTypes { if _, ok := tests[integerType.String()]; !ok { panic(fmt.Sprintf("broken test: missing %s", integerType)) } diff --git a/runtime/interpreter/value_test.go b/runtime/interpreter/value_test.go index 2fdb809b6d..64f74c10da 100644 --- a/runtime/interpreter/value_test.go +++ b/runtime/interpreter/value_test.go @@ -3489,13 +3489,7 @@ func TestNumberValueIntegerConversion(t *testing.T) { sema.Int256Type: NewUnmeteredInt256ValueFromInt64(42), } - for _, ty := range sema.AllIntegerTypes { - // Only test leaf types - switch ty { - case sema.IntegerType, sema.SignedIntegerType, sema.FixedSizeUnsignedIntegerType: - continue - } - + for _, ty := range sema.AllLeafIntegerTypes { _, ok := testValues[ty.(*sema.NumericType)] require.True(t, ok, "missing expected value for type %s", ty.String()) } @@ -3794,13 +3788,7 @@ func TestValue_ConformsToStaticType(t *testing.T) { sema.Int256Type: NewUnmeteredInt256ValueFromInt64(42), } - for _, ty := range sema.AllIntegerTypes { - // Only test leaf types - switch ty { - case sema.IntegerType, sema.SignedIntegerType, sema.FixedSizeUnsignedIntegerType: - continue - } - + for _, ty := range sema.AllLeafIntegerTypes { _, ok := testCases[ty.(*sema.NumericType)] require.True(t, ok, "missing case for type %s", ty.String()) } @@ -3826,13 +3814,7 @@ func TestValue_ConformsToStaticType(t *testing.T) { sema.Fix64Type: NewUnmeteredFix64ValueWithInteger(42, EmptyLocationRange), } - for _, ty := range sema.AllFixedPointTypes { - // Only test leaf types - switch ty { - case sema.FixedPointType, sema.SignedFixedPointType: - continue - } - + for _, ty := range sema.AllLeafFixedPointTypes { _, ok := testCases[ty.(*sema.FixedPointNumericType)] require.True(t, ok, "missing case for type %s", ty.String()) } diff --git a/runtime/literal_test.go b/runtime/literal_test.go index 1fe6450373..7a8cf3b349 100644 --- a/runtime/literal_test.go +++ b/runtime/literal_test.go @@ -607,7 +607,7 @@ func TestRuntimeParseLiteral(t *testing.T) { require.Nil(t, value) }) - for _, unsignedIntegerType := range sema.AllUnsignedIntegerTypes { + for _, unsignedIntegerType := range sema.AllLeafUnsignedIntegerTypes { t.Run( fmt.Sprintf( @@ -649,7 +649,7 @@ func TestRuntimeParseLiteral(t *testing.T) { } for _, signedIntegerType := range common.Concat( - sema.AllSignedIntegerTypes, + sema.AllLeafSignedIntegerTypes, []sema.Type{ sema.IntegerType, sema.SignedIntegerType, diff --git a/runtime/program_params_validation_test.go b/runtime/program_params_validation_test.go index f9a2b693db..d7f202af4b 100644 --- a/runtime/program_params_validation_test.go +++ b/runtime/program_params_validation_test.go @@ -348,7 +348,7 @@ func TestRuntimeScriptParameterTypeValidation(t *testing.T) { require.ErrorAs(t, err, &checkerError) errs := checker.RequireCheckerErrors(t, checkerError, 1) - assert.IsType(t, &sema.InvalidTypeArgumentError{}, errs[0]) + assert.IsType(t, &sema.TypeBoundError{}, errs[0]) }) t.Run("Invalid InclusiveRange with mixed value types", func(t *testing.T) { @@ -387,7 +387,7 @@ func TestRuntimeScriptParameterTypeValidation(t *testing.T) { require.ErrorAs(t, err, &checkerError) errs := checker.RequireCheckerErrors(t, checkerError, 1) - assert.IsType(t, &sema.InvalidTypeArgumentError{}, errs[0]) + assert.IsType(t, &sema.TypeBoundError{}, errs[0]) }) t.Run("Capability", func(t *testing.T) { diff --git a/runtime/runtime_test.go b/runtime/runtime_test.go index dea74cfe52..38673b35d3 100644 --- a/runtime/runtime_test.go +++ b/runtime/runtime_test.go @@ -1734,10 +1734,24 @@ func TestRuntimeStorageMultipleTransactionsInclusiveRangeFunction(t *testing.T) errs := checker.RequireCheckerErrors(t, checkerErr, 1) - assert.IsType(t, &sema.TypeMismatchError{}, errs[0]) + var typeBoundError *sema.TypeBoundError + require.ErrorAs(t, errs[0], &typeBoundError) + + assert.Equal( + t, + sema.SubtypeTypeBound{ + Type: sema.StorableType, + }, + typeBoundError.ExpectedTypeBound, + ) + assert.Equal( + t, + &sema.InclusiveRangeType{ + MemberType: sema.IntType, + }, + typeBoundError.ActualType, + ) - typeMismatchError := errs[0].(*sema.TypeMismatchError) - assert.Contains(t, typeMismatchError.SecondaryError(), "expected `Storable`, got `InclusiveRange`") } func TestRuntimeResourceContractUseThroughReference(t *testing.T) { @@ -4582,7 +4596,7 @@ func TestRuntimeRandom(t *testing.T) { } testTypes := func(t *testing.T, testType func(*testing.T, sema.Type)) { - for _, ty := range sema.AllFixedSizeUnsignedIntegerTypes { + for _, ty := range sema.AllLeafFixedSizeUnsignedIntegerTypes { ty := ty t.Run(ty.String(), func(t *testing.T) { t.Parallel() diff --git a/runtime/sema/account.gen.go b/runtime/sema/account.gen.go index 1e1fbbc8d3..dc2f046987 100644 --- a/runtime/sema/account.gen.go +++ b/runtime/sema/account.gen.go @@ -127,8 +127,10 @@ All storage paths of this account. const Account_StorageTypeSaveFunctionName = "save" var Account_StorageTypeSaveFunctionTypeParameterT = &TypeParameter{ - Name: "T", - TypeBound: StorableType, + Name: "T", + TypeBound: SubtypeTypeBound{ + Type: StorableType, + }, } var Account_StorageTypeSaveFunctionType = &FunctionType{ @@ -193,8 +195,10 @@ The path must be a storage path, i.e., only the domain ` + "`storage`" + ` is al const Account_StorageTypeLoadFunctionName = "load" var Account_StorageTypeLoadFunctionTypeParameterT = &TypeParameter{ - Name: "T", - TypeBound: StorableType, + Name: "T", + TypeBound: SubtypeTypeBound{ + Type: StorableType, + }, } var Account_StorageTypeLoadFunctionType = &FunctionType{ @@ -236,8 +240,10 @@ The path must be a storage path, i.e., only the domain ` + "`storage`" + ` is al const Account_StorageTypeCopyFunctionName = "copy" var Account_StorageTypeCopyFunctionTypeParameterT = &TypeParameter{ - Name: "T", - TypeBound: AnyStructType, + Name: "T", + TypeBound: SubtypeTypeBound{ + Type: AnyStructType, + }, } var Account_StorageTypeCopyFunctionType = &FunctionType{ @@ -279,8 +285,10 @@ The path must be a storage path, i.e., only the domain ` + "`storage`" + ` is al const Account_StorageTypeCheckFunctionName = "check" var Account_StorageTypeCheckFunctionTypeParameterT = &TypeParameter{ - Name: "T", - TypeBound: AnyType, + Name: "T", + TypeBound: SubtypeTypeBound{ + Type: AnyType, + }, } var Account_StorageTypeCheckFunctionType = &FunctionType{ @@ -312,9 +320,11 @@ const Account_StorageTypeBorrowFunctionName = "borrow" var Account_StorageTypeBorrowFunctionTypeParameterT = &TypeParameter{ Name: "T", - TypeBound: &ReferenceType{ - Type: AnyType, - Authorization: UnauthorizedAccess, + TypeBound: SubtypeTypeBound{ + Type: &ReferenceType{ + Type: AnyType, + Authorization: UnauthorizedAccess, + }, }, } @@ -735,9 +745,11 @@ const Account_ContractsTypeBorrowFunctionName = "borrow" var Account_ContractsTypeBorrowFunctionTypeParameterT = &TypeParameter{ Name: "T", - TypeBound: &ReferenceType{ - Type: AnyType, - Authorization: UnauthorizedAccess, + TypeBound: SubtypeTypeBound{ + Type: &ReferenceType{ + Type: AnyType, + Authorization: UnauthorizedAccess, + }, }, } @@ -1060,9 +1072,11 @@ const Account_InboxTypeUnpublishFunctionName = "unpublish" var Account_InboxTypeUnpublishFunctionTypeParameterT = &TypeParameter{ Name: "T", - TypeBound: &ReferenceType{ - Type: AnyType, - Authorization: UnauthorizedAccess, + TypeBound: SubtypeTypeBound{ + Type: &ReferenceType{ + Type: AnyType, + Authorization: UnauthorizedAccess, + }, }, } @@ -1101,9 +1115,11 @@ const Account_InboxTypeClaimFunctionName = "claim" var Account_InboxTypeClaimFunctionTypeParameterT = &TypeParameter{ Name: "T", - TypeBound: &ReferenceType{ - Type: AnyType, - Authorization: UnauthorizedAccess, + TypeBound: SubtypeTypeBound{ + Type: &ReferenceType{ + Type: AnyType, + Authorization: UnauthorizedAccess, + }, }, } @@ -1214,9 +1230,11 @@ const Account_CapabilitiesTypeGetFunctionName = "get" var Account_CapabilitiesTypeGetFunctionTypeParameterT = &TypeParameter{ Name: "T", - TypeBound: &ReferenceType{ - Type: AnyType, - Authorization: UnauthorizedAccess, + TypeBound: SubtypeTypeBound{ + Type: &ReferenceType{ + Type: AnyType, + Authorization: UnauthorizedAccess, + }, }, } @@ -1254,9 +1272,11 @@ const Account_CapabilitiesTypeBorrowFunctionName = "borrow" var Account_CapabilitiesTypeBorrowFunctionTypeParameterT = &TypeParameter{ Name: "T", - TypeBound: &ReferenceType{ - Type: AnyType, - Authorization: UnauthorizedAccess, + TypeBound: SubtypeTypeBound{ + Type: &ReferenceType{ + Type: AnyType, + Authorization: UnauthorizedAccess, + }, }, } @@ -1446,9 +1466,11 @@ const Account_StorageCapabilitiesTypeIssueFunctionName = "issue" var Account_StorageCapabilitiesTypeIssueFunctionTypeParameterT = &TypeParameter{ Name: "T", - TypeBound: &ReferenceType{ - Type: AnyType, - Authorization: UnauthorizedAccess, + TypeBound: SubtypeTypeBound{ + Type: &ReferenceType{ + Type: AnyType, + Authorization: UnauthorizedAccess, + }, }, } @@ -1669,9 +1691,11 @@ const Account_AccountCapabilitiesTypeIssueFunctionName = "issue" var Account_AccountCapabilitiesTypeIssueFunctionTypeParameterT = &TypeParameter{ Name: "T", - TypeBound: &ReferenceType{ - Type: AccountType, - Authorization: UnauthorizedAccess, + TypeBound: SubtypeTypeBound{ + Type: &ReferenceType{ + Type: AccountType, + Authorization: UnauthorizedAccess, + }, }, } diff --git a/runtime/sema/account.go b/runtime/sema/account.go index 75eba98d60..72189f1060 100644 --- a/runtime/sema/account.go +++ b/runtime/sema/account.go @@ -18,14 +18,6 @@ package sema -import ( - "fmt" - - "github.com/onflow/cadence/runtime/ast" - "github.com/onflow/cadence/runtime/common" - "github.com/onflow/cadence/runtime/errors" -) - //go:generate go run ./gen account.cdc account.gen.go var AccountTypeAnnotation = NewTypeAnnotation(AccountType) @@ -64,30 +56,13 @@ var FullyEntitledAccountReferenceTypeAnnotation = NewTypeAnnotation(FullyEntitle func init() { Account_ContractsTypeAddFunctionType.Arity = &Arity{Min: 2} - Account_CapabilitiesTypeGetFunctionType.TypeArgumentsCheck = - func(memoryGauge common.MemoryGauge, - typeArguments *TypeParameterTypeOrderedMap, - _ []*ast.TypeAnnotation, - invocationRange ast.HasPosition, - report func(err error), - ) { - typeArg, ok := typeArguments.Get(Account_CapabilitiesTypeGetFunctionTypeParameterT) - if !ok || typeArg == nil { - // checker should prevent this - panic(errors.NewUnreachableError()) - } - if typeArg == NeverType { - report(&InvalidTypeArgumentError{ - TypeArgumentName: Account_CapabilitiesTypeGetFunctionTypeParameterT.Name, - Range: ast.NewRangeFromPositioned(memoryGauge, invocationRange), - Details: fmt.Sprintf( - "Type argument for `%s` cannot be `%s`", - Account_CapabilitiesTypeGetFunctionName, - NeverType, - ), - }) - } - } + // capabilities.get has a strict supertype requirement that its type argument is not `Never`, + // but we can't yet express this in source syntax. + // TODO: if we add support for arbitrary logical type bounds to the source language, move this + // into the generator + Account_CapabilitiesTypeGetFunctionType.TypeParameters[0].TypeBound = + Account_CapabilitiesTypeGetFunctionType.TypeParameters[0].TypeBound. + And(NewStrictSupertypeTypeBound(NeverType)) addToBaseActivation(AccountMappingType) addToBaseActivation(CapabilitiesMappingType) diff --git a/runtime/sema/checker.go b/runtime/sema/checker.go index d9dcb64014..ccdb7bb719 100644 --- a/runtime/sema/checker.go +++ b/runtime/sema/checker.go @@ -41,8 +41,10 @@ const ResultIdentifier = "result" var beforeType = func() *FunctionType { typeParameter := &TypeParameter{ - Name: "T", - TypeBound: AnyStructType, + Name: "T", + TypeBound: SubtypeTypeBound{ + Type: AnyStructType, + }, } typeAnnotation := NewTypeAnnotation( @@ -1378,11 +1380,14 @@ func (checker *Checker) typeParameters(typeParameterList *ast.TypeParameterList) for i, typeParameter := range typeParameterList.TypeParameters { typeBoundAnnotation := typeParameter.TypeBound - var convertedTypeBound Type + var convertedTypeBound TypeBound if typeBoundAnnotation != nil { + // TODO: for now, the syntax only supports subtype type bounds convertedTypeBoundAnnotation := checker.ConvertTypeAnnotation(typeBoundAnnotation) checker.checkTypeAnnotation(convertedTypeBoundAnnotation, typeBoundAnnotation) - convertedTypeBound = convertedTypeBoundAnnotation.Type + convertedTypeBound = SubtypeTypeBound{ + Type: convertedTypeBoundAnnotation.Type, + } } typeParameters[i] = &TypeParameter{ diff --git a/runtime/sema/errors.go b/runtime/sema/errors.go index 6a4153be1b..1885e2fb99 100644 --- a/runtime/sema/errors.go +++ b/runtime/sema/errors.go @@ -333,6 +333,35 @@ func (e *TypeMismatchWithDescriptionError) SecondaryError() string { ) } +// TypeBoundError + +type TypeBoundError struct { + ExpectedTypeBound TypeBound + ActualType Type + Expression ast.Expression + ast.Range +} + +var _ SemanticError = &TypeBoundError{} +var _ errors.UserError = &TypeBoundError{} +var _ errors.SecondaryError = &TypeBoundError{} + +func (*TypeBoundError) isSemanticError() {} + +func (*TypeBoundError) IsUserError() {} + +func (e *TypeBoundError) Error() string { + return "type bound unsatisfied" +} + +func (e *TypeBoundError) SecondaryError() string { + return fmt.Sprintf( + "expected type satisfying %s, got `%s`", + e.ExpectedTypeBound, + e.ActualType.QualifiedString(), + ) +} + // NotIndexableTypeError type NotIndexableTypeError struct { diff --git a/runtime/sema/gen/main.go b/runtime/sema/gen/main.go index b69133a6f2..c7ab6036b2 100644 --- a/runtime/sema/gen/main.go +++ b/runtime/sema/gen/main.go @@ -2086,9 +2086,22 @@ func typeParameterExpr(name string, typeBound dst.Expr) dst.Expr { goKeyValue("Name", goStringLit(name)), } if typeBound != nil { + subtypeTypeBoundLit := &dst.CompositeLit{ + Type: &dst.Ident{ + Name: "SubtypeTypeBound", + Path: semaPath, + }, + Elts: []dst.Expr{ + goKeyValue("Type", typeBound), + }, + } + elements = append( elements, - goKeyValue("TypeBound", typeBound), + goKeyValue( + "TypeBound", + subtypeTypeBoundLit, + ), ) } diff --git a/runtime/sema/gen/testdata/functions/test.golden.go b/runtime/sema/gen/testdata/functions/test.golden.go index 8a1baee350..b0d593224d 100644 --- a/runtime/sema/gen/testdata/functions/test.golden.go +++ b/runtime/sema/gen/testdata/functions/test.golden.go @@ -117,9 +117,11 @@ const TestTypeTypeParamWithBoundFunctionName = "typeParamWithBound" var TestTypeTypeParamWithBoundFunctionTypeParameterT = &sema.TypeParameter{ Name: "T", - TypeBound: &sema.ReferenceType{ - Type: sema.AnyType, - Authorization: sema.UnauthorizedAccess, + TypeBound: sema.SubtypeTypeBound{ + Type: &sema.ReferenceType{ + Type: sema.AnyType, + Authorization: sema.UnauthorizedAccess, + }, }, } diff --git a/runtime/sema/type.go b/runtime/sema/type.go index f796ee79eb..fa5a297ac9 100644 --- a/runtime/sema/type.go +++ b/runtime/sema/type.go @@ -3393,31 +3393,30 @@ func (p Parameter) EffectiveArgumentLabel() string { // TypeParameter type TypeParameter struct { - TypeBound Type + TypeBound TypeBound Name string Optional bool } -func (p TypeParameter) string(typeFormatter func(Type) string) string { +func (p TypeParameter) String() string { var builder strings.Builder builder.WriteString(p.Name) if p.TypeBound != nil { - builder.WriteString(": ") - builder.WriteString(typeFormatter(p.TypeBound)) + // so as not to be confusing, existing subtype bounds will continue to be printed the normal way, + // and we only use special printing for the cases where there are more general bounds + if subtypeBound, isSubtypeBound := p.TypeBound.(SubtypeTypeBound); isSubtypeBound { + builder.WriteString(": ") + builder.WriteString(subtypeBound.Type.String()) + } else { + builder.WriteString(" ") + builder.WriteString(p.TypeBound.String()) + } } return builder.String() } -func (p TypeParameter) String() string { - return p.string(func(t Type) string { - return t.String() - }) -} - func (p TypeParameter) QualifiedString() string { - return p.string(func(t Type) string { - return t.QualifiedString() - }) + return p.String() } func (p TypeParameter) Equal(other *TypeParameter) bool { @@ -3442,17 +3441,17 @@ func (p TypeParameter) Equal(other *TypeParameter) bool { func (p TypeParameter) checkTypeBound(ty Type, memoryGauge common.MemoryGauge, typeRange ast.HasPosition) error { if p.TypeBound == nil || - p.TypeBound.IsInvalidType() || + p.TypeBound.HasInvalidType() || ty.IsInvalidType() { return nil } - if !IsSubType(ty, p.TypeBound) { - return &TypeMismatchError{ - ExpectedType: p.TypeBound, - ActualType: ty, - Range: ast.NewRangeFromPositioned(memoryGauge, typeRange), + if !p.TypeBound.Satisfies(ty) { + return &TypeBoundError{ + ExpectedTypeBound: p.TypeBound, + ActualType: ty, + Range: ast.NewRangeFromPositioned(memoryGauge, typeRange), } } @@ -3793,7 +3792,7 @@ func (t *FunctionType) IsInvalidType() bool { for _, typeParameter := range t.TypeParameters { if typeParameter.TypeBound != nil && - typeParameter.TypeBound.IsInvalidType() { + typeParameter.TypeBound.HasInvalidType() { return true } @@ -3867,17 +3866,17 @@ func (t *FunctionType) TypeAnnotationState() TypeAnnotationState { func (t *FunctionType) RewriteWithIntersectionTypes() (Type, bool) { anyRewritten := false - rewrittenTypeParameterTypeBounds := map[*TypeParameter]Type{} + rewrittenTypeParameterTypeBounds := map[*TypeParameter]TypeBound{} for _, typeParameter := range t.TypeParameters { if typeParameter.TypeBound == nil { continue } - rewrittenType, rewritten := typeParameter.TypeBound.RewriteWithIntersectionTypes() + rewrittenTypeBound, rewritten := typeParameter.TypeBound.RewriteWithIntersectionTypes() if rewritten { anyRewritten = true - rewrittenTypeParameterTypeBounds[typeParameter] = rewrittenType + rewrittenTypeParameterTypeBounds[typeParameter] = rewrittenTypeBound } } @@ -4253,24 +4252,30 @@ func baseTypeVariable(name string, ty Type) *Variable { // the values available in programs var BaseValueActivation = NewVariableActivation(nil) -var AllSignedFixedPointTypes = []Type{ +var AllLeafSignedFixedPointTypes = []Type{ Fix64Type, } -var AllUnsignedFixedPointTypes = []Type{ +var AllLeafUnsignedFixedPointTypes = []Type{ UFix64Type, } +var AllLeafFixedPointTypes = common.Concat( + AllLeafUnsignedFixedPointTypes, + AllLeafSignedFixedPointTypes, +) + +var AllNonLeafFixedPointTypes = []Type{ + FixedPointType, + SignedFixedPointType, +} + var AllFixedPointTypes = common.Concat( - AllUnsignedFixedPointTypes, - AllSignedFixedPointTypes, - []Type{ - FixedPointType, - SignedFixedPointType, - }, + AllLeafFixedPointTypes, + AllNonLeafFixedPointTypes, ) -var AllSignedIntegerTypes = []Type{ +var AllLeafSignedIntegerTypes = []Type{ IntType, Int8Type, Int16Type, @@ -4280,7 +4285,7 @@ var AllSignedIntegerTypes = []Type{ Int256Type, } -var AllFixedSizeUnsignedIntegerTypes = []Type{ +var AllLeafFixedSizeUnsignedIntegerTypes = []Type{ // UInt* UInt8Type, UInt16Type, @@ -4297,8 +4302,8 @@ var AllFixedSizeUnsignedIntegerTypes = []Type{ Word256Type, } -var AllUnsignedIntegerTypes = common.Concat( - AllFixedSizeUnsignedIntegerTypes, +var AllLeafUnsignedIntegerTypes = common.Concat( + AllLeafFixedSizeUnsignedIntegerTypes, []Type{ UIntType, }, @@ -4310,9 +4315,13 @@ var AllNonLeafIntegerTypes = []Type{ FixedSizeUnsignedIntegerType, } +var AllLeafIntegerTypes = common.Concat( + AllLeafUnsignedIntegerTypes, + AllLeafSignedIntegerTypes, +) + var AllIntegerTypes = common.Concat( - AllUnsignedIntegerTypes, - AllSignedIntegerTypes, + AllLeafIntegerTypes, AllNonLeafIntegerTypes, ) @@ -6687,15 +6696,9 @@ func (t *InclusiveRangeType) Instantiate( } 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), - }) - } + err := InclusiveRangeConstructorFunctionTypeParameter.checkTypeBound(memberType, memoryGauge, paramAstRange) + if err != nil { + report(err) } return &InclusiveRangeType{ @@ -6716,7 +6719,7 @@ func (t *InclusiveRangeType) CheckInstantiated(pos ast.HasPosition, memoryGauge var inclusiveRangeTypeParameter = &TypeParameter{ Name: "T", - TypeBound: IntegerType, + TypeBound: SubtypeTypeBound{Type: IntegerType}, } func (*InclusiveRangeType) TypeParameters() []*TypeParameter { @@ -6751,6 +6754,16 @@ const inclusiveRangeTypeContainsFunctionDocString = ` Returns true if the given integer is in the InclusiveRange sequence ` +var InclusiveRangeConstructorFunctionTypeParameter = &TypeParameter{ + Name: "T", + TypeBound: NewDisjunctionTypeBound([]TypeBound{ + NewEqualTypeBound(UIntType), + NewEqualTypeBound(IntType), + NewStrictSubtypeTypeBound(FixedSizeUnsignedIntegerType), + NewStrictSubtypeTypeBound(SignedIntegerType), + }), +} + func (t *InclusiveRangeType) GetMembers() map[string]MemberResolver { t.initializeMemberResolvers() return t.memberResolvers @@ -7426,7 +7439,7 @@ func IsProperSubType(subType Type, superType Type) bool { // the equality of the two types, so does NOT return a specific // value when the two types are equal or are not. // -// Consider using IsSubType or IsProperSubType +// Consider using IsSubType or IsStrictSubType func checkSubTypeWithoutEquality(subType Type, superType Type) bool { if subType == NeverType { @@ -8653,9 +8666,11 @@ func (t *CapabilityType) Resolve(typeArguments *TypeParameterTypeOrderedMap) Typ var capabilityTypeParameter = &TypeParameter{ Name: "T", - TypeBound: &ReferenceType{ - Type: AnyType, - Authorization: UnauthorizedAccess, + TypeBound: SubtypeTypeBound{ + Type: &ReferenceType{ + Type: AnyType, + Authorization: UnauthorizedAccess, + }, }, } diff --git a/runtime/sema/type_test.go b/runtime/sema/type_test.go index 3252a63e34..be011f20f8 100644 --- a/runtime/sema/type_test.go +++ b/runtime/sema/type_test.go @@ -2272,7 +2272,7 @@ func TestTypeInclusions(t *testing.T) { t.Run("SignedInteger", func(t *testing.T) { t.Parallel() - for _, typ := range AllSignedIntegerTypes { + for _, typ := range AllLeafSignedIntegerTypes { t.Run(typ.String(), func(t *testing.T) { assert.True(t, SignedIntegerTypeTag.ContainsAny(typ.Tag())) }) @@ -2282,7 +2282,7 @@ func TestTypeInclusions(t *testing.T) { t.Run("UnsignedInteger", func(t *testing.T) { t.Parallel() - for _, typ := range AllUnsignedIntegerTypes { + for _, typ := range AllLeafUnsignedIntegerTypes { t.Run(typ.String(), func(t *testing.T) { assert.True(t, UnsignedIntegerTypeTag.ContainsAny(typ.Tag())) }) @@ -2292,7 +2292,7 @@ func TestTypeInclusions(t *testing.T) { t.Run("FixedSizeUnsignedInteger", func(t *testing.T) { t.Parallel() - for _, typ := range AllFixedSizeUnsignedIntegerTypes { + for _, typ := range AllLeafFixedSizeUnsignedIntegerTypes { t.Run(typ.String(), func(t *testing.T) { assert.True(t, FixedSizeUnsignedIntegerTypeTag.ContainsAny(typ.Tag())) }) @@ -2312,7 +2312,7 @@ func TestTypeInclusions(t *testing.T) { t.Run("SignedFixedPoint", func(t *testing.T) { t.Parallel() - for _, typ := range AllSignedFixedPointTypes { + for _, typ := range AllLeafSignedFixedPointTypes { t.Run(typ.String(), func(t *testing.T) { assert.True(t, SignedFixedPointTypeTag.ContainsAny(typ.Tag())) }) @@ -2322,7 +2322,7 @@ func TestTypeInclusions(t *testing.T) { t.Run("UnsignedFixedPoint", func(t *testing.T) { t.Parallel() - for _, typ := range AllUnsignedFixedPointTypes { + for _, typ := range AllLeafUnsignedFixedPointTypes { t.Run(typ.String(), func(t *testing.T) { assert.True(t, UnsignedFixedPointTypeTag.ContainsAny(typ.Tag())) }) @@ -2538,10 +2538,13 @@ func TestMapType(t *testing.T) { t.Run("map function type", func(t *testing.T) { t.Parallel() + originalTypeParam := &TypeParameter{ - TypeBound: Int64Type, - Name: "X", - Optional: true, + TypeBound: SubtypeTypeBound{ + Type: Int64Type, + }, + Name: "X", + Optional: true, } original := NewSimpleFunctionType( FunctionPurityView, @@ -2566,9 +2569,11 @@ func TestMapType(t *testing.T) { original.TypeParameters = []*TypeParameter{originalTypeParam} mappedTypeParam := &TypeParameter{ - TypeBound: StringType, - Name: "X", - Optional: true, + TypeBound: SubtypeTypeBound{ + Type: StringType, + }, + Name: "X", + Optional: true, } mapped := NewSimpleFunctionType( FunctionPurityView, @@ -2600,7 +2605,10 @@ func TestMapType(t *testing.T) { require.Equal(t, mapped, outputFunction) require.IsType(t, &GenericType{}, outputFunction.Parameters[0].TypeAnnotation.Type) - require.True(t, outputFunction.Parameters[0].TypeAnnotation.Type.(*GenericType).TypeParameter == outputFunction.TypeParameters[0]) + require.Same(t, + outputFunction.Parameters[0].TypeAnnotation.Type.(*GenericType).TypeParameter, + outputFunction.TypeParameters[0], + ) }) } diff --git a/runtime/sema/typebound.go b/runtime/sema/typebound.go new file mode 100644 index 0000000000..423a221bb8 --- /dev/null +++ b/runtime/sema/typebound.go @@ -0,0 +1,568 @@ +/* +* 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 sema + +import ( + "fmt" + "strings" + + "github.com/onflow/cadence/runtime/ast" + "github.com/onflow/cadence/runtime/common" +) + +// TypeBound + +type TypeBound interface { + isTypeBound() + Satisfies(ty Type) bool + HasInvalidType() bool + Equal(TypeBound) bool + CheckInstantiated( + pos ast.HasPosition, + memoryGauge common.MemoryGauge, + report func(err error), + ) + Map( + gauge common.MemoryGauge, + typeParamMap map[*TypeParameter]*TypeParameter, + f func(Type) Type, + ) TypeBound + TypeAnnotationState() TypeAnnotationState + RewriteWithIntersectionTypes() (result TypeBound, rewritten bool) + + And(TypeBound) TypeBound + Or(TypeBound) TypeBound + Not() TypeBound + + withPrettyString(string) TypeBound + String() string +} + +// SubtypeTypeBound(T) expresses the requirement that +// ∀U, U <= T + +type SubtypeTypeBound struct { + Type Type +} + +var _ TypeBound = SubtypeTypeBound{} + +func NewSubtypeTypeBound(ty Type) TypeBound { + return SubtypeTypeBound{Type: ty} +} + +func (SubtypeTypeBound) isTypeBound() {} + +func (b SubtypeTypeBound) Satisfies(ty Type) bool { + return IsSubType(ty, b.Type) +} + +func (b SubtypeTypeBound) HasInvalidType() bool { + return b.Type.IsInvalidType() +} + +func (b SubtypeTypeBound) Equal(bound TypeBound) bool { + other, ok := bound.(SubtypeTypeBound) + if !ok { + return false + } + return b.Type.Equal(other.Type) +} + +func (b SubtypeTypeBound) CheckInstantiated( + pos ast.HasPosition, + memoryGauge common.MemoryGauge, + report func(err error), +) { + b.Type.CheckInstantiated(pos, memoryGauge, report) +} + +func (b SubtypeTypeBound) Map( + gauge common.MemoryGauge, + typeParamMap map[*TypeParameter]*TypeParameter, + f func(Type) Type, +) TypeBound { + return SubtypeTypeBound{ + Type: b.Type.Map(gauge, typeParamMap, f), + } +} + +func (b SubtypeTypeBound) TypeAnnotationState() TypeAnnotationState { + return b.Type.TypeAnnotationState() +} + +func (b SubtypeTypeBound) RewriteWithIntersectionTypes() (result TypeBound, rewritten bool) { + rewrittenType, rewritten := b.Type.RewriteWithIntersectionTypes() + if rewritten { + return SubtypeTypeBound{ + Type: rewrittenType, + }, true + } + return b, false +} + +func (b SubtypeTypeBound) And(bound TypeBound) TypeBound { + return NewConjunctionTypeBound([]TypeBound{b, bound}) +} + +func (b SubtypeTypeBound) Or(bound TypeBound) TypeBound { + return NewDisjunctionTypeBound([]TypeBound{b, bound}) +} + +func (b SubtypeTypeBound) Not() TypeBound { + return NewNegationTypeBound(b) +} + +func (b SubtypeTypeBound) String() string { + return fmt.Sprintf("<=: %s", b.Type.QualifiedString()) +} + +func (b SubtypeTypeBound) withPrettyString(_ string) TypeBound { + return b +} + +// SupertypeTypeBound(T) expresses the requirement that +// ∀U, U >= T + +type SupertypeTypeBound struct { + Type Type +} + +var _ TypeBound = SubtypeTypeBound{} + +func NewSupertypeTypeBound(ty Type) TypeBound { + return SupertypeTypeBound{Type: ty} +} + +func (SupertypeTypeBound) isTypeBound() {} + +func (b SupertypeTypeBound) Satisfies(ty Type) bool { + return IsSubType(b.Type, ty) +} + +func (b SupertypeTypeBound) HasInvalidType() bool { + return b.Type.IsInvalidType() +} + +func (b SupertypeTypeBound) Equal(bound TypeBound) bool { + other, ok := bound.(SupertypeTypeBound) + if !ok { + return false + } + return b.Type.Equal(other.Type) +} + +func (b SupertypeTypeBound) CheckInstantiated( + pos ast.HasPosition, + memoryGauge common.MemoryGauge, + report func(err error), +) { + b.Type.CheckInstantiated(pos, memoryGauge, report) +} + +func (b SupertypeTypeBound) Map( + gauge common.MemoryGauge, + typeParamMap map[*TypeParameter]*TypeParameter, + f func(Type) Type, +) TypeBound { + return SupertypeTypeBound{ + Type: b.Type.Map(gauge, typeParamMap, f), + } +} + +func (b SupertypeTypeBound) TypeAnnotationState() TypeAnnotationState { + return b.Type.TypeAnnotationState() +} + +func (b SupertypeTypeBound) RewriteWithIntersectionTypes() (result TypeBound, rewritten bool) { + rewrittenType, rewritten := b.Type.RewriteWithIntersectionTypes() + if rewritten { + return SupertypeTypeBound{ + Type: rewrittenType, + }, true + } + return b, false +} + +func (b SupertypeTypeBound) And(bound TypeBound) TypeBound { + return NewConjunctionTypeBound([]TypeBound{b, bound}) +} + +func (b SupertypeTypeBound) Or(bound TypeBound) TypeBound { + return NewDisjunctionTypeBound([]TypeBound{b, bound}) +} + +func (b SupertypeTypeBound) Not() TypeBound { + return NewNegationTypeBound(b) +} + +func (b SupertypeTypeBound) String() string { + return fmt.Sprintf(">=: %s", b.Type.QualifiedString()) +} + +func (b SupertypeTypeBound) withPrettyString(_ string) TypeBound { + return b +} + +// EqualTypeBound expresses the requirement that +// ∀U, U = T + +type EqualTypeBound struct { + Type Type +} + +var _ TypeBound = EqualTypeBound{} + +func NewEqualTypeBound(ty Type) TypeBound { + return EqualTypeBound{Type: ty} +} + +func (EqualTypeBound) isTypeBound() {} + +func (b EqualTypeBound) Satisfies(ty Type) bool { + return ty.Equal(b.Type) +} + +func (b EqualTypeBound) HasInvalidType() bool { + return b.Type.IsInvalidType() +} + +func (b EqualTypeBound) Equal(bound TypeBound) bool { + other, ok := bound.(EqualTypeBound) + if !ok { + return false + } + return b.Type.Equal(other.Type) +} + +func (b EqualTypeBound) CheckInstantiated( + pos ast.HasPosition, + memoryGauge common.MemoryGauge, + report func(err error), +) { + b.Type.CheckInstantiated(pos, memoryGauge, report) +} + +func (b EqualTypeBound) Map( + gauge common.MemoryGauge, + typeParamMap map[*TypeParameter]*TypeParameter, + f func(Type) Type, +) TypeBound { + return EqualTypeBound{ + Type: b.Type.Map(gauge, typeParamMap, f), + } +} + +func (b EqualTypeBound) TypeAnnotationState() TypeAnnotationState { + return b.Type.TypeAnnotationState() +} + +func (b EqualTypeBound) RewriteWithIntersectionTypes() (result TypeBound, rewritten bool) { + rewrittenType, rewritten := b.Type.RewriteWithIntersectionTypes() + if rewritten { + return EqualTypeBound{ + Type: rewrittenType, + }, true + } + return b, false +} + +func (b EqualTypeBound) And(bound TypeBound) TypeBound { + return NewConjunctionTypeBound([]TypeBound{b, bound}) +} + +func (b EqualTypeBound) Or(bound TypeBound) TypeBound { + return NewDisjunctionTypeBound([]TypeBound{b, bound}) +} + +func (b EqualTypeBound) Not() TypeBound { + return NewNegationTypeBound(b) +} + +func (b EqualTypeBound) String() string { + return fmt.Sprintf("= %s", b.Type.QualifiedString()) +} + +func (b EqualTypeBound) withPrettyString(_ string) TypeBound { + return b +} + +// NegationTypeBound(B) expresses the requirement that +// ∀U, !B(U) + +type NegationTypeBound struct { + NegatedBound TypeBound + prettyString *string +} + +var _ TypeBound = &NegationTypeBound{} + +func NewNegationTypeBound(bound TypeBound) TypeBound { + return NegationTypeBound{NegatedBound: bound} +} + +func (NegationTypeBound) isTypeBound() {} + +func (b NegationTypeBound) Satisfies(ty Type) bool { + return !b.NegatedBound.Satisfies(ty) +} + +func (b NegationTypeBound) HasInvalidType() bool { + return b.NegatedBound.HasInvalidType() +} + +func (b NegationTypeBound) Equal(bound TypeBound) bool { + other, ok := bound.(NegationTypeBound) + if !ok { + return false + } + return b.NegatedBound.Equal(other.NegatedBound) +} + +func (b NegationTypeBound) CheckInstantiated( + pos ast.HasPosition, + memoryGauge common.MemoryGauge, + report func(err error), +) { + b.NegatedBound.CheckInstantiated(pos, memoryGauge, report) +} + +func (b NegationTypeBound) Map( + gauge common.MemoryGauge, + typeParamMap map[*TypeParameter]*TypeParameter, + f func(Type) Type, +) TypeBound { + return &NegationTypeBound{ + NegatedBound: b.NegatedBound.Map(gauge, typeParamMap, f), + } +} + +func (b NegationTypeBound) TypeAnnotationState() TypeAnnotationState { + return b.NegatedBound.TypeAnnotationState() +} + +func (b NegationTypeBound) RewriteWithIntersectionTypes() (result TypeBound, rewritten bool) { + rewrittenBound, rewritten := b.NegatedBound.RewriteWithIntersectionTypes() + if rewritten { + return &NegationTypeBound{ + NegatedBound: rewrittenBound, + }, true + } + return b, false +} + +func (b NegationTypeBound) And(bound TypeBound) TypeBound { + return NewConjunctionTypeBound([]TypeBound{b, bound}) +} + +func (b NegationTypeBound) Or(bound TypeBound) TypeBound { + return NewDisjunctionTypeBound([]TypeBound{b, bound}) +} + +func (b NegationTypeBound) Not() TypeBound { + // !!b = b + return b.NegatedBound +} + +func (b NegationTypeBound) String() string { + if b.prettyString != nil { + return *b.prettyString + } + return fmt.Sprintf("!(%s)", b.NegatedBound.String()) +} + +func (b NegationTypeBound) withPrettyString(prettyString string) TypeBound { + b.prettyString = &prettyString + return b +} + +// ConjunctionTypeBound(B1, ..., Bn) expresses the requirement that +// ∀U, B1(U) & ... & Bn(U) + +type ConjunctionTypeBound struct { + TypeBounds []TypeBound + prettyString *string +} + +var _ TypeBound = ConjunctionTypeBound{} + +func NewConjunctionTypeBound(typeBounds []TypeBound) TypeBound { + return ConjunctionTypeBound{TypeBounds: typeBounds} +} + +func (ConjunctionTypeBound) isTypeBound() {} + +func (b ConjunctionTypeBound) Satisfies(ty Type) bool { + for _, typeBound := range b.TypeBounds { + if !typeBound.Satisfies(ty) { + return false + } + } + return true +} + +func (b ConjunctionTypeBound) HasInvalidType() bool { + for _, typeBound := range b.TypeBounds { + if typeBound.HasInvalidType() { + return true + } + } + return false +} + +func (b ConjunctionTypeBound) Equal(bound TypeBound) bool { + other, ok := bound.(ConjunctionTypeBound) + if !ok { + return false + } + + if len(b.TypeBounds) != len(other.TypeBounds) { + return false + } + + for i, typeBound := range b.TypeBounds { + otherTypeBound := other.TypeBounds[i] + if !typeBound.Equal(otherTypeBound) { + return false + } + } + + return true +} + +func (b ConjunctionTypeBound) CheckInstantiated( + pos ast.HasPosition, + memoryGauge common.MemoryGauge, + report func(err error), +) { + for _, typeBound := range b.TypeBounds { + typeBound.CheckInstantiated(pos, memoryGauge, report) + } +} + +func (b ConjunctionTypeBound) Map( + gauge common.MemoryGauge, + typeParamMap map[*TypeParameter]*TypeParameter, + f func(Type) Type, +) TypeBound { + newTypeBounds := make([]TypeBound, 0, len(b.TypeBounds)) + for _, typeBound := range b.TypeBounds { + newTypeBounds = append( + newTypeBounds, + typeBound.Map(gauge, typeParamMap, f), + ) + } + return ConjunctionTypeBound{ + TypeBounds: newTypeBounds, + } +} + +func (b ConjunctionTypeBound) TypeAnnotationState() TypeAnnotationState { + for _, typeBound := range b.TypeBounds { + state := typeBound.TypeAnnotationState() + if state != TypeAnnotationStateValid { + return state + } + } + return TypeAnnotationStateValid +} + +func (b ConjunctionTypeBound) RewriteWithIntersectionTypes() (result TypeBound, rewritten bool) { + rewrittenTypeBounds := make([]TypeBound, 0, len(b.TypeBounds)) + for _, typeBound := range b.TypeBounds { + rewrittenTypeBound, currentRewritten := typeBound.RewriteWithIntersectionTypes() + if currentRewritten { + rewritten = true + rewrittenTypeBounds = append(rewrittenTypeBounds, rewrittenTypeBound) + } else { + rewrittenTypeBounds = append(rewrittenTypeBounds, typeBound) + } + } + if rewritten { + return ConjunctionTypeBound{ + TypeBounds: rewrittenTypeBounds, + }, true + } else { + return b, false + } +} + +func (b ConjunctionTypeBound) And(bound TypeBound) TypeBound { + return NewConjunctionTypeBound(append(b.TypeBounds, bound)) +} + +func (b ConjunctionTypeBound) Or(bound TypeBound) TypeBound { + return NewDisjunctionTypeBound([]TypeBound{b, bound}) +} + +func (b ConjunctionTypeBound) Not() TypeBound { + return NewNegationTypeBound(b) +} + +func (b ConjunctionTypeBound) String() string { + if b.prettyString != nil { + return *b.prettyString + } + + var strs []string + for _, bound := range b.TypeBounds { + strs = append(strs, fmt.Sprintf("(%s)", bound.String())) + } + return strings.Join(strs[:], " && ") +} + +func (b ConjunctionTypeBound) withPrettyString(prettyString string) TypeBound { + b.prettyString = &prettyString + return b +} + +// Any other kinds of type bounds we might wish to express can be +// written as the composition of `<=`, `>=`, `=`, `!` and `&&`. Technically, `=` is not +// really even necessary, as `U = T` is equivalent to `U <= T && U >= T`, but for +// performance reasons we give it its own basic bound. + +// `U <= T && !(T = U) ==> U < T` +func NewStrictSubtypeTypeBound(ty Type) TypeBound { + return NewEqualTypeBound(ty). + Not(). + And(NewSubtypeTypeBound(ty)). + withPrettyString(fmt.Sprintf("<: %s", ty.String())) +} + +// `U >= T && !(T = U) ==> U > T` +func NewStrictSupertypeTypeBound(ty Type) TypeBound { + return NewEqualTypeBound(ty). + Not(). + And(NewSupertypeTypeBound(ty)). + withPrettyString(fmt.Sprintf(">: %s", ty.String())) +} + +// `!(!B1 && ... && !Bn) ==> B1 || ... || Bn` +func NewDisjunctionTypeBound(typeBounds []TypeBound) TypeBound { + var negatedTypeBounds []TypeBound + var strs []string + for _, bound := range typeBounds { + negatedTypeBounds = append(negatedTypeBounds, bound.Not()) + strs = append(strs, fmt.Sprintf("(%s)", bound.String())) + } + + return NewConjunctionTypeBound(negatedTypeBounds). + Not(). + withPrettyString(strings.Join(strs[:], " || ")) +} diff --git a/runtime/sema/typebound_test.go b/runtime/sema/typebound_test.go new file mode 100644 index 0000000000..0463fee1dc --- /dev/null +++ b/runtime/sema/typebound_test.go @@ -0,0 +1,317 @@ +/* + * 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 sema + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestTypeBound_Satisfies(t *testing.T) { + t.Parallel() + + t.Run("subtype", func(t *testing.T) { + + t.Parallel() + + typeBound := NewSubtypeTypeBound(IntegerType) + + assert.True(t, typeBound.Satisfies(IntegerType)) + assert.True(t, typeBound.Satisfies(NeverType)) + assert.False(t, typeBound.Satisfies(StringType)) + + for _, integerType := range AllLeafIntegerTypes { + assert.True(t, typeBound.Satisfies(integerType)) + } + }) + + t.Run("strict subtype", func(t *testing.T) { + + t.Parallel() + + typeBound := NewStrictSubtypeTypeBound(IntegerType) + + assert.False(t, typeBound.Satisfies(IntegerType)) + assert.True(t, typeBound.Satisfies(NeverType)) + assert.False(t, typeBound.Satisfies(StringType)) + + for _, integerType := range AllLeafIntegerTypes { + assert.Truef(t, typeBound.Satisfies(integerType), "%s should satisfy", integerType) + } + }) + + t.Run("supertype", func(t *testing.T) { + + t.Parallel() + + typeBound := NewSupertypeTypeBound(NeverType) + intTypeBound := NewStrictSupertypeTypeBound(IntType) + + assert.True(t, typeBound.Satisfies(NeverType)) + assert.True(t, typeBound.Satisfies(IntegerType)) + assert.False(t, intTypeBound.Satisfies(StringType)) + + for _, integerType := range AllLeafIntegerTypes { + assert.True(t, typeBound.Satisfies(integerType)) + } + }) + + t.Run("strict supertype", func(t *testing.T) { + + t.Parallel() + + typeBound := NewStrictSupertypeTypeBound(NeverType) + intTypeBound := NewStrictSupertypeTypeBound(IntType) + + assert.False(t, typeBound.Satisfies(NeverType)) + assert.True(t, typeBound.Satisfies(IntegerType)) + assert.False(t, intTypeBound.Satisfies(StringType)) + + for _, integerType := range AllLeafIntegerTypes { + assert.True(t, typeBound.Satisfies(integerType)) + } + }) + + t.Run("conjunction", func(t *testing.T) { + + t.Parallel() + + typeBound := NewConjunctionTypeBound([]TypeBound{ + NewStrictSupertypeTypeBound(NeverType), + NewStrictSubtypeTypeBound(FixedSizeUnsignedIntegerType), + }) + + assert.False(t, typeBound.Satisfies(FixedSizeUnsignedIntegerType)) + assert.False(t, typeBound.Satisfies(NeverType)) + assert.False(t, typeBound.Satisfies(StringType)) + + for _, integerType := range AllLeafFixedSizeUnsignedIntegerTypes { + assert.True(t, typeBound.Satisfies(integerType)) + } + }) + + t.Run("vacuous disjunction", func(t *testing.T) { + + t.Parallel() + + typeBound := NewDisjunctionTypeBound([]TypeBound{ + NewStrictSupertypeTypeBound(NeverType), + NewStrictSubtypeTypeBound(NeverType), + }) + + assert.True(t, typeBound.Satisfies(FixedSizeUnsignedIntegerType)) + assert.True(t, typeBound.Satisfies(AnyStructType)) + assert.True(t, typeBound.Satisfies(StringType)) + assert.False(t, typeBound.Satisfies(NeverType)) + + for _, integerType := range AllLeafFixedSizeUnsignedIntegerTypes { + assert.True(t, typeBound.Satisfies(integerType)) + } + }) + + t.Run("disjunction", func(t *testing.T) { + + t.Parallel() + + typeBound := NewDisjunctionTypeBound([]TypeBound{ + NewStrictSupertypeTypeBound(StringType), + NewSupertypeTypeBound(IntType), + }) + + assert.False(t, typeBound.Satisfies(FixedSizeUnsignedIntegerType)) + assert.True(t, typeBound.Satisfies(IntegerType)) + assert.True(t, typeBound.Satisfies(AnyStructType)) + assert.True(t, typeBound.Satisfies(IntType)) + assert.False(t, typeBound.Satisfies(StringType)) + assert.False(t, typeBound.Satisfies(NeverType)) + }) +} + +func TestTypeBoundSerialization(t *testing.T) { + t.Parallel() + + type testCase struct { + name string + bound TypeBound + expected string + } + + tests := []testCase{ + { + name: "basic subtype", + bound: NewSubtypeTypeBound(IntType), + expected: "<=: Int", + }, + { + name: "basic equal", + bound: NewEqualTypeBound(IntType), + expected: "= Int", + }, + { + name: "basic not subtype", + bound: NewSubtypeTypeBound(IntType).Not(), + expected: "!(<=: Int)", + }, + { + name: "basic not equal", + bound: NewEqualTypeBound(IntType).Not(), + expected: "!(= Int)", + }, + { + name: "basic conjunction", + bound: NewSubtypeTypeBound(IntType).And(NewEqualTypeBound(StringType)), + expected: "(<=: Int) && (= String)", + }, + { + name: "conjunction of negations", + bound: NewSubtypeTypeBound(IntType).Not().And(NewEqualTypeBound(StringType).Not()), + expected: "(!(<=: Int)) && (!(= String))", + }, + { + name: "flattened double negative", + bound: NewSubtypeTypeBound(IntType).Not().Not(), + expected: "<=: Int", + }, + { + name: "strict subtype", + bound: NewStrictSubtypeTypeBound(IntType), + expected: "<: Int", + }, + { + name: "strict supertype", + bound: NewStrictSupertypeTypeBound(IntType), + expected: ">: Int", + }, + { + name: "supertype", + bound: NewSupertypeTypeBound(IntType), + expected: ">=: Int", + }, + { + name: "basic disjunction", + bound: NewSubtypeTypeBound(IntType).Or(NewEqualTypeBound(StringType)), + expected: "(<=: Int) || (= String)", + }, + { + name: "triple conjunction", + bound: NewSubtypeTypeBound(IntType). + And(NewEqualTypeBound(StringType)). + And(NewStrictSupertypeTypeBound(BoolType)), + expected: "(<=: Int) && (= String) && (>: Bool)", + }, + { + name: "Capabilities.get", + bound: Account_CapabilitiesTypeGetFunctionTypeParameterT.TypeBound, + expected: "(<=: &Any) && (>: Never)", + }, + { + name: "InclusiveRange", + bound: InclusiveRangeConstructorFunctionTypeParameter.TypeBound, + expected: "(= UInt) || (= Int) || (<: FixedSizeUnsignedInteger) || (<: SignedInteger)", + }, + { + name: "triple disjunction", + bound: NewSubtypeTypeBound(IntType). + Or(NewEqualTypeBound(StringType)). + Or(NewStrictSupertypeTypeBound(BoolType)), + expected: "((<=: Int) || (= String)) || (>: Bool)", + }, + { + name: "conjunction of disjunctions", + bound: NewSubtypeTypeBound(IntType). + Or(NewEqualTypeBound(StringType)). + And( + NewStrictSupertypeTypeBound(BoolType). + Or(NewSupertypeTypeBound(AnyStructType)), + ), + expected: "((<=: Int) || (= String)) && ((>: Bool) || (>=: AnyStruct))", + }, + } + + test := func(test testCase) { + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + require.Equal(t, test.bound.String(), test.expected) + }) + } + + for _, testCase := range tests { + test(testCase) + } +} + +func TestTypeBound_HasInvalid(t *testing.T) { + t.Parallel() + + t.Run("subtype", func(t *testing.T) { + + t.Parallel() + + assert.False(t, NewSubtypeTypeBound(IntegerType).HasInvalidType()) + assert.True(t, NewSubtypeTypeBound(InvalidType).HasInvalidType()) + }) + + t.Run("strict subtype", func(t *testing.T) { + + t.Parallel() + + assert.False(t, NewStrictSubtypeTypeBound(IntegerType).HasInvalidType()) + assert.True(t, NewStrictSubtypeTypeBound(InvalidType).HasInvalidType()) + }) + + t.Run("supertype", func(t *testing.T) { + + t.Parallel() + + assert.False(t, NewSupertypeTypeBound(IntegerType).HasInvalidType()) + assert.True(t, NewSupertypeTypeBound(InvalidType).HasInvalidType()) + }) + + t.Run("strict supertype", func(t *testing.T) { + + t.Parallel() + + assert.False(t, NewStrictSupertypeTypeBound(IntegerType).HasInvalidType()) + assert.True(t, NewStrictSupertypeTypeBound(InvalidType).HasInvalidType()) + }) + + t.Run("conjunction", func(t *testing.T) { + + t.Parallel() + + assert.False(t, + (&ConjunctionTypeBound{ + TypeBounds: []TypeBound{ + SubtypeTypeBound{Type: IntegerType}, + }, + }).HasInvalidType(), + ) + + assert.True(t, + (&ConjunctionTypeBound{ + TypeBounds: []TypeBound{ + SubtypeTypeBound{Type: InvalidType}, + }, + }).HasInvalidType(), + ) + }) +} diff --git a/runtime/stdlib/account.go b/runtime/stdlib/account.go index 5e71bd8945..3520822661 100644 --- a/runtime/stdlib/account.go +++ b/runtime/stdlib/account.go @@ -187,7 +187,9 @@ var getAuthAccountFunctionType = func() *sema.FunctionType { typeParam := &sema.TypeParameter{ Name: "T", - TypeBound: sema.AccountReferenceType, + TypeBound: sema.SubtypeTypeBound{ + Type: sema.AccountReferenceType, + }, } return &sema.FunctionType{ diff --git a/runtime/stdlib/random.go b/runtime/stdlib/random.go index 10c3d42389..a090f2b7eb 100644 --- a/runtime/stdlib/random.go +++ b/runtime/stdlib/random.go @@ -20,10 +20,8 @@ package stdlib import ( "encoding/binary" - "fmt" "math/big" - "github.com/onflow/cadence/runtime/ast" "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/errors" "github.com/onflow/cadence/runtime/interpreter" @@ -42,8 +40,9 @@ const revertibleRandomFunctionName = "revertibleRandom" var revertibleRandomFunctionType = func() *sema.FunctionType { typeParameter := &sema.TypeParameter{ - Name: "T", - TypeBound: sema.FixedSizeUnsignedIntegerType, + Name: "T", + TypeBound: sema.NewStrictSubtypeTypeBound(sema.FixedSizeUnsignedIntegerType). + And(sema.NewStrictSupertypeTypeBound(sema.NeverType)), } typeAnnotation := sema.NewTypeAnnotation( @@ -62,29 +61,6 @@ var revertibleRandomFunctionType = func() *sema.FunctionType { TypeAnnotation: typeAnnotation, }, }, - TypeArgumentsCheck: func( - memoryGauge common.MemoryGauge, - typeArguments *sema.TypeParameterTypeOrderedMap, - _ []*ast.TypeAnnotation, - invocationRange ast.HasPosition, - report func(err error)) { - typeArg, ok := typeArguments.Get(typeParameter) - if !ok || typeArg == nil { - // checker should prevent this - panic(errors.NewUnreachableError()) - } - if typeArg == sema.NeverType || typeArg == sema.FixedSizeUnsignedIntegerType { - report(&sema.InvalidTypeArgumentError{ - TypeArgumentName: typeParameter.Name, - Range: ast.NewRangeFromPositioned(memoryGauge, invocationRange), - Details: fmt.Sprintf( - "Type argument for `%s` cannot be `%s`", - revertibleRandomFunctionName, - typeArg, - ), - }) - } - }, ReturnTypeAnnotation: typeAnnotation, // `modulo` parameter is optional Arity: &sema.Arity{Min: 0, Max: 1}, diff --git a/runtime/stdlib/random_test.go b/runtime/stdlib/random_test.go index 1f2c0af529..587429a6fd 100644 --- a/runtime/stdlib/random_test.go +++ b/runtime/stdlib/random_test.go @@ -54,7 +54,7 @@ func TestRandomBasicUniformityWithModulo(t *testing.T) { } testTypes := func(t *testing.T, testType func(*testing.T, sema.Type)) { - for _, ty := range sema.AllFixedSizeUnsignedIntegerTypes { + for _, ty := range sema.AllLeafFixedSizeUnsignedIntegerTypes { tyCopy := ty t.Run(ty.String(), func(t *testing.T) { t.Parallel() diff --git a/runtime/stdlib/range.go b/runtime/stdlib/range.go index b4339116c1..26cf227981 100644 --- a/runtime/stdlib/range.go +++ b/runtime/stdlib/range.go @@ -21,8 +21,6 @@ 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" @@ -37,21 +35,17 @@ const inclusiveRangeConstructorFunctionDocString = ` 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, - } +var InclusiveRangeConstructorFunctionType = func() *sema.FunctionType { typeAnnotation := sema.NewTypeAnnotation( &sema.GenericType{ - TypeParameter: typeParameter, + TypeParameter: sema.InclusiveRangeConstructorFunctionTypeParameter, }, ) return &sema.FunctionType{ TypeParameters: []*sema.TypeParameter{ - typeParameter, + sema.InclusiveRangeConstructorFunctionTypeParameter, }, Parameters: []sema.Parameter{ { @@ -76,46 +70,12 @@ var inclusiveRangeConstructorFunctionType = func() *sema.FunctionType { ), // `step` parameter is optional Arity: &sema.Arity{Min: 2, Max: 3}, - TypeArgumentsCheck: func( - memoryGauge common.MemoryGauge, - typeArguments *sema.TypeParameterTypeOrderedMap, - astTypeArguments []*ast.TypeAnnotation, - invocationRange ast.HasPosition, - report func(error), - ) { - memberType, ok := typeArguments.Get(typeParameter) - if !ok || memberType == nil { - // checker should prevent this - panic(errors.NewUnreachableError()) - } - - // memberType must only be a leaf integer type. - for _, ty := range sema.AllNonLeafIntegerTypes { - if memberType != ty { - continue - } - - // If type argument was provided, use its range otherwise fallback to invocation range. - errorRange := invocationRange - if len(astTypeArguments) > 0 { - errorRange = astTypeArguments[0] - } - - report(&sema.InvalidTypeArgumentError{ - TypeArgumentName: typeParameter.Name, - Range: ast.NewRangeFromPositioned(memoryGauge, errorRange), - Details: fmt.Sprintf("Creation of InclusiveRange<%s> is disallowed", memberType), - }) - - break - } - }, } }() var InclusiveRangeConstructorFunction = NewStandardLibraryFunction( "InclusiveRange", - inclusiveRangeConstructorFunctionType, + InclusiveRangeConstructorFunctionType, inclusiveRangeConstructorFunctionDocString, func(invocation interpreter.Invocation) interpreter.Value { start, startOk := invocation.Arguments[0].(interpreter.IntegerValue) diff --git a/runtime/stdlib/test_contract.go b/runtime/stdlib/test_contract.go index 3e8b17c5f2..b5fd748ab3 100644 --- a/runtime/stdlib/test_contract.go +++ b/runtime/stdlib/test_contract.go @@ -233,9 +233,11 @@ const testTypeExpectFunctionName = "expect" func newTestTypeExpectFunctionType(matcherType *sema.CompositeType) *sema.FunctionType { typeParameter := &sema.TypeParameter{ - TypeBound: sema.AnyStructType, - Name: "T", - Optional: true, + TypeBound: sema.SubtypeTypeBound{ + Type: sema.AnyStructType, + }, + Name: "T", + Optional: true, } return &sema.FunctionType{ @@ -399,9 +401,11 @@ const testTypeNewMatcherFunctionName = "newMatcher" func newTestTypeNewMatcherFunctionType(matcherType *sema.CompositeType) *sema.FunctionType { typeParameter := &sema.TypeParameter{ - TypeBound: sema.AnyStructType, - Name: "T", - Optional: true, + TypeBound: sema.SubtypeTypeBound{ + Type: sema.AnyStructType, + }, + Name: "T", + Optional: true, } return &sema.FunctionType{ @@ -467,9 +471,11 @@ Returns a matcher that succeeds if the tested value is equal to the given value. func newTestTypeEqualFunctionType(matcherType *sema.CompositeType) *sema.FunctionType { typeParameter := &sema.TypeParameter{ - TypeBound: sema.AnyStructType, - Name: "T", - Optional: true, + TypeBound: sema.SubtypeTypeBound{ + Type: sema.AnyStructType, + }, + Name: "T", + Optional: true, } return &sema.FunctionType{ diff --git a/runtime/stdlib/test_test.go b/runtime/stdlib/test_test.go index ce51d534e1..9ab42d8edc 100644 --- a/runtime/stdlib/test_test.go +++ b/runtime/stdlib/test_test.go @@ -289,7 +289,7 @@ func TestTestNewMatcher(t *testing.T) { _, err := newTestContractInterpreter(t, script) errs := checker.RequireCheckerErrors(t, err, 1) - assert.IsType(t, &sema.TypeMismatchError{}, errs[0]) + assert.IsType(t, &sema.TypeBoundError{}, errs[0]) }) t.Run("custom matcher with explicit type", func(t *testing.T) { @@ -462,7 +462,7 @@ func TestTestEqualMatcher(t *testing.T) { require.Error(t, err) errs := checker.RequireCheckerErrors(t, err, 2) - assert.IsType(t, &sema.TypeMismatchError{}, errs[0]) + assert.IsType(t, &sema.TypeBoundError{}, errs[0]) assert.IsType(t, &sema.TypeMismatchError{}, errs[1]) }) @@ -672,8 +672,8 @@ func TestTestEqualMatcher(t *testing.T) { _, err := newTestContractInterpreter(t, script) errs := checker.RequireCheckerErrors(t, err, 4) - assert.IsType(t, &sema.TypeMismatchError{}, errs[0]) - assert.IsType(t, &sema.TypeMismatchError{}, errs[1]) + assert.IsType(t, &sema.TypeBoundError{}, errs[0]) + assert.IsType(t, &sema.TypeBoundError{}, errs[1]) assert.IsType(t, &sema.TypeMismatchError{}, errs[2]) assert.IsType(t, &sema.TypeMismatchError{}, errs[3]) }) @@ -706,8 +706,8 @@ func TestTestEqualMatcher(t *testing.T) { _, err := newTestContractInterpreter(t, script) errs := checker.RequireCheckerErrors(t, err, 3) - assert.IsType(t, &sema.TypeMismatchError{}, errs[0]) - assert.IsType(t, &sema.TypeMismatchError{}, errs[1]) + assert.IsType(t, &sema.TypeBoundError{}, errs[0]) + assert.IsType(t, &sema.TypeBoundError{}, errs[1]) assert.IsType(t, &sema.TypeMismatchError{}, errs[2]) }) } @@ -1956,8 +1956,8 @@ func TestTestExpect(t *testing.T) { _, err := newTestContractInterpreter(t, script) errs := checker.RequireCheckerErrors(t, err, 2) - assert.IsType(t, &sema.TypeMismatchError{}, errs[0]) - assert.IsType(t, &sema.TypeMismatchError{}, errs[1]) + assert.IsType(t, &sema.TypeBoundError{}, errs[0]) + assert.IsType(t, &sema.TypeBoundError{}, errs[1]) }) t.Run("resource with struct matcher", func(t *testing.T) { @@ -1982,7 +1982,7 @@ func TestTestExpect(t *testing.T) { _, err := newTestContractInterpreter(t, script) errs := checker.RequireCheckerErrors(t, err, 1) - assert.IsType(t, &sema.TypeMismatchError{}, errs[0]) + assert.IsType(t, &sema.TypeBoundError{}, errs[0]) }) t.Run("struct with resource matcher", func(t *testing.T) { @@ -2007,7 +2007,7 @@ func TestTestExpect(t *testing.T) { _, err := newTestContractInterpreter(t, script) errs := checker.RequireCheckerErrors(t, err, 1) - assert.IsType(t, &sema.TypeMismatchError{}, errs[0]) + assert.IsType(t, &sema.TypeBoundError{}, errs[0]) }) } diff --git a/runtime/tests/checker/account_test.go b/runtime/tests/checker/account_test.go index ab1ce04d91..96dd68e037 100644 --- a/runtime/tests/checker/account_test.go +++ b/runtime/tests/checker/account_test.go @@ -300,11 +300,11 @@ func TestCheckAccountStorageSave(t *testing.T) { if domain == common.PathDomainStorage { errs := RequireCheckerErrors(t, err, 1) - require.IsType(t, &sema.TypeMismatchError{}, errs[0]) + require.IsType(t, &sema.TypeBoundError{}, errs[0]) } else { errs := RequireCheckerErrors(t, err, 2) - require.IsType(t, &sema.TypeMismatchError{}, errs[0]) + require.IsType(t, &sema.TypeBoundError{}, errs[0]) require.IsType(t, &sema.TypeMismatchError{}, errs[1]) } }) @@ -331,11 +331,11 @@ func TestCheckAccountStorageSave(t *testing.T) { if domain == common.PathDomainStorage { errs := RequireCheckerErrors(t, err, 1) - require.IsType(t, &sema.TypeMismatchError{}, errs[0]) + require.IsType(t, &sema.TypeBoundError{}, errs[0]) } else { errs := RequireCheckerErrors(t, err, 2) - require.IsType(t, &sema.TypeMismatchError{}, errs[0]) + require.IsType(t, &sema.TypeBoundError{}, errs[0]) require.IsType(t, &sema.TypeMismatchError{}, errs[1]) } }) @@ -624,12 +624,12 @@ func TestCheckAccountStorageCopy(t *testing.T) { if domain == common.PathDomainStorage { errs := RequireCheckerErrors(t, err, 1) - require.IsType(t, &sema.TypeMismatchError{}, errs[0]) + require.IsType(t, &sema.TypeBoundError{}, errs[0]) } else { errs := RequireCheckerErrors(t, err, 2) - require.IsType(t, &sema.TypeMismatchError{}, errs[0]) + require.IsType(t, &sema.TypeBoundError{}, errs[0]) require.IsType(t, &sema.TypeMismatchError{}, errs[1]) } }) @@ -839,11 +839,11 @@ func TestCheckAccountStorageBorrow(t *testing.T) { errs := RequireCheckerErrors(t, err, 1) - require.IsType(t, &sema.TypeMismatchError{}, errs[0]) + require.IsType(t, &sema.TypeBoundError{}, errs[0]) } else { errs := RequireCheckerErrors(t, err, 2) - require.IsType(t, &sema.TypeMismatchError{}, errs[0]) + require.IsType(t, &sema.TypeBoundError{}, errs[0]) require.IsType(t, &sema.TypeMismatchError{}, errs[1]) } }) @@ -869,11 +869,11 @@ func TestCheckAccountStorageBorrow(t *testing.T) { errs := RequireCheckerErrors(t, err, 1) - require.IsType(t, &sema.TypeMismatchError{}, errs[0]) + require.IsType(t, &sema.TypeBoundError{}, errs[0]) } else { errs := RequireCheckerErrors(t, err, 2) - require.IsType(t, &sema.TypeMismatchError{}, errs[0]) + require.IsType(t, &sema.TypeBoundError{}, errs[0]) require.IsType(t, &sema.TypeMismatchError{}, errs[1]) } }) @@ -1739,8 +1739,8 @@ func TestCheckAccountCapabilities(t *testing.T) { `) errs := RequireCheckerErrors(t, err, 1) - require.IsType(t, &sema.InvalidTypeArgumentError{}, errs[0]) - require.Equal(t, errs[0].(errors.SecondaryError).SecondaryError(), "Type argument for `get` cannot be `Never`") + require.IsType(t, &sema.TypeBoundError{}, errs[0]) + require.Equal(t, errs[0].(errors.SecondaryError).SecondaryError(), "expected type satisfying (<=: &Any) && (>: Never), got `Never`") }) } diff --git a/runtime/tests/checker/builtinfunctions_test.go b/runtime/tests/checker/builtinfunctions_test.go index 80203e15a3..4d2454603b 100644 --- a/runtime/tests/checker/builtinfunctions_test.go +++ b/runtime/tests/checker/builtinfunctions_test.go @@ -333,15 +333,9 @@ func TestCheckRevertibleRandom(t *testing.T) { }) } - for _, ty := range sema.AllFixedSizeUnsignedIntegerTypes { - switch ty { - case sema.FixedSizeUnsignedIntegerType: - continue - - default: - runValidCaseWithoutModulo(t, ty) - runValidCaseWithModulo(t, ty) - } + for _, ty := range sema.AllLeafFixedSizeUnsignedIntegerTypes { + runValidCaseWithoutModulo(t, ty) + runValidCaseWithModulo(t, ty) } runInvalidCase( @@ -349,7 +343,7 @@ func TestCheckRevertibleRandom(t *testing.T) { "revertibleRandom", "let rand = revertibleRandom()", []error{ - &sema.TypeMismatchError{}, + &sema.TypeBoundError{}, }, ) @@ -358,7 +352,7 @@ func TestCheckRevertibleRandom(t *testing.T) { "revertibleRandom", `let rand = revertibleRandom(modulo: "abcd")`, []error{ - &sema.TypeMismatchError{}, + &sema.TypeBoundError{}, }, ) @@ -412,8 +406,8 @@ func TestCheckRevertibleRandom(t *testing.T) { "invalid type arg never", `let rand = revertibleRandom(modulo: 1)`, []error{ + &sema.TypeBoundError{}, &sema.TypeMismatchError{}, - &sema.InvalidTypeArgumentError{}, }, ) runInvalidCase( @@ -421,7 +415,7 @@ func TestCheckRevertibleRandom(t *testing.T) { "invalid type arg FixedSizeUnsignedInteger", `let rand = revertibleRandom(modulo: 1)`, []error{ - &sema.InvalidTypeArgumentError{}, + &sema.TypeBoundError{}, }, ) diff --git a/runtime/tests/checker/capability_test.go b/runtime/tests/checker/capability_test.go index 35b6fc5b73..146aed9e5f 100644 --- a/runtime/tests/checker/capability_test.go +++ b/runtime/tests/checker/capability_test.go @@ -278,7 +278,7 @@ func TestCheckCapability_borrow(t *testing.T) { errs := RequireCheckerErrors(t, err, 1) - require.IsType(t, &sema.TypeMismatchError{}, errs[0]) + require.IsType(t, &sema.TypeBoundError{}, errs[0]) }) t.Run("struct", func(t *testing.T) { @@ -294,7 +294,7 @@ func TestCheckCapability_borrow(t *testing.T) { errs := RequireCheckerErrors(t, err, 1) - require.IsType(t, &sema.TypeMismatchError{}, errs[0]) + require.IsType(t, &sema.TypeBoundError{}, errs[0]) }) }) } @@ -456,7 +456,7 @@ func TestCheckCapability_check(t *testing.T) { errs := RequireCheckerErrors(t, err, 1) - require.IsType(t, &sema.TypeMismatchError{}, errs[0]) + require.IsType(t, &sema.TypeBoundError{}, errs[0]) }) t.Run("struct", func(t *testing.T) { @@ -472,7 +472,7 @@ func TestCheckCapability_check(t *testing.T) { errs := RequireCheckerErrors(t, err, 1) - require.IsType(t, &sema.TypeMismatchError{}, errs[0]) + require.IsType(t, &sema.TypeBoundError{}, errs[0]) }) }) } diff --git a/runtime/tests/checker/entitlements_test.go b/runtime/tests/checker/entitlements_test.go index 7067895fc4..c8259f938b 100644 --- a/runtime/tests/checker/entitlements_test.go +++ b/runtime/tests/checker/entitlements_test.go @@ -3284,7 +3284,7 @@ func TestCheckEntitlementTypeAnnotation(t *testing.T) { require.IsType(t, &sema.DirectEntitlementAnnotationError{}, errs[0]) // entitlements are not storable either - require.IsType(t, &sema.TypeMismatchError{}, errs[1]) + require.IsType(t, &sema.TypeBoundError{}, errs[1]) }) t.Run("intersection", func(t *testing.T) { @@ -3526,7 +3526,7 @@ func TestCheckEntitlementMappingTypeAnnotation(t *testing.T) { require.IsType(t, &sema.DirectEntitlementAnnotationError{}, errs[0]) // entitlements are not storable either - require.IsType(t, &sema.TypeMismatchError{}, errs[1]) + require.IsType(t, &sema.TypeBoundError{}, errs[1]) }) t.Run("intersection", func(t *testing.T) { diff --git a/runtime/tests/checker/fixedpoint_test.go b/runtime/tests/checker/fixedpoint_test.go index 18d1be7708..9605922162 100644 --- a/runtime/tests/checker/fixedpoint_test.go +++ b/runtime/tests/checker/fixedpoint_test.go @@ -122,12 +122,7 @@ func TestCheckFixedPointLiteralRanges(t *testing.T) { return RequireGlobalValue(t, checker.Elaboration, "x") } - for _, ty := range sema.AllFixedPointTypes { - // Only test leaf types - switch ty { - case sema.FixedPointType, sema.SignedFixedPointType: - continue - } + for _, ty := range sema.AllLeafFixedPointTypes { t.Run(ty.String(), func(t *testing.T) { @@ -553,7 +548,7 @@ func TestCheckSignedFixedPointNegate(t *testing.T) { t.Parallel() - for _, ty := range sema.AllSignedFixedPointTypes { + for _, ty := range sema.AllLeafSignedFixedPointTypes { name := ty.String() t.Run(name, func(t *testing.T) { @@ -576,7 +571,7 @@ func TestCheckInvalidUnsignedFixedPointNegate(t *testing.T) { t.Parallel() - for _, ty := range sema.AllUnsignedFixedPointTypes { + for _, ty := range sema.AllLeafUnsignedFixedPointTypes { t.Run(ty.String(), func(t *testing.T) { @@ -600,7 +595,7 @@ func TestCheckInvalidNegativeZeroUnsignedFixedPoint(t *testing.T) { t.Parallel() - for _, ty := range sema.AllUnsignedFixedPointTypes { + for _, ty := range sema.AllLeafUnsignedFixedPointTypes { t.Run(ty.String(), func(t *testing.T) { @@ -623,12 +618,7 @@ func TestCheckFixedPointLiteralScales(t *testing.T) { t.Parallel() - for _, ty := range sema.AllFixedPointTypes { - // Only test leaf types - switch ty { - case sema.FixedPointType, sema.SignedFixedPointType: - continue - } + for _, ty := range sema.AllLeafFixedPointTypes { t.Run(ty.String(), func(t *testing.T) { @@ -697,12 +687,8 @@ func TestCheckFixedPointMinMax(t *testing.T) { ) } - for _, ty := range sema.AllFixedPointTypes { - // Only test leaf types - switch ty { - case sema.FixedPointType, sema.SignedFixedPointType: - continue - } + for _, ty := range sema.AllLeafFixedPointTypes { + t.Run(ty.String(), func(t *testing.T) { test(t, ty) diff --git a/runtime/tests/checker/for_test.go b/runtime/tests/checker/for_test.go index 04a5294e3c..c377264e42 100644 --- a/runtime/tests/checker/for_test.go +++ b/runtime/tests/checker/for_test.go @@ -120,15 +120,7 @@ func TestCheckForInclusiveRange(t *testing.T) { }) } - for _, typ := range sema.AllIntegerTypes { - // Only test leaf integer types - switch typ { - case sema.IntegerType, - sema.SignedIntegerType, - sema.FixedSizeUnsignedIntegerType: - continue - } - + for _, typ := range sema.AllLeafIntegerTypes { test(typ) } diff --git a/runtime/tests/checker/genericfunction_test.go b/runtime/tests/checker/genericfunction_test.go index 22559c9acf..58d7953d1c 100644 --- a/runtime/tests/checker/genericfunction_test.go +++ b/runtime/tests/checker/genericfunction_test.go @@ -567,8 +567,10 @@ func TestCheckGenericFunctionInvocation(t *testing.T) { t.Parallel() typeParameter := &sema.TypeParameter{ - Name: "T", - TypeBound: sema.NumberType, + Name: "T", + TypeBound: sema.SubtypeTypeBound{ + Type: sema.NumberType, + }, } checker, err := parseAndCheckWithTestValue(t, @@ -605,8 +607,10 @@ func TestCheckGenericFunctionInvocation(t *testing.T) { t.Parallel() typeParameter := &sema.TypeParameter{ - Name: "T", - TypeBound: sema.NumberType, + Name: "T", + TypeBound: sema.SubtypeTypeBound{ + Type: sema.NumberType, + }, } _, err := parseAndCheckWithTestValue(t, @@ -623,7 +627,7 @@ func TestCheckGenericFunctionInvocation(t *testing.T) { errs := RequireCheckerErrors(t, err, 1) - assert.IsType(t, &sema.TypeMismatchError{}, errs[0]) + assert.IsType(t, &sema.TypeBoundError{}, errs[0]) }) t.Run("invalid: one type parameter with type bound, no type argument, one parameter, one argument: bound not satisfied", func(t *testing.T) { @@ -631,8 +635,10 @@ func TestCheckGenericFunctionInvocation(t *testing.T) { t.Parallel() typeParameter := &sema.TypeParameter{ - Name: "T", - TypeBound: sema.NumberType, + Name: "T", + TypeBound: sema.SubtypeTypeBound{ + Type: sema.NumberType, + }, } _, err := parseAndCheckWithTestValue(t, @@ -660,7 +666,7 @@ func TestCheckGenericFunctionInvocation(t *testing.T) { errs := RequireCheckerErrors(t, err, 1) - assert.IsType(t, &sema.TypeMismatchError{}, errs[0]) + assert.IsType(t, &sema.TypeBoundError{}, errs[0]) }) t.Run("valid: one type parameter, one type argument, no parameters, no arguments, generic return type", func(t *testing.T) { @@ -712,8 +718,10 @@ func TestCheckGenericFunctionInvocation(t *testing.T) { t.Run(test.name, func(t *testing.T) { typeParameter := &sema.TypeParameter{ - Name: "T", - TypeBound: sema.NumberType, + Name: "T", + TypeBound: sema.SubtypeTypeBound{ + Type: sema.NumberType, + }, } checker, err := parseAndCheckWithTestValue(t, @@ -802,8 +810,10 @@ func TestCheckGenericFunctionInvocation(t *testing.T) { t.Run(test.name, func(t *testing.T) { typeParameter := &sema.TypeParameter{ - Name: "T", - TypeBound: sema.NumberType, + Name: "T", + TypeBound: sema.SubtypeTypeBound{ + Type: sema.NumberType, + }, } checker, err := parseAndCheckWithTestValue(t, @@ -1115,6 +1125,6 @@ func TestCheckGenericFunctionDeclaration(t *testing.T) { errs := RequireCheckerErrors(t, err, 1) - require.IsType(t, &sema.TypeMismatchError{}, errs[0]) + require.IsType(t, &sema.TypeBoundError{}, errs[0]) }) } diff --git a/runtime/tests/checker/integer_test.go b/runtime/tests/checker/integer_test.go index 7051db107b..9e1db319e2 100644 --- a/runtime/tests/checker/integer_test.go +++ b/runtime/tests/checker/integer_test.go @@ -409,7 +409,7 @@ func TestCheckSignedIntegerNegate(t *testing.T) { t.Parallel() - for _, ty := range sema.AllSignedIntegerTypes { + for _, ty := range sema.AllLeafSignedIntegerTypes { name := ty.String() t.Run(name, func(t *testing.T) { @@ -431,7 +431,7 @@ func TestCheckInvalidUnsignedIntegerNegate(t *testing.T) { t.Parallel() - for _, ty := range sema.AllUnsignedIntegerTypes { + for _, ty := range sema.AllLeafUnsignedIntegerTypes { name := ty.String() t.Run(name, func(t *testing.T) { @@ -485,12 +485,7 @@ func TestCheckFixedPointToIntegerConversion(t *testing.T) { t.Parallel() - for _, ty := range sema.AllIntegerTypes { - // Only test leaf types - switch ty { - case sema.IntegerType, sema.SignedIntegerType, sema.FixedSizeUnsignedIntegerType: - continue - } + for _, ty := range sema.AllLeafIntegerTypes { t.Run(ty.String(), func(t *testing.T) { @@ -565,12 +560,7 @@ func TestCheckIntegerMinMax(t *testing.T) { ) } - for _, ty := range sema.AllIntegerTypes { - // Only test leaf types - switch ty { - case sema.IntegerType, sema.SignedIntegerType, sema.FixedSizeUnsignedIntegerType: - continue - } + for _, ty := range sema.AllLeafIntegerTypes { t.Run(ty.String(), func(t *testing.T) { numericType := ty.(*sema.NumericType) diff --git a/runtime/tests/checker/operations_test.go b/runtime/tests/checker/operations_test.go index 38c02bcc7f..6edd4fac7d 100644 --- a/runtime/tests/checker/operations_test.go +++ b/runtime/tests/checker/operations_test.go @@ -448,8 +448,8 @@ func TestCheckSaturatedArithmeticFunctions(t *testing.T) { } for _, ty := range common.Concat( - sema.AllSignedIntegerTypes, - sema.AllSignedFixedPointTypes, + sema.AllLeafSignedIntegerTypes, + sema.AllLeafSignedFixedPointTypes, ) { if ty == sema.IntType { @@ -466,8 +466,8 @@ func TestCheckSaturatedArithmeticFunctions(t *testing.T) { } for _, ty := range common.Concat( - sema.AllUnsignedIntegerTypes, - sema.AllUnsignedFixedPointTypes, + sema.AllLeafUnsignedIntegerTypes, + sema.AllLeafUnsignedFixedPointTypes, ) { if ty == sema.UIntType || strings.HasPrefix(ty.String(), "Word") { diff --git a/runtime/tests/checker/range_value_test.go b/runtime/tests/checker/range_value_test.go index 4e64dd56fc..ea7f97d94c 100644 --- a/runtime/tests/checker/range_value_test.go +++ b/runtime/tests/checker/range_value_test.go @@ -306,14 +306,7 @@ func TestCheckInclusiveRangeConstructionInvalid(t *testing.T) { }) } - for _, integerType := range sema.AllIntegerTypes { - // Only test leaf types - switch integerType { - case sema.IntegerType, - sema.SignedIntegerType, - sema.FixedSizeUnsignedIntegerType: - continue - } + for _, integerType := range sema.AllLeafIntegerTypes { typeString := integerType.String() @@ -396,7 +389,7 @@ func TestCheckInclusiveRangeConstructionInvalid(t *testing.T) { let b: Integer = Int16(10) let r = InclusiveRange(a, b) `, - []error{&sema.InvalidTypeArgumentError{}}, + []error{&sema.TypeBoundError{}}, ) runInvalidCase( t, @@ -407,7 +400,7 @@ func TestCheckInclusiveRangeConstructionInvalid(t *testing.T) { let s: Integer = UInt16(2) let r = InclusiveRange(a, b, step: s) `, - []error{&sema.InvalidTypeArgumentError{}}, + []error{&sema.TypeBoundError{}}, ) } @@ -439,7 +432,7 @@ func TestInclusiveRangeNonLeafIntegerTypes(t *testing.T) { `, ty), newOptions()) errs := RequireCheckerErrors(t, err, 1) - assert.IsType(t, &sema.InvalidTypeArgumentError{}, errs[0]) + assert.IsType(t, &sema.TypeBoundError{}, errs[0]) }) t.Run(fmt.Sprintf("InclusiveRange<%s> infer from lhs", ty), func(t *testing.T) { @@ -453,8 +446,8 @@ func TestInclusiveRangeNonLeafIntegerTypes(t *testing.T) { // 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]) + assert.IsType(t, &sema.TypeBoundError{}, errs[0]) + assert.IsType(t, &sema.TypeBoundError{}, errs[1]) }) t.Run(fmt.Sprintf("InclusiveRange<%s> assignment", ty), func(t *testing.T) { @@ -466,7 +459,7 @@ func TestInclusiveRangeNonLeafIntegerTypes(t *testing.T) { `, ty), newOptions()) errs := RequireCheckerErrors(t, err, 1) - assert.IsType(t, &sema.InvalidTypeArgumentError{}, errs[0]) + assert.IsType(t, &sema.TypeBoundError{}, errs[0]) }) } diff --git a/runtime/tests/checker/typeargument_test.go b/runtime/tests/checker/typeargument_test.go index 060c983961..2b6402f730 100644 --- a/runtime/tests/checker/typeargument_test.go +++ b/runtime/tests/checker/typeargument_test.go @@ -140,7 +140,7 @@ func TestCheckTypeArguments(t *testing.T) { errs := RequireCheckerErrors(t, err, 1) - assert.IsType(t, &sema.TypeMismatchError{}, errs[0]) + assert.IsType(t, &sema.TypeBoundError{}, errs[0]) }) t.Run("capability, instantiation with two arguments", func(t *testing.T) { @@ -915,8 +915,10 @@ func TestCheckParameterizedTypeIsInstantiated(t *testing.T) { &sema.FunctionType{ TypeParameters: []*sema.TypeParameter{ { - Name: "T", - TypeBound: &sema.InclusiveRangeType{}, + Name: "T", + TypeBound: sema.SubtypeTypeBound{ + Type: &sema.InclusiveRangeType{}, + }, }, }, ReturnTypeAnnotation: sema.VoidTypeAnnotation, @@ -936,8 +938,10 @@ func TestCheckParameterizedTypeIsInstantiated(t *testing.T) { TypeParameters: []*sema.TypeParameter{ { Name: "T", - TypeBound: &sema.InclusiveRangeType{ - MemberType: sema.IntType, + TypeBound: sema.SubtypeTypeBound{ + Type: &sema.InclusiveRangeType{ + MemberType: sema.IntType, + }, }, }, }, diff --git a/runtime/tests/interpreter/arithmetic_test.go b/runtime/tests/interpreter/arithmetic_test.go index 84974e2d4a..358cd22571 100644 --- a/runtime/tests/interpreter/arithmetic_test.go +++ b/runtime/tests/interpreter/arithmetic_test.go @@ -725,8 +725,8 @@ func TestInterpretSaturatedArithmeticFunctions(t *testing.T) { // Verify all test cases exist for _, ty := range common.Concat( - sema.AllSignedIntegerTypes, - sema.AllSignedFixedPointTypes, + sema.AllLeafSignedIntegerTypes, + sema.AllLeafSignedFixedPointTypes, ) { testCase, ok := testCases[ty] @@ -751,8 +751,8 @@ func TestInterpretSaturatedArithmeticFunctions(t *testing.T) { } for _, ty := range common.Concat( - sema.AllUnsignedIntegerTypes, - sema.AllUnsignedFixedPointTypes, + sema.AllLeafUnsignedIntegerTypes, + sema.AllLeafUnsignedFixedPointTypes, ) { if strings.HasPrefix(ty.String(), "Word") { diff --git a/runtime/tests/interpreter/fixedpoint_test.go b/runtime/tests/interpreter/fixedpoint_test.go index b099d8519e..37c32f49a5 100644 --- a/runtime/tests/interpreter/fixedpoint_test.go +++ b/runtime/tests/interpreter/fixedpoint_test.go @@ -59,12 +59,7 @@ func TestInterpretFixedPointConversionAndAddition(t *testing.T) { "UFix64": interpreter.NewUnmeteredUFix64Value(123000000), } - for _, fixedPointType := range sema.AllFixedPointTypes { - // Only test leaf types - switch fixedPointType { - case sema.FixedPointType, sema.SignedFixedPointType: - continue - } + for _, fixedPointType := range sema.AllLeafFixedPointTypes { if _, ok := tests[fixedPointType.String()]; !ok { panic(fmt.Sprintf("broken test: missing %s", fixedPointType)) @@ -117,13 +112,7 @@ var testFixedPointValues = map[string]interpreter.Value{ } func init() { - for _, fixedPointType := range sema.AllFixedPointTypes { - // Only test leaf types - switch fixedPointType { - case sema.FixedPointType, sema.SignedFixedPointType: - continue - } - + for _, fixedPointType := range sema.AllLeafFixedPointTypes { if _, ok := testFixedPointValues[fixedPointType.String()]; !ok { panic(fmt.Sprintf("broken test: missing fixed-point type: %s", fixedPointType)) } @@ -354,7 +343,7 @@ func TestInterpretFixedPointConversions(t *testing.T) { t.Run("invalid negative integer to UFix64", func(t *testing.T) { - for _, integerType := range sema.AllSignedIntegerTypes { + for _, integerType := range sema.AllLeafSignedIntegerTypes { t.Run(integerType.String(), func(t *testing.T) { @@ -500,7 +489,7 @@ func TestInterpretFixedPointConversions(t *testing.T) { const testedValue = sema.Fix64TypeMinInt - 1 testValueBig := big.NewInt(testedValue) - for _, integerType := range sema.AllSignedIntegerTypes { + for _, integerType := range sema.AllLeafSignedIntegerTypes { // Only test for integer types that can hold testedValue @@ -579,13 +568,7 @@ func TestInterpretFixedPointMinMax(t *testing.T) { }, } - for _, ty := range sema.AllFixedPointTypes { - // Only test leaf types - switch ty { - case sema.FixedPointType, sema.SignedFixedPointType: - continue - } - + for _, ty := range sema.AllLeafFixedPointTypes { if _, ok := testCases[ty]; !ok { require.Fail(t, "missing type: %s", ty.String()) } diff --git a/runtime/tests/interpreter/for_test.go b/runtime/tests/interpreter/for_test.go index 3d9b94d185..08bacefc21 100644 --- a/runtime/tests/interpreter/for_test.go +++ b/runtime/tests/interpreter/for_test.go @@ -886,26 +886,14 @@ func TestInclusiveRangeForInLoop(t *testing.T) { }) } - for _, typ := range sema.AllIntegerTypes { - // Only test leaf types - switch typ { - case sema.IntegerType, - sema.SignedIntegerType, - sema.FixedSizeUnsignedIntegerType: - continue - } + for _, typ := range sema.AllLeafIntegerTypes { for _, testCase := range unsignedTestCases { runTestCase(t, typ, testCase) } } - for _, typ := range sema.AllSignedIntegerTypes { - // Only test leaf types - switch typ { - case sema.SignedIntegerType: - continue - } + for _, typ := range sema.AllLeafSignedIntegerTypes { for _, testCase := range signedTestCases { runTestCase(t, typ, testCase) diff --git a/runtime/tests/interpreter/integers_test.go b/runtime/tests/interpreter/integers_test.go index 285b35dd63..ce93d70aa4 100644 --- a/runtime/tests/interpreter/integers_test.go +++ b/runtime/tests/interpreter/integers_test.go @@ -61,13 +61,7 @@ var testIntegerTypesAndValues = map[string]interpreter.Value{ } func init() { - for _, integerType := range sema.AllIntegerTypes { - // Only test leaf types - switch integerType { - case sema.IntegerType, sema.SignedIntegerType, sema.FixedSizeUnsignedIntegerType: - continue - } - + for _, integerType := range sema.AllLeafIntegerTypes { if _, ok := testIntegerTypesAndValues[integerType.String()]; !ok { panic(fmt.Sprintf("broken test: missing %s", integerType)) } @@ -702,13 +696,7 @@ func TestInterpretIntegerConversion(t *testing.T) { }, } - for _, ty := range sema.AllIntegerTypes { - // Only test leaf types - switch ty { - case sema.IntegerType, sema.SignedIntegerType, sema.FixedSizeUnsignedIntegerType: - continue - } - + for _, ty := range sema.AllLeafIntegerTypes { _, ok := testValues[ty.(*sema.NumericType)] require.True(t, ok, "missing expected value for type %s", ty.String()) } @@ -888,13 +876,7 @@ func TestInterpretIntegerMinMax(t *testing.T) { }, } - for _, ty := range sema.AllIntegerTypes { - // Only test leaf types - switch ty { - case sema.IntegerType, sema.SignedIntegerType, sema.FixedSizeUnsignedIntegerType: - continue - } - + for _, ty := range sema.AllLeafIntegerTypes { if _, ok := testCases[ty]; !ok { require.Fail(t, "missing type: %s", ty.String()) } @@ -959,7 +941,7 @@ func TestInterpretStringIntegerConversion(t *testing.T) { } } - for _, typ := range append(sema.AllSignedIntegerTypes, sema.AllUnsignedIntegerTypes...) { + for _, typ := range sema.AllLeafIntegerTypes { t.Run(typ.String(), func(t *testing.T) { test(t, typ) }) } } diff --git a/runtime/tests/interpreter/interpreter_test.go b/runtime/tests/interpreter/interpreter_test.go index 4f5808b40a..56e86cd6fe 100644 --- a/runtime/tests/interpreter/interpreter_test.go +++ b/runtime/tests/interpreter/interpreter_test.go @@ -7519,12 +7519,7 @@ func TestInterpretEmitEventParameterTypes(t *testing.T) { } } - for _, fixedPointType := range sema.AllFixedPointTypes { - - switch fixedPointType { - case sema.FixedPointType, sema.SignedFixedPointType: - continue - } + for _, fixedPointType := range sema.AllLeafFixedPointTypes { if _, ok := validTypes[fixedPointType.String()]; !ok { panic(fmt.Sprintf("broken test: missing %s", fixedPointType)) diff --git a/runtime/tests/interpreter/range_value_test.go b/runtime/tests/interpreter/range_value_test.go index 93bb45c80b..f9c8c8b15e 100644 --- a/runtime/tests/interpreter/range_value_test.go +++ b/runtime/tests/interpreter/range_value_test.go @@ -529,14 +529,7 @@ func TestInclusiveRangeConstructionInvalid(t *testing.T) { }) } - for _, integerType := range sema.AllIntegerTypes { - // Only test leaf types - switch integerType { - case sema.IntegerType, - sema.SignedIntegerType, - sema.FixedSizeUnsignedIntegerType: - continue - } + for _, integerType := range sema.AllLeafIntegerTypes { typeString := integerType.String() @@ -560,12 +553,7 @@ func TestInclusiveRangeConstructionInvalid(t *testing.T) { } // Additional invalid cases for signed integer types - for _, integerType := range sema.AllSignedIntegerTypes { - // Only test leaf types - switch integerType { - case sema.SignedIntegerType: - continue - } + for _, integerType := range sema.AllLeafSignedIntegerTypes { typeString := integerType.String() @@ -582,12 +570,7 @@ func TestInclusiveRangeConstructionInvalid(t *testing.T) { } // Additional invalid cases for unsigned integer types - for _, integerType := range sema.AllUnsignedIntegerTypes { - // Only test leaf types - switch integerType { - case sema.IntegerType: - continue - } + for _, integerType := range sema.AllLeafUnsignedIntegerTypes { typeString := integerType.String() diff --git a/runtime/tests/interpreter/reference_test.go b/runtime/tests/interpreter/reference_test.go index 0bf15748ae..7c1de7459e 100644 --- a/runtime/tests/interpreter/reference_test.go +++ b/runtime/tests/interpreter/reference_test.go @@ -1893,12 +1893,7 @@ func TestInterpretDereference(t *testing.T) { sema.Int256Type: interpreter.NewUnmeteredInt256ValueFromInt64(42), } - for _, typ := range sema.AllIntegerTypes { - // Only test leaf types - switch typ { - case sema.IntegerType, sema.SignedIntegerType, sema.FixedSizeUnsignedIntegerType: - continue - } + for _, typ := range sema.AllLeafIntegerTypes { integerType := typ typString := typ.QualifiedString() @@ -1930,12 +1925,7 @@ func TestInterpretDereference(t *testing.T) { sema.Fix64Type: interpreter.NewUnmeteredFix64Value(4224_000_000), } - for _, typ := range sema.AllFixedPointTypes { - // Only test leaf types - switch typ { - case sema.FixedPointType, sema.SignedFixedPointType: - continue - } + for _, typ := range sema.AllLeafFixedPointTypes { fixedPointType := typ typString := typ.QualifiedString() @@ -1962,12 +1952,7 @@ func TestInterpretDereference(t *testing.T) { t.Run("Variable-sized array of integers", func(t *testing.T) { t.Parallel() - for _, typ := range sema.AllIntegerTypes { - // Only test leaf types - switch typ { - case sema.IntegerType, sema.SignedIntegerType, sema.FixedSizeUnsignedIntegerType: - continue - } + for _, typ := range sema.AllLeafIntegerTypes { integerType := typ typString := typ.QualifiedString() @@ -2379,12 +2364,7 @@ func TestInterpretDereference(t *testing.T) { t.Run("Constant-sized array of integers", func(t *testing.T) { t.Parallel() - for _, typ := range sema.AllIntegerTypes { - // Only test leaf types - switch typ { - case sema.IntegerType, sema.SignedIntegerType, sema.FixedSizeUnsignedIntegerType: - continue - } + for _, typ := range sema.AllLeafIntegerTypes { integerType := typ typString := typ.QualifiedString() diff --git a/types.go b/types.go index 9c21f3a37b..9f8c77bba4 100644 --- a/types.go +++ b/types.go @@ -21,6 +21,7 @@ package cadence import ( "fmt" "reflect" + "strings" "sync" "github.com/onflow/cadence/runtime/common" @@ -674,12 +675,12 @@ func NewParameter( type TypeParameter struct { Name string - TypeBound Type + TypeBound TypeBound } func NewTypeParameter( name string, - typeBound Type, + typeBound TypeBound, ) TypeParameter { return TypeParameter{ Name: name, @@ -1813,3 +1814,149 @@ func (t *EnumType) Equal(other Type) bool { return t.Location == otherType.Location && t.QualifiedIdentifier == otherType.QualifiedIdentifier } + +type TypeBound interface { + isTypeBound() + ID() string + Equal(other TypeBound) bool +} + +type SubtypeTypeBound struct { + Type Type +} + +var _ TypeBound = SubtypeTypeBound{} + +func NewSubtypeTypeBound(ty Type) SubtypeTypeBound { + return SubtypeTypeBound{Type: ty} +} + +func (SubtypeTypeBound) isTypeBound() {} + +func (b SubtypeTypeBound) ID() string { + return fmt.Sprintf("<=: %s", b.Type.ID()) +} + +func (b SubtypeTypeBound) Equal(other TypeBound) bool { + otherBound, ok := other.(SubtypeTypeBound) + if !ok { + return false + } + + return b.Type.Equal(otherBound.Type) +} + +type SupertypeTypeBound struct { + Type Type +} + +var _ TypeBound = SupertypeTypeBound{} + +func NewSupertypeTypeBound(ty Type) SupertypeTypeBound { + return SupertypeTypeBound{Type: ty} +} + +func (SupertypeTypeBound) isTypeBound() {} + +func (b SupertypeTypeBound) ID() string { + return fmt.Sprintf(">=: %s", b.Type.ID()) +} + +func (b SupertypeTypeBound) Equal(other TypeBound) bool { + otherBound, ok := other.(SupertypeTypeBound) + if !ok { + return false + } + + return b.Type.Equal(otherBound.Type) +} + +type EqualTypeBound struct { + Type Type +} + +var _ TypeBound = EqualTypeBound{} + +func NewEqualTypeBound(ty Type) EqualTypeBound { + return EqualTypeBound{Type: ty} +} + +func (EqualTypeBound) isTypeBound() {} + +func (b EqualTypeBound) ID() string { + return fmt.Sprintf("= %s", b.Type.ID()) +} + +func (b EqualTypeBound) Equal(other TypeBound) bool { + otherBound, ok := other.(EqualTypeBound) + if !ok { + return false + } + + return b.Type.Equal(otherBound.Type) +} + +type NegationTypeBound struct { + NegatedBound TypeBound +} + +var _ TypeBound = NegationTypeBound{} + +func NewNegationTypeBound(bound TypeBound) NegationTypeBound { + return NegationTypeBound{NegatedBound: bound} +} + +func (NegationTypeBound) isTypeBound() {} + +func (b NegationTypeBound) ID() string { + return fmt.Sprintf("!(%s)", b.NegatedBound.ID()) +} + +func (b NegationTypeBound) Equal(other TypeBound) bool { + otherBound, ok := other.(NegationTypeBound) + if !ok { + return false + } + + return b.NegatedBound.Equal(otherBound.NegatedBound) +} + +type ConjunctionTypeBound struct { + TypeBounds []TypeBound +} + +var _ TypeBound = ConjunctionTypeBound{} + +func NewConjunctionTypeBound(bounds []TypeBound) ConjunctionTypeBound { + return ConjunctionTypeBound{TypeBounds: bounds} +} + +func (ConjunctionTypeBound) isTypeBound() {} + +func (b ConjunctionTypeBound) ID() string { + var strs []string + for _, bound := range b.TypeBounds { + strs = append(strs, fmt.Sprintf("(%s)", bound.ID())) + } + return strings.Join(strs[:], " && ") +} + +func (b ConjunctionTypeBound) Equal(bound TypeBound) bool { + other, ok := bound.(ConjunctionTypeBound) + if !ok { + return false + } + + if len(b.TypeBounds) != len(other.TypeBounds) { + return false + } + + for i, typeBound := range b.TypeBounds { + otherTypeBound := other.TypeBounds[i] + if !typeBound.Equal(otherTypeBound) { + return false + } + } + + return true +} diff --git a/types_test.go b/types_test.go index 395134f55f..74a6eb6e8b 100644 --- a/types_test.go +++ b/types_test.go @@ -1408,7 +1408,7 @@ func TestTypeEquality(t *testing.T) { TypeParameters: []TypeParameter{ { Name: "T", - TypeBound: AnyStructType, + TypeBound: NewSubtypeTypeBound(AnyStructType), }, }, Parameters: []Parameter{ @@ -1428,7 +1428,7 @@ func TestTypeEquality(t *testing.T) { TypeParameters: []TypeParameter{ { Name: "T", - TypeBound: AnyStructType, + TypeBound: NewSubtypeTypeBound(AnyStructType), }, }, Parameters: []Parameter{ @@ -1461,7 +1461,7 @@ func TestTypeEquality(t *testing.T) { TypeParameters: []TypeParameter{ { Name: "T", - TypeBound: AnyResourceType, + TypeBound: NewSubtypeTypeBound(AnyResourceType), }, }, Parameters: []Parameter{ @@ -1475,7 +1475,7 @@ func TestTypeEquality(t *testing.T) { TypeParameters: []TypeParameter{ { Name: "T", - TypeBound: AnyStructType, + TypeBound: NewSubtypeTypeBound(AnyStructType), }, }, Parameters: []Parameter{ @@ -1495,7 +1495,7 @@ func TestTypeEquality(t *testing.T) { TypeParameters: []TypeParameter{ { Name: "T", - TypeBound: AnyResourceType, + TypeBound: NewSubtypeTypeBound(AnyResourceType), }, }, Parameters: []Parameter{ @@ -1509,7 +1509,7 @@ func TestTypeEquality(t *testing.T) { TypeParameters: []TypeParameter{ { Name: "T", - TypeBound: AnyResourceType, + TypeBound: NewSubtypeTypeBound(AnyResourceType), }, }, Parameters: []Parameter{