diff --git a/migrations/entitlements/migration_test.go b/migrations/entitlements/migration_test.go index a46ae9fe58..319dfe9349 100644 --- a/migrations/entitlements/migration_test.go +++ b/migrations/entitlements/migration_test.go @@ -530,6 +530,7 @@ func TestConvertToEntitledType(t *testing.T) { nil, sema.UnauthorizedAccess, sema.NewIntersectionType( + nil, nil, []*sema.InterfaceType{ interfaceTypeInheriting, @@ -541,6 +542,7 @@ func TestConvertToEntitledType(t *testing.T) { nil, eFAndGAccess, sema.NewIntersectionType( + nil, nil, []*sema.InterfaceType{ interfaceTypeInheriting, @@ -556,6 +558,7 @@ func TestConvertToEntitledType(t *testing.T) { sema.NewOptionalType( nil, sema.NewIntersectionType( + nil, nil, []*sema.InterfaceType{ interfaceTypeInheriting, @@ -570,6 +573,7 @@ func TestConvertToEntitledType(t *testing.T) { sema.NewOptionalType( nil, sema.NewIntersectionType( + nil, nil, []*sema.InterfaceType{ interfaceTypeInheriting, @@ -1313,7 +1317,7 @@ func TestConvertToEntitledValue(t *testing.T) { }, } - test := func(testCase testCase, valueGenerator valueGenerator, typeGenerator typeGenerator) { + test := func(t *testing.T, testCase testCase, valueGenerator valueGenerator, typeGenerator typeGenerator) { input := valueGenerator.wrap(typeGenerator.wrap(testCase.Input)) if input == nil { @@ -1346,7 +1350,7 @@ func TestConvertToEntitledValue(t *testing.T) { for _, typeGenerator := range typeGenerators { t.Run(typeGenerator.name, func(t *testing.T) { - test(testCase, valueGenerator, typeGenerator) + test(t, testCase, valueGenerator, typeGenerator) }) } }) diff --git a/migrations/migration_test.go b/migrations/migration_test.go index e759fde504..fd28d99748 100644 --- a/migrations/migration_test.go +++ b/migrations/migration_test.go @@ -32,6 +32,7 @@ import ( "github.com/onflow/cadence/runtime" "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/interpreter" + "github.com/onflow/cadence/runtime/sema" "github.com/onflow/cadence/runtime/stdlib" . "github.com/onflow/cadence/runtime/tests/runtime_utils" "github.com/onflow/cadence/runtime/tests/utils" @@ -42,10 +43,10 @@ type testReporter struct { interpreter.StorageKey interpreter.StorageMapKey }][]string - errored map[struct { + errors map[struct { interpreter.StorageKey interpreter.StorageMapKey - }][]string + }][]error } var _ Reporter = &testReporter{} @@ -56,10 +57,10 @@ func newTestReporter() *testReporter { interpreter.StorageKey interpreter.StorageMapKey }][]string{}, - errored: map[struct { + errors: map[struct { interpreter.StorageKey interpreter.StorageMapKey - }][]string{}, + }][]error{}, } } @@ -85,8 +86,8 @@ func (t *testReporter) Migrated( func (t *testReporter) Error( storageKey interpreter.StorageKey, storageMapKey interpreter.StorageMapKey, - migration string, - _ error, + _ string, + err error, ) { key := struct { interpreter.StorageKey @@ -96,9 +97,9 @@ func (t *testReporter) Error( StorageMapKey: storageMapKey, } - t.errored[key] = append( - t.errored[key], - migration, + t.errors[key] = append( + t.errors[key], + err, ) } @@ -702,7 +703,7 @@ func TestMigrationError(t *testing.T) { map[struct { interpreter.StorageKey interpreter.StorageMapKey - }][]string{ + }][]error{ { StorageKey: interpreter.StorageKey{ Address: account, @@ -710,10 +711,10 @@ func TestMigrationError(t *testing.T) { }, StorageMapKey: interpreter.StringStorageMapKey("int8_value"), }: { - "testInt8Migration", + errors.New("error occurred while migrating int8"), }, }, - reporter.errored, + reporter.errors, ) } @@ -806,7 +807,7 @@ func TestCapConMigration(t *testing.T) { // Assert - assert.Len(t, reporter.errored, 0) + assert.Len(t, reporter.errors, 0) assert.Len(t, reporter.migrated, 2) storageMap = storage.GetStorageMap( @@ -921,7 +922,7 @@ func TestContractMigration(t *testing.T) { // Assert - assert.Len(t, reporter.errored, 0) + assert.Len(t, reporter.errors, 0) assert.Len(t, reporter.migrated, 1) value, err := rt.ExecuteScript( @@ -947,3 +948,191 @@ func TestContractMigration(t *testing.T) { value, ) } + +// testCompositeValueMigration + +type testCompositeValueMigration struct { +} + +var _ ValueMigration = testCompositeValueMigration{} + +func (testCompositeValueMigration) Name() string { + return "testCompositeValueMigration" +} + +func (m testCompositeValueMigration) Migrate( + _ interpreter.StorageKey, + _ interpreter.StorageMapKey, + value interpreter.Value, + inter *interpreter.Interpreter, +) ( + interpreter.Value, + error, +) { + compositeValue, ok := value.(*interpreter.CompositeValue) + if !ok { + return nil, nil + } + + return interpreter.NewCompositeValue( + inter, + emptyLocationRange, + utils.TestLocation, + "S2", + common.CompositeKindStructure, + nil, + common.Address(compositeValue.StorageAddress()), + ), nil +} + +func TestEmptyIntersectionTypeMigration(t *testing.T) { + + t.Parallel() + + testAddress := common.MustBytesToAddress([]byte{0x1}) + + rt := NewTestInterpreterRuntime() + + runtimeInterface := &TestRuntimeInterface{ + Storage: NewTestLedger(nil, nil), + } + + // Prepare + + storage, inter, err := rt.Storage(runtime.Context{ + Location: utils.TestLocation, + Interface: runtimeInterface, + }) + require.NoError(t, err) + + storageMap := storage.GetStorageMap( + testAddress, + common.PathDomainStorage.Identifier(), + true, + ) + + elaboration := sema.NewElaboration(nil) + + const s1QualifiedIdentifier = "S1" + const s2QualifiedIdentifier = "S2" + + elaboration.SetCompositeType( + utils.TestLocation.TypeID(nil, s1QualifiedIdentifier), + &sema.CompositeType{ + Location: utils.TestLocation, + Members: &sema.StringMemberOrderedMap{}, + Identifier: s1QualifiedIdentifier, + Kind: common.CompositeKindStructure, + }, + ) + + elaboration.SetCompositeType( + utils.TestLocation.TypeID(nil, s2QualifiedIdentifier), + &sema.CompositeType{ + Location: utils.TestLocation, + Members: &sema.StringMemberOrderedMap{}, + Identifier: s2QualifiedIdentifier, + Kind: common.CompositeKindStructure, + }, + ) + + compositeValue := interpreter.NewCompositeValue( + inter, + emptyLocationRange, + utils.TestLocation, + s1QualifiedIdentifier, + common.CompositeKindStructure, + nil, + testAddress, + ) + + inter.Program = &interpreter.Program{ + Elaboration: elaboration, + } + + // NOTE: create an empty intersection type with a legacy type: AnyStruct{} + emptyIntersectionType := interpreter.NewIntersectionStaticType( + nil, + nil, + ) + emptyIntersectionType.LegacyType = interpreter.PrimitiveStaticTypeAnyStruct + + storageMapKey := interpreter.StringStorageMapKey("test") + + dictionaryKey := interpreter.NewUnmeteredStringValue("foo") + + dictionaryValue := interpreter.NewDictionaryValueWithAddress( + inter, + emptyLocationRange, + interpreter.NewDictionaryStaticType( + nil, + interpreter.PrimitiveStaticTypeString, + emptyIntersectionType, + ), + testAddress, + ) + + dictionaryValue.Insert( + inter, + emptyLocationRange, + dictionaryKey, + compositeValue, + ) + + storageMap.WriteValue( + inter, + storageMapKey, + dictionaryValue, + ) + + // Migrate + + reporter := newTestReporter() + + migration := NewStorageMigration(inter, storage) + + migration.Migrate( + &AddressSliceIterator{ + Addresses: []common.Address{ + testAddress, + }, + }, + migration.NewValueMigrationsPathMigrator( + reporter, + testCompositeValueMigration{}, + ), + ) + + err = migration.Commit() + require.NoError(t, err) + + // Assert + + assert.Len(t, reporter.errors, 0) + assert.Len(t, reporter.migrated, 1) + + storageMap = storage.GetStorageMap( + testAddress, + common.PathDomainStorage.Identifier(), + false, + ) + + assert.Equal(t, uint64(1), storageMap.Count()) + + migratedValue := storageMap.ReadValue(nil, storageMapKey) + + require.IsType(t, &interpreter.DictionaryValue{}, migratedValue) + migratedDictionaryValue := migratedValue.(*interpreter.DictionaryValue) + + migratedChildValue, ok := migratedDictionaryValue.Get(inter, emptyLocationRange, dictionaryKey) + require.True(t, ok) + + require.IsType(t, &interpreter.CompositeValue{}, migratedChildValue) + migratedCompositeValue := migratedChildValue.(*interpreter.CompositeValue) + + require.Equal( + t, + s2QualifiedIdentifier, + migratedCompositeValue.QualifiedIdentifier, + ) +} diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index ab3d512be0..9b7dfa914b 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -3321,7 +3321,7 @@ func lookupEntitlement(interpreter *Interpreter, typeID string) (*sema.Entitleme return nil, err } - typ, err := interpreter.getEntitlement(common.TypeID(typeID)) + typ, err := interpreter.GetEntitlementType(common.TypeID(typeID)) if err != nil { return nil, err } @@ -4473,7 +4473,7 @@ func (interpreter *Interpreter) authAccountCheckFunction(addressValue AddressVal ) } -func (interpreter *Interpreter) getEntitlement(typeID common.TypeID) (*sema.EntitlementType, error) { +func (interpreter *Interpreter) GetEntitlementType(typeID common.TypeID) (*sema.EntitlementType, error) { location, qualifiedIdentifier, err := common.DecodeTypeID(interpreter, string(typeID)) if err != nil { return nil, err @@ -4507,7 +4507,7 @@ func (interpreter *Interpreter) getEntitlement(typeID common.TypeID) (*sema.Enti return ty, nil } -func (interpreter *Interpreter) getEntitlementMapType(typeID common.TypeID) (*sema.EntitlementMapType, error) { +func (interpreter *Interpreter) GetEntitlementMapType(typeID common.TypeID) (*sema.EntitlementMapType, error) { location, qualifiedIdentifier, err := common.DecodeTypeID(interpreter, string(typeID)) if err != nil { return nil, err @@ -4546,10 +4546,7 @@ func (interpreter *Interpreter) ConvertStaticToSemaType(staticType StaticType) ( return ConvertStaticToSemaType( config.MemoryGauge, staticType, - interpreter.GetInterfaceType, - interpreter.GetCompositeType, - interpreter.getEntitlement, - interpreter.getEntitlementMapType, + interpreter, ) } @@ -4569,8 +4566,7 @@ func (interpreter *Interpreter) MustConvertStaticAuthorizationToSemaAccess(auth access, err := ConvertStaticAuthorizationToSemaAccess( interpreter, auth, - interpreter.getEntitlement, - interpreter.getEntitlementMapType, + interpreter, ) if err != nil { panic(err) diff --git a/runtime/interpreter/statictype.go b/runtime/interpreter/statictype.go index 8b39e9d38c..dae75efef3 100644 --- a/runtime/interpreter/statictype.go +++ b/runtime/interpreter/statictype.go @@ -972,11 +972,18 @@ func ConvertSemaToStaticType(memoryGauge common.MemoryGauge, t sema.Type) Static } } - return NewIntersectionStaticType( + intersectionType := NewIntersectionStaticType( memoryGauge, intersectedTypes, ) + legacyType := t.LegacyType //nolint:staticcheck + if legacyType != nil { + intersectionType.LegacyType = ConvertSemaToStaticType(memoryGauge, legacyType) + } + + return intersectionType + case *sema.ReferenceType: return ConvertSemaReferenceTypeToStaticReferenceType(memoryGauge, t) @@ -1106,14 +1113,18 @@ func ConvertSemaInterfaceTypeToStaticInterfaceType( func ConvertStaticAuthorizationToSemaAccess( memoryGauge common.MemoryGauge, auth Authorization, - getEntitlement func(typeID common.TypeID) (*sema.EntitlementType, error), - getEntitlementMapType func(typeID common.TypeID) (*sema.EntitlementMapType, error), -) (sema.Access, error) { + handler StaticAuthorizationConversionHandler, +) ( + sema.Access, + error, +) { + switch auth := auth.(type) { case Unauthorized: return sema.UnauthorizedAccess, nil + case EntitlementMapAuthorization: - entitlement, err := getEntitlementMapType(auth.TypeID) + entitlement, err := handler.GetEntitlementMapType(auth.TypeID) if err != nil { return nil, err } @@ -1122,7 +1133,7 @@ func ConvertStaticAuthorizationToSemaAccess( case EntitlementSetAuthorization: var entitlements []*sema.EntitlementType err := auth.Entitlements.ForeachWithError(func(id common.TypeID, value struct{}) error { - entitlement, err := getEntitlement(id) + entitlement, err := handler.GetEntitlementType(id) if err != nil { return err } @@ -1138,29 +1149,42 @@ func ConvertStaticAuthorizationToSemaAccess( panic(errors.NewUnreachableError()) } +type StaticAuthorizationConversionHandler interface { + GetEntitlementType(typeID TypeID) (*sema.EntitlementType, error) + GetEntitlementMapType(typeID TypeID) (*sema.EntitlementMapType, error) +} + +type StaticTypeConversionHandler interface { + StaticAuthorizationConversionHandler + GetInterfaceType(location common.Location, qualifiedIdentifier string, typeID TypeID) (*sema.InterfaceType, error) + GetCompositeType(location common.Location, qualifiedIdentifier string, typeID TypeID) (*sema.CompositeType, error) +} + func ConvertStaticToSemaType( memoryGauge common.MemoryGauge, typ StaticType, - getInterface func(location common.Location, qualifiedIdentifier string, typeID TypeID) (*sema.InterfaceType, error), - getComposite func(location common.Location, qualifiedIdentifier string, typeID TypeID) (*sema.CompositeType, error), - getEntitlement func(typeID TypeID) (*sema.EntitlementType, error), - getEntitlementMapType func(typeID TypeID) (*sema.EntitlementMapType, error), + handler StaticTypeConversionHandler, ) (_ sema.Type, err error) { switch t := typ.(type) { case *CompositeStaticType: - return getComposite(t.Location, t.QualifiedIdentifier, t.TypeID) + return handler.GetCompositeType( + t.Location, + t.QualifiedIdentifier, + t.TypeID, + ) case *InterfaceStaticType: - return getInterface(t.Location, t.QualifiedIdentifier, t.TypeID) + return handler.GetInterfaceType( + t.Location, + t.QualifiedIdentifier, + t.TypeID, + ) case *VariableSizedStaticType: ty, err := ConvertStaticToSemaType( memoryGauge, t.Type, - getInterface, - getComposite, - getEntitlement, - getEntitlementMapType, + handler, ) if err != nil { return nil, err @@ -1171,10 +1195,7 @@ func ConvertStaticToSemaType( ty, err := ConvertStaticToSemaType( memoryGauge, t.Type, - getInterface, - getComposite, - getEntitlement, - getEntitlementMapType, + handler, ) if err != nil { return nil, err @@ -1190,10 +1211,7 @@ func ConvertStaticToSemaType( keyType, err := ConvertStaticToSemaType( memoryGauge, t.KeyType, - getInterface, - getComposite, - getEntitlement, - getEntitlementMapType, + handler, ) if err != nil { return nil, err @@ -1202,10 +1220,7 @@ func ConvertStaticToSemaType( valueType, err := ConvertStaticToSemaType( memoryGauge, t.ValueType, - getInterface, - getComposite, - getEntitlement, - getEntitlementMapType, + handler, ) if err != nil { return nil, err @@ -1221,10 +1236,7 @@ func ConvertStaticToSemaType( elementType, err := ConvertStaticToSemaType( memoryGauge, t.ElementType, - getInterface, - getComposite, - getEntitlement, - getEntitlementMapType, + handler, ) if err != nil { return nil, err @@ -1239,10 +1251,7 @@ func ConvertStaticToSemaType( ty, err := ConvertStaticToSemaType( memoryGauge, t.Type, - getInterface, - getComposite, - getEntitlement, - getEntitlementMapType, + handler, ) if err != nil { return nil, err @@ -1250,6 +1259,19 @@ func ConvertStaticToSemaType( return sema.NewOptionalType(memoryGauge, ty), err case *IntersectionStaticType: + var convertedLegacyType sema.Type + legacyType := t.LegacyType + if legacyType != nil { + convertedLegacyType, err = ConvertStaticToSemaType( + memoryGauge, + legacyType, + handler, + ) + if err != nil { + return nil, err + } + } + var intersectedTypes []*sema.InterfaceType typeCount := len(t.Types) @@ -1257,7 +1279,11 @@ func ConvertStaticToSemaType( intersectedTypes = make([]*sema.InterfaceType, typeCount) for i, typ := range t.Types { - intersectedTypes[i], err = getInterface(typ.Location, typ.QualifiedIdentifier, typ.TypeID) + intersectedTypes[i], err = handler.GetInterfaceType( + typ.Location, + typ.QualifiedIdentifier, + typ.TypeID, + ) if err != nil { return nil, err } @@ -1266,6 +1292,7 @@ func ConvertStaticToSemaType( return sema.NewIntersectionType( memoryGauge, + convertedLegacyType, intersectedTypes, ), nil @@ -1273,10 +1300,7 @@ func ConvertStaticToSemaType( ty, err := ConvertStaticToSemaType( memoryGauge, t.ReferencedType, - getInterface, - getComposite, - getEntitlement, - getEntitlementMapType, + handler, ) if err != nil { return nil, err @@ -1285,8 +1309,7 @@ func ConvertStaticToSemaType( access, err := ConvertStaticAuthorizationToSemaAccess( memoryGauge, t.Authorization, - getEntitlement, - getEntitlementMapType, + handler, ) if err != nil { @@ -1301,10 +1324,7 @@ func ConvertStaticToSemaType( borrowType, err = ConvertStaticToSemaType( memoryGauge, t.BorrowType, - getInterface, - getComposite, - getEntitlement, - getEntitlementMapType, + handler, ) if err != nil { return nil, err diff --git a/runtime/interpreter/statictype_test.go b/runtime/interpreter/statictype_test.go index 4992a37ea3..6397347ce1 100644 --- a/runtime/interpreter/statictype_test.go +++ b/runtime/interpreter/statictype_test.go @@ -1643,9 +1643,9 @@ func TestStaticTypeConversion(t *testing.T) { // Test static to sema - getInterface := test.getInterface - if getInterface == nil { - getInterface = func( + getInterfaceType := test.getInterface + if getInterfaceType == nil { + getInterfaceType = func( _ *testing.T, _ common.Location, _ string, @@ -1656,40 +1656,48 @@ func TestStaticTypeConversion(t *testing.T) { } } - getComposite := test.getComposite - if getComposite == nil { - getComposite = func( + getCompositeType := test.getComposite + if getCompositeType == nil { + getCompositeType = func( _ *testing.T, _ common.Location, _ string, _ TypeID, ) (*sema.CompositeType, error) { - require.FailNow(t, "getComposite should not be called") + require.FailNow(t, "getCompositeType should not be called") return nil, nil } } - getEntitlement := func(_ common.TypeID) (*sema.EntitlementType, error) { - require.FailNow(t, "getComposite should not be called") - return nil, nil - } - - getEntitlementMap := func(_ common.TypeID) (*sema.EntitlementMapType, error) { - require.FailNow(t, "getComposite should not be called") - return nil, nil + handler := staticTypeConversionHandler{ + getInterfaceType: func( + location common.Location, + qualifiedIdentifier string, + typeID TypeID, + ) (*sema.InterfaceType, error) { + return getInterfaceType(t, location, qualifiedIdentifier, typeID) + }, + getCompositeType: func( + location common.Location, + qualifiedIdentifier string, + typeID TypeID, + ) (*sema.CompositeType, error) { + return getCompositeType(t, location, qualifiedIdentifier, typeID) + }, + getEntitlementType: func(_ common.TypeID) (*sema.EntitlementType, error) { + require.FailNow(t, "getEntitlementType should not be called") + return nil, nil + }, + getEntitlementMapType: func(_ common.TypeID) (*sema.EntitlementMapType, error) { + require.FailNow(t, "getEntitlementMapType should not be called") + return nil, nil + }, } convertedSemaType, err := ConvertStaticToSemaType( nil, test.staticType, - func(location common.Location, qualifiedIdentifier string, typeID TypeID) (*sema.InterfaceType, error) { - return getInterface(t, location, qualifiedIdentifier, typeID) - }, - func(location common.Location, qualifiedIdentifier string, typeID TypeID) (*sema.CompositeType, error) { - return getComposite(t, location, qualifiedIdentifier, typeID) - }, - getEntitlement, - getEntitlementMap, + handler, ) require.NoError(t, err) require.Equal(t, @@ -1717,6 +1725,39 @@ func TestStaticTypeConversion(t *testing.T) { } +type staticTypeConversionHandler struct { + getInterfaceType func(location common.Location, qualifiedIdentifier string, typeID TypeID) (*sema.InterfaceType, error) + getCompositeType func(location common.Location, qualifiedIdentifier string, typeID TypeID) (*sema.CompositeType, error) + getEntitlementType func(typeID common.TypeID) (*sema.EntitlementType, error) + getEntitlementMapType func(typeID common.TypeID) (*sema.EntitlementMapType, error) +} + +var _ StaticTypeConversionHandler = staticTypeConversionHandler{} + +func (s staticTypeConversionHandler) GetInterfaceType( + location common.Location, + qualifiedIdentifier string, + typeID TypeID, +) (*sema.InterfaceType, error) { + return s.getInterfaceType(location, qualifiedIdentifier, typeID) +} + +func (s staticTypeConversionHandler) GetCompositeType( + location common.Location, + qualifiedIdentifier string, + typeID TypeID, +) (*sema.CompositeType, error) { + return s.getCompositeType(location, qualifiedIdentifier, typeID) +} + +func (s staticTypeConversionHandler) GetEntitlementType(typeID TypeID) (*sema.EntitlementType, error) { + return s.getEntitlementType(typeID) +} + +func (s staticTypeConversionHandler) GetEntitlementMapType(typeID TypeID) (*sema.EntitlementMapType, error) { + return s.getEntitlementMapType(typeID) +} + func TestIntersectionStaticType_ID(t *testing.T) { t.Parallel() diff --git a/runtime/interpreter/value_pathcapability.go b/runtime/interpreter/value_pathcapability.go index 5c01cae210..910352da2e 100644 --- a/runtime/interpreter/value_pathcapability.go +++ b/runtime/interpreter/value_pathcapability.go @@ -19,6 +19,8 @@ package interpreter import ( + "fmt" + "github.com/onflow/atree" "github.com/onflow/cadence/runtime/common" @@ -62,13 +64,26 @@ func (v *PathCapabilityValue) StaticType(inter *Interpreter) StaticType { func (v *PathCapabilityValue) IsImportable(_ *Interpreter, _ LocationRange) bool { panic(errors.NewUnreachableError()) } - func (v *PathCapabilityValue) String() string { - panic(errors.NewUnreachableError()) + return v.RecursiveString(SeenReferences{}) } -func (v *PathCapabilityValue) RecursiveString(_ SeenReferences) string { - panic(errors.NewUnreachableError()) +func (v *PathCapabilityValue) RecursiveString(seenReferences SeenReferences) string { + borrowType := v.BorrowType + if borrowType == nil { + return fmt.Sprintf( + "Capability(address: %s, path: %s)", + v.Address.RecursiveString(seenReferences), + v.Path.RecursiveString(seenReferences), + ) + } else { + return fmt.Sprintf( + "Capability<%s>(address: %s, path: %s)", + borrowType.String(), + v.Address.RecursiveString(seenReferences), + v.Path.RecursiveString(seenReferences), + ) + } } func (v *PathCapabilityValue) MeteredString(_ common.MemoryGauge, _ SeenReferences) string { diff --git a/runtime/parser/declaration_test.go b/runtime/parser/declaration_test.go index 452628a8cd..6a9b892f7d 100644 --- a/runtime/parser/declaration_test.go +++ b/runtime/parser/declaration_test.go @@ -968,9 +968,9 @@ func TestParseFunctionDeclaration(t *testing.T) { t.Run("without space after return type", func(t *testing.T) { // A brace after the return type is ambiguous: - // It could be the start of a intersection type. + // It could be the start of an intersection type. // However, if there is space after the brace, which is most common - // in function declarations, we consider it not a intersection type + // in function declarations, we consider it not an intersection type t.Parallel() diff --git a/runtime/parser/type.go b/runtime/parser/type.go index c6ca63a254..ea61780627 100644 --- a/runtime/parser/type.go +++ b/runtime/parser/type.go @@ -320,10 +320,10 @@ func defineReferenceType() { func defineIntersectionOrDictionaryType() { // For the null denotation it is not clear after the start - // if it is a intersection type or a dictionary type. + // if it is an intersection type or a dictionary type. // // If a colon is seen it is a dictionary type. - // If no colon is seen it is a intersection type. + // If no colon is seen it is an intersection type. setTypeNullDenotation( lexer.TokenBraceOpen, diff --git a/runtime/sema/check_casting_expression.go b/runtime/sema/check_casting_expression.go index 1ec4cf2a4c..69d2b53a53 100644 --- a/runtime/sema/check_casting_expression.go +++ b/runtime/sema/check_casting_expression.go @@ -204,15 +204,15 @@ func FailableCastCanSucceed(subType, superType Type) bool { switch typedSubType := subType.(type) { case *IntersectionType: - // A intersection type `{Us}` - // is a subtype of a intersection type `{Vs}`: + // An intersection type `{Us}` + // is a subtype of an intersection type `{Vs}`: // if the run-time type conforms to `Vs` // `Us` and `Vs` do *not* have to be subsets. return true case *CompositeType: - // A type `T` is a subtype of a intersection type `{Us}`: + // A type `T` is a subtype of an intersection type `{Us}`: // // When `T != AnyResource && T != AnyStruct && T != Any`: // if `T` conforms to `Us`. @@ -224,7 +224,7 @@ func FailableCastCanSucceed(subType, superType Type) bool { switch subType { case AnyResourceType, AnyStructType, AnyType: - // A type `T` is a subtype of a intersection type `{Us}`: + // A type `T` is a subtype of an intersection type `{Us}`: // if the run-time type conforms to `Vs` return true @@ -234,7 +234,7 @@ func FailableCastCanSucceed(subType, superType Type) bool { switch subType.(type) { case *IntersectionType: - // A intersection type `{Us}` is a subtype of a type `V`: + // An intersection type `{Us}` is a subtype of a type `V`: // if the run-time type is V. return true } @@ -244,7 +244,7 @@ func FailableCastCanSucceed(subType, superType Type) bool { switch superType { case AnyResourceType, AnyStructType: - // A intersection type `{Us}` or a type `T` s a subtype of the type `AnyResource` / `AnyStruct`: + // An intersection type `{Us}` or a type `T` s a subtype of the type `AnyResource` / `AnyStruct`: // if `T` is `AnyType`, or `T` is a subtype of `AnyResource` / `AnyStruct`, or if `Us` are subtypes of `AnyResource` / `AnyStruct` innerSubtype := subType diff --git a/runtime/sema/check_composite_declaration.go b/runtime/sema/check_composite_declaration.go index 11da300672..56a3b0b401 100644 --- a/runtime/sema/check_composite_declaration.go +++ b/runtime/sema/check_composite_declaration.go @@ -2356,7 +2356,7 @@ func (checker *Checker) declareBaseValue(fnAccess Access, baseType Type, attachm if typedBaseType, ok := baseType.(*InterfaceType); ok { // we can't actually have a value of an interface type I, so instead we create a value of {I} // to be referenced by `base` - baseType = NewIntersectionType(checker.memoryGauge, []*InterfaceType{typedBaseType}) + baseType = NewIntersectionType(checker.memoryGauge, nil, []*InterfaceType{typedBaseType}) } // the `base` value in an attachment is entitled to the same entitlements required by the containing function base := NewReferenceType(checker.memoryGauge, fnAccess, baseType) diff --git a/runtime/sema/checker.go b/runtime/sema/checker.go index 54bda84a3c..56a024dbba 100644 --- a/runtime/sema/checker.go +++ b/runtime/sema/checker.go @@ -1002,7 +1002,7 @@ func CheckIntersectionType( panic(errors.NewUnreachableError()) } - return NewIntersectionType(memoryGauge, types) + return NewIntersectionType(memoryGauge, nil, types) } func (checker *Checker) convertIntersectionType(t *ast.IntersectionType) Type { diff --git a/runtime/sema/type.go b/runtime/sema/type.go index 87ffa41065..0481afc307 100644 --- a/runtime/sema/type.go +++ b/runtime/sema/type.go @@ -7659,21 +7659,164 @@ func checkSubTypeWithoutEquality(subType Type, superType Type) bool { return true case *IntersectionType: - switch typedSubType := subType.(type) { - case *IntersectionType: - // A intersection type `{Us}` is a subtype of a intersection type `{Vs}` / `{Vs}` / `{Vs}`: - // when `Vs` is a subset of `Us`. + // TODO: replace with + // + //switch typedSubType := subType.(type) { + //case *IntersectionType: + // + // // An intersection type `{Us}` is a subtype of an intersection type `{Vs}` / `{Vs}` / `{Vs}`: + // // when `Vs` is a subset of `Us`. + // + // return typedSuperType.EffectiveIntersectionSet(). + // IsSubsetOf(typedSubType.EffectiveIntersectionSet()) + // + //case *CompositeType: + // // A type `T` is a subtype of an intersection type `{Us}` / `{Us}` / `{Us}`: + // // when `T` conforms to `Us`. + // + // return typedSuperType.EffectiveIntersectionSet(). + // IsSubsetOf(typedSubType.EffectiveInterfaceConformanceSet()) + //} + + intersectionSuperType := typedSuperType.LegacyType //nolint:staticcheck + + switch intersectionSuperType { + case nil, AnyResourceType, AnyStructType, AnyType: + + switch subType { + case AnyResourceType: + // `AnyResource` is a subtype of an intersection type + // - `AnyResource{Us}`: not statically; + // - `AnyStruct{Us}`: never. + // - `Any{Us}`: not statically; - return typedSuperType.EffectiveIntersectionSet(). - IsSubsetOf(typedSubType.EffectiveIntersectionSet()) + return false - case *CompositeType: - // A type `T` is a subtype of a intersection type `{Us}` / `{Us}` / `{Us}`: - // when `T` conforms to `Us`. + case AnyStructType: + // `AnyStruct` is a subtype of an intersection type + // - `AnyStruct{Us}`: not statically. + // - `AnyResource{Us}`: never; + // - `Any{Us}`: not statically. + + return false + + case AnyType: + // `Any` is a subtype of an intersection type + // - `Any{Us}: not statically.` + // - `AnyStruct{Us}`: never; + // - `AnyResource{Us}`: never; + + return false + } + + switch typedSubType := subType.(type) { + case *IntersectionType: + + // An intersection type `T{Us}` + // is a subtype of an intersection type `AnyResource{Vs}` / `AnyStruct{Vs}` / `Any{Vs}`: + + intersectionSubtype := typedSubType.LegacyType //nolint:staticcheck + switch intersectionSubtype { + case nil: + // An intersection type `{Us}` is a subtype of an intersection type `{Vs}` / `{Vs}` / `{Vs}`: + // when `Vs` is a subset of `Us`. + + return typedSuperType.EffectiveIntersectionSet(). + IsSubsetOf(typedSubType.EffectiveIntersectionSet()) + + case AnyResourceType, AnyStructType, AnyType: + // 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`. + + if intersectionSuperType != nil && + !IsSubType(intersectionSubtype, intersectionSuperType) { + + return false + } + + return typedSuperType.EffectiveIntersectionSet(). + IsSubsetOf(typedSubType.EffectiveIntersectionSet()) + } + + if intersectionSubtype, ok := intersectionSubtype.(*CompositeType); ok { + // 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. + + if intersectionSuperType != nil && + !IsSubType(intersectionSubtype, intersectionSuperType) { + + return false + } + + return typedSuperType.EffectiveIntersectionSet(). + IsSubsetOf(intersectionSubtype.EffectiveInterfaceConformanceSet()) + } + + case *CompositeType: + // A type `T` + // is a subtype of an intersection type `AnyResource{Us}` / `AnyStruct{Us}` / `Any{Us}`: + // if `T` is a subtype of the intersection supertype, + // and `T` conforms to `Us`. + + if intersectionSuperType != nil && + !IsSubType(typedSubType, intersectionSuperType) { + + return false + } + + return typedSuperType.EffectiveIntersectionSet(). + IsSubsetOf(typedSubType.EffectiveInterfaceConformanceSet()) + } + + default: + // Supertype (intersection) has a non-Any* legacy type + + switch typedSubType := subType.(type) { + case *IntersectionType: + + // An intersection type `T{Us}` + // is a subtype of an intersection type `V{Ws}`: + + intersectionSubType := typedSubType.LegacyType //nolint:staticcheck + switch intersectionSubType { + case nil, AnyResourceType, AnyStructType, AnyType: + // When `T == AnyResource || T == AnyStruct || T == Any`: + // not statically. + return false + } + + if intersectionSubType, ok := intersectionSubType.(*CompositeType); ok { + // 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 intersectionSubType == intersectionSuperType + } + + case *CompositeType: + // A type `T` + // is a subtype of an intersection type `U{Vs}`: if `T <: U`. + // + // The owner may freely restrict. + + return IsSubType(typedSubType, intersectionSuperType) + } + + switch subType { + case AnyResourceType, AnyStructType, AnyType: + // A type `T` + // is a subtype of an intersection type `AnyResource{Vs}` / `AnyStruct{Vs}` / `Any{Vs}`: + // not statically. - return typedSuperType.EffectiveIntersectionSet(). - IsSubsetOf(typedSubType.EffectiveInterfaceConformanceSet()) + return false + } } case *CompositeType: @@ -7681,11 +7824,31 @@ func checkSubTypeWithoutEquality(subType Type, superType Type) bool { // NOTE: type equality case (composite type `T` is subtype of composite type `U`) // is already handled at beginning of function - switch subType.(type) { + switch typedSubType := subType.(type) { case *IntersectionType: - // A intersection type `{Us}` is never a subtype of a type `V`: - return false + // TODO: bring back once legacy type is removed + // An intersection type `{Us}` is never a subtype of a type `V`: + //return false + + // TODO: remove support for legacy type + // An intersection type `T{Us}` + // is a subtype of a type `V`: + + legacyType := typedSubType.LegacyType + switch legacyType { + case nil, AnyResourceType, AnyStructType, AnyType: + // When `T == AnyResource || T == AnyStruct || T == Any`: not statically. + return false + } + + if intersectionSubType, ok := legacyType.(*CompositeType); ok { + // When `T != AnyResource && T != AnyStruct`: if `T == V`. + // + // The owner may freely unrestrict. + + return intersectionSubType == typedSuperType + } case *CompositeType: // Non-equal composite types are never subtypes of each other @@ -7697,7 +7860,7 @@ func checkSubTypeWithoutEquality(subType Type, superType Type) bool { switch typedSubType := subType.(type) { case *CompositeType: - // A composite type `T` is a subtype of a interface type `V`: + // A composite type `T` is a subtype of an interface type `V`: // if `T` conforms to `V`, and `V` and `T` are of the same kind if typedSubType.Kind != typedSuperType.CompositeKind { @@ -7707,15 +7870,17 @@ func checkSubTypeWithoutEquality(subType Type, superType Type) bool { return typedSubType.EffectiveInterfaceConformanceSet(). Contains(typedSuperType) - // An interface type is a supertype of a intersection type if at least one value + // An interface type is a supertype of an intersection type if at least one value // in the intersection set is a subtype of the interface supertype. - - // This particular case comes up when checking attachment access; enabling the following expression to typechecking: - // resource interface I { /* ... */ } - // attachment A for I { /* ... */ } - - // let i : {I} = ... // some operation constructing `i` - // let a = i[A] // must here check that `i`'s type is a subtype of `A`'s base type, or that {I} <: I + // + // This particular case comes up when checking attachment access; + // enabling the following expression to type-checking: + // + // resource interface I { /* ... */ } + // attachment A for I { /* ... */ } + // + // let i : {I} = ... // some operation constructing `i` + // let a = i[A] // must here check that `i`'s type is a subtype of `A`'s base type, or that {I} <: I case *IntersectionType: return typedSubType.EffectiveIntersectionSet().Contains(typedSuperType) @@ -7982,12 +8147,15 @@ type IntersectionType struct { memberResolvers map[string]MemberResolver memberResolversOnce sync.Once supportedEntitlements *EntitlementOrderedSet + // Deprecated + LegacyType Type } var _ Type = &IntersectionType{} -func NewIntersectionType(memoryGauge common.MemoryGauge, types []*InterfaceType) *IntersectionType { - if len(types) == 0 { +// TODO: remove `legacyType` once all uses of it are removed +func NewIntersectionType(memoryGauge common.MemoryGauge, legacyType Type, types []*InterfaceType) *IntersectionType { + if len(types) == 0 && legacyType == nil { panic(errors.NewUnreachableError()) } @@ -8000,7 +8168,8 @@ func NewIntersectionType(memoryGauge common.MemoryGauge, types []*InterfaceType) common.UseMemory(memoryGauge, entriesUsage) return &IntersectionType{ - Types: types, + Types: types, + LegacyType: legacyType, //nolint:staticcheck } } @@ -8201,6 +8370,7 @@ func (t *IntersectionType) Map(gauge common.MemoryGauge, typeParamMap map[*TypeP return f(NewIntersectionType( gauge, + t.LegacyType, //nolint:staticcheck intersectionTypes, )) } @@ -8215,8 +8385,8 @@ func (t *IntersectionType) initializeMemberResolvers() { memberResolvers := map[string]MemberResolver{} - // Return the members of all typs. - // The invariant that typs may not have overlapping members is not checked here, + // Return the members of all types. + // The invariant that types may not have overlapping members is not checked here, // but implicitly when the resource declaration's conformances are checked. for _, typ := range t.Types { @@ -8262,11 +8432,14 @@ func (t *IntersectionType) Resolve(_ *TypeParameterTypeOrderedMap) Type { return t } -// intersection types must be type indexable, because this is how we handle access control for attachments. +// Intersection types must be type indexable, because this is how we handle access control for attachments. // Specifically, because in `v[A]`, `v` must be a subtype of `A`'s declared base, -// if `v` is a intersection type `{I}`, only attachments declared for `I` or a supertype can be accessed on `v`. +// if `v` is an intersection type `{I}`, only attachments declared for `I` or a supertype can be accessed on `v`. +// // Attachments declared for concrete types implementing `I` cannot be accessed. -// A good elucidating example here is that an attachment declared for `Vault` cannot be accessed on a value of type `&{Provider}` +// +// A good elucidating example here is that an attachment declared for `Vault` +// cannot be accessed on a value of type `&{Provider}` func (t *IntersectionType) isTypeIndexableType() bool { // resources and structs only can be indexed for attachments, but all intersection types // are necessarily structs and resources, we return true diff --git a/runtime/sema/type_test.go b/runtime/sema/type_test.go index c4c7a8e19b..1a30e919b3 100644 --- a/runtime/sema/type_test.go +++ b/runtime/sema/type_test.go @@ -2253,7 +2253,7 @@ func TestMapType(t *testing.T) { for _, i := range typ.Types { interfaces = append(interfaces, &InterfaceType{Identifier: i.Identifier + "f"}) } - return NewIntersectionType(nil, interfaces) + return NewIntersectionType(nil, nil, interfaces) } return ty } @@ -2311,6 +2311,7 @@ func TestMapType(t *testing.T) { t.Parallel() original := NewIntersectionType( + nil, nil, []*InterfaceType{ {Identifier: "foo"}, @@ -2318,6 +2319,7 @@ func TestMapType(t *testing.T) { }, ) mapped := NewIntersectionType( + nil, nil, []*InterfaceType{ {Identifier: "foof"}, @@ -2646,6 +2648,7 @@ func TestIntersectionType_ID(t *testing.T) { t.Parallel() intersectionType := NewIntersectionType( + nil, nil, []*InterfaceType{ { @@ -2664,6 +2667,7 @@ func TestIntersectionType_ID(t *testing.T) { t.Parallel() intersectionType := NewIntersectionType( + nil, nil, []*InterfaceType{ // NOTE: order @@ -2700,6 +2704,7 @@ func TestIntersectionType_ID(t *testing.T) { interfaceType2.SetContainerType(containerType) intersectionType := NewIntersectionType( + nil, nil, []*InterfaceType{ // NOTE: order @@ -2724,6 +2729,7 @@ func TestIntersectionType_String(t *testing.T) { t.Parallel() intersectionType := NewIntersectionType( + nil, nil, []*InterfaceType{ { @@ -2742,6 +2748,7 @@ func TestIntersectionType_String(t *testing.T) { t.Parallel() intersectionType := NewIntersectionType( + nil, nil, []*InterfaceType{ // NOTE: order @@ -2777,6 +2784,7 @@ func TestIntersectionType_QualifiedString(t *testing.T) { t.Parallel() intersectionType := NewIntersectionType( + nil, nil, []*InterfaceType{ { @@ -2795,6 +2803,7 @@ func TestIntersectionType_QualifiedString(t *testing.T) { t.Parallel() intersectionType := NewIntersectionType( + nil, nil, []*InterfaceType{ // NOTE: order @@ -2831,6 +2840,7 @@ func TestIntersectionType_QualifiedString(t *testing.T) { interfaceType2.SetContainerType(containerType) intersectionType := NewIntersectionType( + nil, nil, []*InterfaceType{ // NOTE: order diff --git a/runtime/tests/checker/resources_test.go b/runtime/tests/checker/resources_test.go index 9f79e0f1bc..7dbbc05a3c 100644 --- a/runtime/tests/checker/resources_test.go +++ b/runtime/tests/checker/resources_test.go @@ -3345,7 +3345,7 @@ func TestCheckInvalidResourceInterfaceUseAsType(t *testing.T) { } // TestCheckResourceInterfaceUseAsType test if a resource -// is a subtype of a intersection AnyResource type. +// is a subtype of an intersection AnyResource type. func TestCheckResourceInterfaceUseAsType(t *testing.T) { t.Parallel()