diff --git a/interpreter/primitivestatictype.go b/interpreter/primitivestatictype.go index a333135f6f..468fee0bfe 100644 --- a/interpreter/primitivestatictype.go +++ b/interpreter/primitivestatictype.go @@ -38,6 +38,9 @@ type PrimitiveStaticType uint var _ StaticType = PrimitiveStaticType(0) +// Some simple types are conforming types. +var _ ConformingStaticType = PrimitiveStaticType(0) + const primitiveStaticTypePrefix = "PrimitiveStaticType" var primitiveStaticTypeConstantLength = len(primitiveStaticTypePrefix) + 2 // + 2 for parentheses @@ -260,6 +263,8 @@ const ( func (PrimitiveStaticType) isStaticType() {} +func (PrimitiveStaticType) isConformingStaticType() {} + func (t PrimitiveStaticType) elementSize() uint { switch t { case diff --git a/interpreter/statictype.go b/interpreter/statictype.go index 50c8bcac79..275652bd7a 100644 --- a/interpreter/statictype.go +++ b/interpreter/statictype.go @@ -63,6 +63,7 @@ type CompositeStaticType struct { } var _ StaticType = &CompositeStaticType{} +var _ ConformingStaticType = &CompositeStaticType{} func NewCompositeStaticType( memoryGauge common.MemoryGauge, @@ -104,6 +105,8 @@ func NewCompositeStaticTypeComputeTypeID( func (*CompositeStaticType) isStaticType() {} +func (*CompositeStaticType) isConformingStaticType() {} + func (*CompositeStaticType) elementSize() uint { return UnknownElementSize } @@ -143,6 +146,7 @@ type InterfaceStaticType struct { } var _ StaticType = &InterfaceStaticType{} +var _ ConformingStaticType = &InterfaceStaticType{} func NewInterfaceStaticType( memoryGauge common.MemoryGauge, @@ -184,6 +188,8 @@ func NewInterfaceStaticTypeComputeTypeID( func (*InterfaceStaticType) isStaticType() {} +func (*InterfaceStaticType) isConformingStaticType() {} + func (*InterfaceStaticType) elementSize() uint { return UnknownElementSize } @@ -299,6 +305,7 @@ type InclusiveRangeStaticType struct { } var _ StaticType = InclusiveRangeStaticType{} +var _ ParameterizedStaticType = InclusiveRangeStaticType{} func NewInclusiveRangeStaticType( memoryGauge common.MemoryGauge, @@ -346,6 +353,19 @@ func (t InclusiveRangeStaticType) IsDeprecated() bool { return t.ElementType.IsDeprecated() } +func (t InclusiveRangeStaticType) BaseType() StaticType { + if t.ElementType == nil { + return nil + } + return &InclusiveRangeStaticType{} +} + +func (t InclusiveRangeStaticType) TypeArguments() []StaticType { + return []StaticType{ + t.ElementType, + } +} + // ConstantSizedStaticType type ConstantSizedStaticType struct { @@ -926,6 +946,7 @@ type CapabilityStaticType struct { } var _ StaticType = &CapabilityStaticType{} +var _ ParameterizedStaticType = &CapabilityStaticType{} func NewCapabilityStaticType( memoryGauge common.MemoryGauge, @@ -991,6 +1012,30 @@ func (t *CapabilityStaticType) IsDeprecated() bool { return t.BorrowType.IsDeprecated() } +func (t *CapabilityStaticType) BaseType() StaticType { + // Note: Must be same as `sema.CapabilityType.BaseType()` + if t.BorrowType == nil { + return nil + } + + return PrimitiveStaticTypeCapability +} + +func (t *CapabilityStaticType) TypeArguments() []StaticType { + // Note: Must be same as `sema.CapabilityType.TypeArguments()` + borrowType := t.BorrowType + if borrowType == nil { + borrowType = &ReferenceStaticType{ + ReferencedType: PrimitiveStaticTypeAny, + Authorization: UnauthorizedAccess, + } + } + + return []StaticType{ + borrowType, + } +} + // Conversion func ConvertSemaToStaticType(memoryGauge common.MemoryGauge, t sema.Type) StaticType { @@ -1063,6 +1108,11 @@ func ConvertSemaToStaticType(memoryGauge common.MemoryGauge, t sema.Type) Static case *sema.TransactionType: return ConvertSemaTransactionToStaticTransactionType(memoryGauge, t) + + case *sema.GenericType: + // Function types could have generic-typed returns/parameters. e.g: builtin functions. + // Since they are not resolved, the type is unknown here. + return PrimitiveStaticTypeUnknown } return nil @@ -1504,3 +1554,16 @@ func (p TypeParameter) String() string { } return builder.String() } + +type ParameterizedStaticType interface { + StaticType + BaseType() StaticType + TypeArguments() []StaticType +} + +// ConformingStaticType is any static type that conforms to some interface. +// This is the static-type counterpart of `sema.ConformingType`. +type ConformingStaticType interface { + StaticType + isConformingStaticType() +} diff --git a/interpreter/subtype_check.gen.go b/interpreter/subtype_check.gen.go new file mode 100644 index 0000000000..4db739ec73 --- /dev/null +++ b/interpreter/subtype_check.gen.go @@ -0,0 +1,449 @@ +// Code generated from rules.yaml. DO NOT EDIT. +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package interpreter + +import "github.com/onflow/cadence/sema" + +func checkSubTypeWithoutEquality_gen(typeConverter TypeConverter, subType StaticType, superType StaticType) bool { + if subType == PrimitiveStaticTypeNever { + return true + } + + switch superType { + case PrimitiveStaticTypeAny: + return true + + case PrimitiveStaticTypeAnyStruct: + return !(IsResourceType(typeConverter, subType)) && + subType != PrimitiveStaticTypeAny + + case PrimitiveStaticTypeAnyResource: + return IsResourceType(typeConverter, subType) + + case PrimitiveStaticTypeAnyResourceAttachment: + return isAttachmentType(typeConverter, subType) && + IsResourceType(typeConverter, subType) + + case PrimitiveStaticTypeAnyStructAttachment: + return isAttachmentType(typeConverter, subType) && + !(IsResourceType(typeConverter, subType)) + + case PrimitiveStaticTypeHashableStruct: + return IsHashableStructType(typeConverter, subType) + + case PrimitiveStaticTypePath: + return IsSubType(typeConverter, subType, PrimitiveStaticTypeStoragePath) || + IsSubType(typeConverter, subType, PrimitiveStaticTypeCapabilityPath) + + case PrimitiveStaticTypeCapabilityPath: + switch subType { + case PrimitiveStaticTypePrivatePath, + PrimitiveStaticTypePublicPath: + return true + } + + return false + + case PrimitiveStaticTypeNumber: + switch subType { + case PrimitiveStaticTypeNumber, + PrimitiveStaticTypeSignedNumber: + return true + } + + return IsSubType(typeConverter, subType, PrimitiveStaticTypeInteger) || + IsSubType(typeConverter, subType, PrimitiveStaticTypeFixedPoint) + + case PrimitiveStaticTypeSignedNumber: + + // TODO: Maybe remove since these predicates only need to check for strict-subtyping, without the "equality". + return subType == PrimitiveStaticTypeSignedNumber || + (IsSubType(typeConverter, subType, PrimitiveStaticTypeSignedInteger) || + IsSubType(typeConverter, subType, PrimitiveStaticTypeSignedFixedPoint)) + + case PrimitiveStaticTypeInteger: + switch subType { + case PrimitiveStaticTypeInteger, + PrimitiveStaticTypeSignedInteger, + PrimitiveStaticTypeFixedSizeUnsignedInteger, + PrimitiveStaticTypeUInt: + return true + } + + return IsSubType(typeConverter, subType, PrimitiveStaticTypeSignedInteger) || + IsSubType(typeConverter, subType, PrimitiveStaticTypeFixedSizeUnsignedInteger) + + case PrimitiveStaticTypeSignedInteger: + switch subType { + case PrimitiveStaticTypeSignedInteger, + PrimitiveStaticTypeInt, + PrimitiveStaticTypeInt8, + PrimitiveStaticTypeInt16, + PrimitiveStaticTypeInt32, + PrimitiveStaticTypeInt64, + PrimitiveStaticTypeInt128, + PrimitiveStaticTypeInt256: + return true + } + + return false + + case PrimitiveStaticTypeFixedSizeUnsignedInteger: + switch subType { + case PrimitiveStaticTypeUInt8, + PrimitiveStaticTypeUInt16, + PrimitiveStaticTypeUInt32, + PrimitiveStaticTypeUInt64, + PrimitiveStaticTypeUInt128, + PrimitiveStaticTypeUInt256, + PrimitiveStaticTypeWord8, + PrimitiveStaticTypeWord16, + PrimitiveStaticTypeWord32, + PrimitiveStaticTypeWord64, + PrimitiveStaticTypeWord128, + PrimitiveStaticTypeWord256: + return true + } + + return false + + case PrimitiveStaticTypeFixedPoint: + switch subType { + case PrimitiveStaticTypeFixedPoint, + PrimitiveStaticTypeSignedFixedPoint, + PrimitiveStaticTypeUFix64, + PrimitiveStaticTypeUFix128: + return true + } + + return IsSubType(typeConverter, subType, PrimitiveStaticTypeSignedFixedPoint) + + case PrimitiveStaticTypeSignedFixedPoint: + switch subType { + case PrimitiveStaticTypeSignedFixedPoint, + PrimitiveStaticTypeFix64, + PrimitiveStaticTypeFix128: + return true + } + + return false + + } + + switch typedSuperType := superType.(type) { + case *OptionalStaticType: + + // Optionals are covariant: T? <: U? if T <: U + switch typedSubType := subType.(type) { + case *OptionalStaticType: + return IsSubType(typeConverter, typedSubType.Type, typedSuperType.Type) + } + + // T <: U? if T <: U + return IsSubType(typeConverter, subType, typedSuperType.Type) + + case *DictionaryStaticType: + switch typedSubType := subType.(type) { + case *DictionaryStaticType: + return IsSubType(typeConverter, typedSubType.ValueType, typedSuperType.ValueType) && + IsSubType(typeConverter, typedSubType.KeyType, typedSuperType.KeyType) + } + + return false + + case *VariableSizedStaticType: + switch typedSubType := subType.(type) { + case *VariableSizedStaticType: + return IsSubType(typeConverter, typedSubType.ElementType(), typedSuperType.ElementType()) + } + + return false + + case *ConstantSizedStaticType: + switch typedSubType := subType.(type) { + case *ConstantSizedStaticType: + return typedSuperType.Size == typedSubType.Size && + IsSubType(typeConverter, typedSubType.ElementType(), typedSuperType.ElementType()) + } + + return false + + case *ReferenceStaticType: + switch typedSubType := subType.(type) { + case *ReferenceStaticType: + + // The authorization of the subtype reference must be usable in all situations where the supertype reference is usable. + return PermitsAccess(typeConverter, typedSuperType.Authorization, typedSubType.Authorization) && + // References are covariant in their referenced type + IsSubType(typeConverter, typedSubType.ReferencedType, typedSuperType.ReferencedType) + } + + return false + + case *CompositeStaticType: + switch typedSubType := subType.(type) { + case *IntersectionStaticType: + switch typedSubType.LegacyType { + case nil, + PrimitiveStaticTypeAnyResource, + PrimitiveStaticTypeAnyStruct, + PrimitiveStaticTypeAny: + return false + } + + switch typedSubTypeLegacyType := typedSubType.LegacyType.(type) { + case *CompositeStaticType: + return typedSubTypeLegacyType == typedSuperType + } + + return false + case *CompositeStaticType: + return false + } + + return IsParameterizedSubType(typeConverter, subType, typedSuperType) + + case *InterfaceStaticType: + switch typedSubType := subType.(type) { + case *CompositeStaticType: + typedSemaSuperType := typeConverter.SemaTypeFromStaticType(typedSuperType).(*sema.InterfaceType) + typedSemaSubType := typeConverter.SemaTypeFromStaticType(typedSubType).(*sema.CompositeType) + return typedSemaSubType.Kind == typedSemaSuperType.CompositeKind && + typedSemaSubType.EffectiveInterfaceConformanceSet().Contains(typedSemaSuperType) + case *IntersectionStaticType: + typedSemaSuperType := typeConverter.SemaTypeFromStaticType(typedSuperType).(*sema.InterfaceType) + typedSemaSubType := typeConverter.SemaTypeFromStaticType(typedSubType).(*sema.IntersectionType) + return typedSemaSubType.EffectiveIntersectionSet().Contains(typedSemaSuperType) + case *InterfaceStaticType: + typedSemaSuperType := typeConverter.SemaTypeFromStaticType(typedSuperType).(*sema.InterfaceType) + typedSemaSubType := typeConverter.SemaTypeFromStaticType(typedSubType).(*sema.InterfaceType) + return typedSemaSubType.EffectiveInterfaceConformanceSet().Contains(typedSemaSuperType) + } + + return IsParameterizedSubType(typeConverter, subType, typedSuperType) + + case *IntersectionStaticType: + switch typedSuperType.LegacyType { + case nil, + PrimitiveStaticTypeAny, + PrimitiveStaticTypeAnyStruct, + PrimitiveStaticTypeAnyResource: + + // `Any` is a subtype of an intersection type + // - `Any{Us}: not statically.` + // - `AnyStruct{Us}`: never; + // - `AnyResource{Us}`: never; + // + // `AnyStruct` is a subtype of an intersection type + // - `AnyStruct{Us}`: not statically. + // - `AnyResource{Us}`: never; + // - `Any{Us}`: not statically. + // + // `AnyResource` is a subtype of an intersection type + // - `AnyResource{Us}`: not statically; + // - `AnyStruct{Us}`: never. + // - `Any{Us}`: not statically; + switch subType { + case PrimitiveStaticTypeAny, + PrimitiveStaticTypeAnyStruct, + PrimitiveStaticTypeAnyResource: + return false + } + + // An intersection type `T{Us}` + // is a subtype of an intersection type `AnyResource{Vs}` / `AnyStruct{Vs}` / `Any{Vs}`: + switch typedSubType := subType.(type) { + case *IntersectionStaticType: + // An intersection type `{Us}` is a subtype of an intersection type `{Vs}` / `{Vs}` / `{Vs}`: + // when `Vs` is a subset of `Us`. + if typedSubType.LegacyType == nil && + IsIntersectionSubset(typeConverter, typedSuperType, typedSubType) { + return true + } + + // When `T == AnyResource || T == AnyStruct || T == Any`: + // if the intersection type of the subtype + // is a subtype of the intersection supertype, + // and `Vs` is a subset of `Us`. + switch typedSubType.LegacyType { + case PrimitiveStaticTypeAny, + PrimitiveStaticTypeAnyStruct, + PrimitiveStaticTypeAnyResource: + + // Below two combination is repeated several times below. + // Maybe combine them to produce a single predicate. + return (typedSuperType.LegacyType == nil || + IsSubType(typeConverter, typedSubType.LegacyType, typedSuperType.LegacyType)) && + IsIntersectionSubset(typeConverter, typedSuperType, typedSubType) + } + + // When `T != AnyResource && T != AnyStruct && T != Any`: + // if the intersection type of the subtype + // is a subtype of the intersection supertype, + // and `T` conforms to `Vs`. + // `Us` and `Vs` do *not* have to be subsets. + switch typedSubTypeLegacyType := typedSubType.LegacyType.(type) { + case *CompositeStaticType: + return (typedSuperType.LegacyType == nil || + IsSubType(typeConverter, typedSubTypeLegacyType, typedSuperType.LegacyType)) && + IsIntersectionSubset(typeConverter, typedSuperType, typedSubTypeLegacyType) + } + + return false + case ConformingStaticType: + return (typedSuperType.LegacyType == nil || + IsSubType(typeConverter, typedSubType, typedSuperType.LegacyType)) && + IsIntersectionSubset(typeConverter, typedSuperType, typedSubType) + } + + return false + } + + // An intersection type `T{Us}` + // is a subtype of an intersection type `V{Ws}`: + switch typedSubType := subType.(type) { + case *IntersectionStaticType: + + // When `T == AnyResource || T == AnyStruct || T == Any`: + // not statically. + switch typedSubType.LegacyType { + case nil, + PrimitiveStaticTypeAny, + PrimitiveStaticTypeAnyStruct, + PrimitiveStaticTypeAnyResource: + return false + } + + switch typedSubTypeLegacyType := typedSubType.LegacyType.(type) { + case *CompositeStaticType: + + // When `T != AnyResource && T != AnyStructType && T != Any`: if `T == V`. + // `Us` and `Ws` do *not* have to be subsets: + // The owner may freely restrict and unrestrict. + return typedSubTypeLegacyType == typedSuperType.LegacyType + } + + return false + case *CompositeStaticType: + return IsSubType(typeConverter, typedSubType, typedSuperType.LegacyType) + } + + // A type `T` + // is a subtype of an intersection type `AnyResource{Vs}` / `AnyStruct{Vs}` / `Any{Vs}`: + // not statically. + switch subType { + case PrimitiveStaticTypeAny, + PrimitiveStaticTypeAnyStruct, + PrimitiveStaticTypeAnyResource: + return false + } + + return IsParameterizedSubType(typeConverter, subType, typedSuperType) + + case FunctionStaticType: + switch typedSubType := subType.(type) { + case FunctionStaticType: + + // View functions are subtypes of impure functions + switch typedSubType.Purity { + case typedSuperType.Purity, + FunctionPurityView: + + // Type parameters must be equivalent. This is because for subtyping of functions, + // parameters must be *contravariant/supertypes*, whereas, return types must be *covariant/subtypes*. + // Since type parameters can be used in both parameters and return types, inorder to satisfies both above + // conditions, bound type of type parameters can only be strictly equal, but not subtypes/supertypes of one another. + typedSubTypeTypeParameters := typedSubType.TypeParameters + typedSuperTypeTypeParameters := typedSuperType.TypeParameters + if len(typedSubTypeTypeParameters) != len(typedSuperTypeTypeParameters) { + return false + } + + for i, source := range typedSubTypeTypeParameters { + target := typedSuperTypeTypeParameters[i] + if !(deepEquals(source.TypeBound, target.TypeBound)) { + return false + } + } + + // Functions are contravariant in their parameter types. + typedSubTypeParameters := typedSubType.Parameters + typedSuperTypeParameters := typedSuperType.Parameters + if len(typedSubTypeParameters) != len(typedSuperTypeParameters) { + return false + } + + for i, source := range typedSubTypeParameters { + target := typedSuperTypeParameters[i] + if + + // Note the super-type is the subtype's parameter + // because the parameters are contravariant. + !(sema.IsSubType(target.TypeAnnotation.Type, source.TypeAnnotation.Type)) { + return false + } + } + + return deepEquals(typedSubType.Arity, typedSuperType.Arity) && + // Functions are covariant in their return type. + (AreReturnsCovariant(typedSubType, typedSuperType) && + typedSubType.IsConstructor == typedSuperType.IsConstructor) + } + + return false + } + + return false + + case ParameterizedStaticType: + if typedSuperType.BaseType() != nil { + switch typedSubType := subType.(type) { + case ParameterizedStaticType: + if typedSubType.BaseType() != nil { + if IsSubType(typeConverter, typedSubType.BaseType(), typedSuperType.BaseType()) { + typedSubTypeTypeArguments := typedSubType.TypeArguments() + typedSuperTypeTypeArguments := typedSuperType.TypeArguments() + if len(typedSubTypeTypeArguments) != len(typedSuperTypeTypeArguments) { + return false + } + + for i, source := range typedSubTypeTypeArguments { + target := typedSuperTypeTypeArguments[i] + if !(IsSubType(typeConverter, source, target)) { + return false + } + } + + return true + } + + } + + } + + return false + } + + return IsParameterizedSubType(typeConverter, subType, typedSuperType) + + } + + return IsParameterizedSubType(typeConverter, subType, superType) +} diff --git a/interpreter/subtype_check.go b/interpreter/subtype_check.go new file mode 100644 index 0000000000..9e4b36624b --- /dev/null +++ b/interpreter/subtype_check.go @@ -0,0 +1,135 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package interpreter + +import ( + "github.com/onflow/cadence/common" + "github.com/onflow/cadence/sema" +) + +//go:generate go run ./type_check_gen subtype_check.gen.go + +var FunctionPurityView = sema.FunctionPurityView + +func isAttachmentType(typeConverter TypeConverter, typ StaticType) bool { + switch typ { + case PrimitiveStaticTypeAnyResourceAttachment, PrimitiveStaticTypeAnyStructAttachment: + return true + default: + _, ok := typ.(*CompositeStaticType) + if !ok { + return false + } + + // TODO: Get rid of the conversion + compositeType := typeConverter.SemaTypeFromStaticType(typ).(*sema.CompositeType) + return compositeType.Kind == common.CompositeKindAttachment + } +} + +func IsHashableStructType(typeConverter TypeConverter, typ StaticType) bool { + switch typ { + case PrimitiveStaticTypeNever, + PrimitiveStaticTypeBool, + PrimitiveStaticTypeCharacter, + PrimitiveStaticTypeString, + PrimitiveStaticTypeMetaType, + PrimitiveStaticTypeHashableStruct: + return true + default: + _, ok := typ.(*CompositeStaticType) + if ok { + // TODO: Get rid of the conversion + compositeType := typeConverter.SemaTypeFromStaticType(typ).(*sema.CompositeType) + return compositeType.Kind == common.CompositeKindEnum + } + + return IsSubType(typeConverter, typ, PrimitiveStaticTypeNumber) || + IsSubType(typeConverter, typ, PrimitiveStaticTypePath) + } +} + +func IsResourceType(typeConverter TypeConverter, typ StaticType) bool { + switch typ := typ.(type) { + case PrimitiveStaticType: + // Primitive static type to sema type conversion is just a switch case. + // So not much overhead there. + return typ.SemaType().IsResourceType() + case *OptionalStaticType: + return IsResourceType(typeConverter, typ.Type) + case ArrayStaticType: + return IsResourceType(typeConverter, typ.ElementType()) + case *DictionaryStaticType: + return IsResourceType(typeConverter, typ.ValueType) + default: + semaType := typeConverter.SemaTypeFromStaticType(typ) + return semaType.IsResourceType() + } +} + +func PermitsAccess(typeConverter TypeConverter, superTypeAuth, subTypeAuth Authorization) bool { + superTypeAccess, err := typeConverter.SemaAccessFromStaticAuthorization(superTypeAuth) + if err != nil { + panic(err) + } + + subTypeAccess, err := typeConverter.SemaAccessFromStaticAuthorization(subTypeAuth) + if err != nil { + panic(err) + } + + return sema.PermitsAccess(superTypeAccess, subTypeAccess) +} + +func IsIntersectionSubset(typeConverter TypeConverter, superType *IntersectionStaticType, subType StaticType) bool { + semaSuperType := typeConverter.SemaTypeFromStaticType(superType).(*sema.IntersectionType) + semaSubType := typeConverter.SemaTypeFromStaticType(subType) + return sema.IsIntersectionSubset(semaSuperType, semaSubType) +} + +func AreReturnsCovariant(source, target FunctionStaticType) bool { + return sema.AreReturnsCovariant(source.FunctionType, target.FunctionType) +} + +func IsParameterizedSubType(typeConverter TypeConverter, subType StaticType, superType StaticType) bool { + typedSubType, ok := subType.(ParameterizedStaticType) + if !ok { + return false + } + + if baseType := typedSubType.BaseType(); baseType != nil { + return IsSubType(typeConverter, baseType, superType) + } + + return false +} + +type Equatable[T any] interface { + comparable + Equal(other T) bool +} + +func deepEquals[T Equatable[T]](source, target T) bool { + var empty T + if source == empty { + return target == empty + } + + return source.Equal(target) +} diff --git a/interpreter/type_check_gen/customizer.go b/interpreter/type_check_gen/customizer.go new file mode 100644 index 0000000000..d86b8fdd46 --- /dev/null +++ b/interpreter/type_check_gen/customizer.go @@ -0,0 +1,261 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package main + +import ( + "go/token" + "strings" + + "github.com/dave/dst" + "github.com/dave/dst/dstutil" +) + +const ( + typedSuperTypeVarName = "typedSuperType" + typedSemaSuperTypeVarName = "typedSemaSuperType" + typedSubTypeVarName = "typedSubType" + typedSemaSubTypeVarName = "typedSemaSubType" + typeConverter = "typeConverter" + staticToSemaTypeConversionFuncName = "SemaTypeFromStaticType" +) + +func Update(decls []dst.Decl) []dst.Decl { + for i, decl := range decls { + for _, updater := range updaters { + decls[i] = updater(decl) + } + } + + return decls +} + +var updaters = []CodeUpdater{ + IntersectionTypeCheckUpdater, + FunctionParametersCheckUpdater, +} + +type CodeUpdater func(decl dst.Decl) dst.Decl + +// IntersectionTypeCheckUpdater Updates the intersection type's subtype checking +// to use `sema.Type`s, since `StaticType`s doesn't preserve conformance info. +func IntersectionTypeCheckUpdater(decl dst.Decl) dst.Decl { + var intersectionTypeRuleNode, nestedCaseClause dst.Node + return dstutil.Apply( + decl, + + // Pre-order traversal: called before visiting children + func(cursor *dstutil.Cursor) bool { + currentNode := cursor.Node() + + switch currentNode := currentNode.(type) { + case *dst.CaseClause: + caseExpr := currentNode.List[0] + starExpr, ok := caseExpr.(*dst.StarExpr) + if !ok { + break + } + identifier, ok := starExpr.X.(*dst.Ident) + if !ok { + break + } + + // This is the case-clause for `*InterfaceStaticType`, in the outer type-switch. + if intersectionTypeRuleNode == nil && identifier.Name == "InterfaceStaticType" { + intersectionTypeRuleNode = currentNode + return true + } + + // This is a nested case-clause inside `intersectionTypeRuleNode`. + if intersectionTypeRuleNode != nil { + nestedCaseClause = currentNode + } + + case *dst.Ident: + if nestedCaseClause != nil { + switch currentNode.Name { + case typedSuperTypeVarName: + cursor.Replace(dst.NewIdent(typedSemaSuperTypeVarName)) + case typedSubTypeVarName: + cursor.Replace(dst.NewIdent(typedSemaSubTypeVarName)) + } + } + } + + // Return true to continue visiting children + return true + }, + + // Post-order traversal: called after visiting children + func(cursor *dstutil.Cursor) bool { + node := cursor.Node() + if node == nil { + return true + } + + // Add the new variables after visiting the clause (rather than before visiting), + // so that renaming the variables won't affect this newly added one. + switch node { + case intersectionTypeRuleNode: + // Clear the variable when exiting from the intersection-type case-clause + intersectionTypeRuleNode = nil + + case nestedCaseClause: + caseClause := node.(*dst.CaseClause) + caseExpr := caseClause.List[0] + starExpr := caseExpr.(*dst.StarExpr) + identifier := starExpr.X.(*dst.Ident) + + semaTypeName := strings.ReplaceAll(identifier.Name, "StaticType", "Type") + + superTypeSemaConversion := &dst.AssignStmt{ + Lhs: []dst.Expr{ + dst.NewIdent(typedSemaSuperTypeVarName), + }, + Tok: token.DEFINE, + Rhs: []dst.Expr{ + &dst.TypeAssertExpr{ + X: &dst.CallExpr{ + Fun: &dst.SelectorExpr{ + X: dst.NewIdent(typeConverter), + Sel: dst.NewIdent(staticToSemaTypeConversionFuncName), + }, + Args: []dst.Expr{ + dst.NewIdent(typedSuperTypeVarName), + }, + }, + Type: &dst.StarExpr{ + X: &dst.Ident{ + Name: "InterfaceType", + Path: semaPkgPath, + }, + }, + }, + }, + } + + subtypeSemaConversion := &dst.AssignStmt{ + Lhs: []dst.Expr{ + dst.NewIdent(typedSemaSubTypeVarName), + }, + Tok: token.DEFINE, + Rhs: []dst.Expr{ + &dst.TypeAssertExpr{ + X: &dst.CallExpr{ + Fun: &dst.SelectorExpr{ + X: dst.NewIdent(typeConverter), + Sel: dst.NewIdent(staticToSemaTypeConversionFuncName), + }, + Args: []dst.Expr{ + dst.NewIdent(typedSubTypeVarName), + }, + }, + Type: &dst.StarExpr{ + X: &dst.Ident{ + Name: semaTypeName, + Path: semaPkgPath, + }, + }, + }, + }, + } + + stmts := []dst.Stmt{ + superTypeSemaConversion, + subtypeSemaConversion, + } + + stmts = append(stmts, caseClause.Body...) + caseClause.Body = stmts + + // Clear the variable when exiting from the nested case-clause. + nestedCaseClause = nil + } + + // Return true to continue + return true + }, + ).(dst.Decl) +} + +// FunctionParametersCheckUpdater updates the function parameter check to +// use the `IsSubType` function from the `sema` package. +func FunctionParametersCheckUpdater(decl dst.Decl) dst.Decl { + var functionTypeRuleNode dst.Node + var isFunctionParamsLoop bool + + return dstutil.Apply( + decl, + + // Pre-order traversal: called before visiting children + func(cursor *dstutil.Cursor) bool { + currentNode := cursor.Node() + + switch currentNode := currentNode.(type) { + case *dst.CaseClause: + caseExpr := currentNode.List[0] + identifier, ok := caseExpr.(*dst.Ident) + if !ok { + break + } + + // This is the case-clause for `FunctionStaticType`, in the outer type-switch. + if functionTypeRuleNode == nil && identifier.Name == "FunctionStaticType" { + functionTypeRuleNode = currentNode + } + + case *dst.RangeStmt: + if functionTypeRuleNode != nil { + identifier, ok := currentNode.X.(*dst.Ident) + if ok && identifier.Name == "typedSubTypeParameters" { + isFunctionParamsLoop = true + } + } + + case *dst.CallExpr: + if isFunctionParamsLoop { + identifier, ok := currentNode.Fun.(*dst.Ident) + if ok && identifier.Name == "IsSubType" { + // Update the package of the function. + identifier.Path = semaPkgPath + // Drop the "typeConverter" argument, since `sema.IsSubType` method don't need it. + currentNode.Args = currentNode.Args[1:] + } + } + } + + // Return true to continue visiting children + return true + }, + + // Post-order traversal: called after visiting children + func(cursor *dstutil.Cursor) bool { + node := cursor.Node() + if node == nil { + return true + } + + // Reset the flag when exiting from the function-type case-clause + if node == functionTypeRuleNode { + isFunctionParamsLoop = false + } + + return true + }, + ).(dst.Decl) +} diff --git a/interpreter/type_check_gen/main.go b/interpreter/type_check_gen/main.go new file mode 100644 index 0000000000..595acb7659 --- /dev/null +++ b/interpreter/type_check_gen/main.go @@ -0,0 +1,89 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package main + +import ( + "flag" + "fmt" + "os" + + subtypegen "github.com/onflow/cadence/tools/subtype-gen" +) + +const ( + interpreterPkgPath = "github.com/onflow/cadence/interpreter" + semaPkgPath = "github.com/onflow/cadence/sema" + typeConverterParamName = "typeConverter" + typeConverterTypeName = "TypeConverter" +) + +var packagePathFlag = flag.String("pkg", interpreterPkgPath, "target Go package name") + +func main() { + + flag.Parse() + argumentCount := flag.NArg() + if argumentCount < 1 { + panic("Missing path to output Go file") + } + + outPath := flag.Arg(0) + + // Read and parse YAML rules + rules, err := subtypegen.ParseRules() + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "error reading YAML rules: %v\n", err) + os.Exit(1) + } + + config := subtypegen.Config{ + SimpleTypePrefix: "PrimitiveStaticType", + ComplexTypeSuffix: "StaticType", + ExtraParams: []subtypegen.ExtraParam{ + { + Name: typeConverterParamName, + Type: typeConverterTypeName, + PkgPath: interpreterPkgPath, + }, + }, + SkipTypes: map[string]struct{}{ + subtypegen.TypePlaceholderStorable: {}, + }, + NonPointerTypes: map[string]struct{}{ + subtypegen.TypePlaceholderFunction: {}, + subtypegen.TypePlaceholderConforming: {}, + subtypegen.TypePlaceholderParameterized: {}, + }, + } + + // Generate code using the comprehensive generator + gen := subtypegen.NewSubTypeCheckGenerator(config) + decls := gen.GenerateCheckSubTypeWithoutEqualityFunction(rules) + + decls = Update(decls) + + // Write output + outFile, err := os.Create(outPath) + if err != nil { + panic(err) + } + defer outFile.Close() + + subtypegen.WriteGoFile(outFile, decls, *packagePathFlag) +} diff --git a/sema/subtype_check.gen.go b/sema/subtype_check.gen.go index 5e3986c2fb..8993c531f6 100644 --- a/sema/subtype_check.gen.go +++ b/sema/subtype_check.gen.go @@ -73,8 +73,9 @@ func checkSubTypeWithoutEquality_gen(subType Type, superType Type) bool { IsSubType(subType, FixedPointType) case SignedNumberType: - return subType ==// TODO: Maybe remove since these predicates only need to check for strict-subtyping, without the "equality". - SignedNumberType || + + // TODO: Maybe remove since these predicates only need to check for strict-subtyping, without the "equality". + return subType == SignedNumberType || (IsSubType(subType, SignedIntegerType) || IsSubType(subType, SignedFixedPointType)) diff --git a/tools/subtype-gen/generator.go b/tools/subtype-gen/generator.go index fa60a300d4..1299ae2af3 100644 --- a/tools/subtype-gen/generator.go +++ b/tools/subtype-gen/generator.go @@ -1057,10 +1057,19 @@ func (gen *SubTypeCheckGenerator) generateOrPredicate(predicates []Predicate) (r // Don't negate again here (i.e: don't use `binaryExpression` method), // since negation is already done before calling this method `generateOrPredicate`. + + existingDecs := binaryExpr.Decorations().Start + binaryExpr.Decorations().Start = nil + binaryExpr = &dst.BinaryExpr{ X: binaryExpr, Op: token.LOR, Y: expr, + Decs: dst.BinaryExprDecorations{ + NodeDecs: dst.NodeDecs{ + Start: existingDecs, + }, + }, } } @@ -1079,19 +1088,33 @@ func mergeTypeSwitches(existingTypeSwitch, newTypeSwitch *dst.TypeSwitchStmt) { } func (gen *SubTypeCheckGenerator) isAttachmentPredicate(predicate IsAttachmentPredicate) []dst.Node { + args := gen.extraArguments() + + args = append( + args, + gen.expressionIgnoreNegation(predicate.Expression), + ) + return []dst.Node{ gen.callExpression( dst.NewIdent("isAttachmentType"), - gen.expressionIgnoreNegation(predicate.Expression), + args..., ), } } func (gen *SubTypeCheckGenerator) isResourcePredicate(predicate IsResourcePredicate) []dst.Node { + args := gen.extraArguments() + + args = append( + args, + gen.expressionIgnoreNegation(predicate.Expression), + ) + return []dst.Node{ gen.callExpression( dst.NewIdent("IsResourceType"), - gen.expressionIgnoreNegation(predicate.Expression), + args..., ), } } @@ -1352,10 +1375,13 @@ func (gen *SubTypeCheckGenerator) parseCaseCondition(superType Type) dst.Expr { } func (gen *SubTypeCheckGenerator) permitsPredicate(permits PermitsPredicate) []dst.Node { - args := []dst.Expr{ + args := gen.extraArguments() + + args = append( + args, gen.expressionIgnoreNegation(permits.Super), gen.expressionIgnoreNegation(permits.Sub), - } + ) return []dst.Node{ gen.callExpression( @@ -1544,10 +1570,13 @@ func (gen *SubTypeCheckGenerator) setContains(p SetContainsPredicate) []dst.Node } func (gen *SubTypeCheckGenerator) isIntersectionSubset(p IsIntersectionSubsetPredicate) []dst.Node { - args := []dst.Expr{ + args := gen.extraArguments() + + args = append( + args, gen.expressionIgnoreNegation(p.Super), gen.expressionIgnoreNegation(p.Sub), - } + ) return []dst.Node{ gen.callExpression( @@ -1572,10 +1601,13 @@ func (gen *SubTypeCheckGenerator) returnsCovariantCheck(p ReturnCovariantPredica } func (gen *SubTypeCheckGenerator) isParameterizedSubtype(p IsParameterizedSubtypePredicate) []dst.Node { - args := []dst.Expr{ + args := gen.extraArguments() + + args = append( + args, gen.expressionIgnoreNegation(p.Sub), gen.expressionIgnoreNegation(p.Super), - } + ) return []dst.Node{ gen.callExpression(