From a1f6520e38be75f5ba8d9ea2f4b7547682467ab8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Mon, 26 Feb 2024 15:01:46 -0800 Subject: [PATCH 01/12] add test case --- migrations/migration_test.go | 171 ++++++++++++++++++++++++++++++++--- 1 file changed, 159 insertions(+), 12 deletions(-) diff --git a/migrations/migration_test.go b/migrations/migration_test.go index e759fde504..51b74793f4 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, ) } @@ -713,7 +714,7 @@ func TestMigrationError(t *testing.T) { "testInt8Migration", }, }, - 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,149 @@ func TestContractMigration(t *testing.T) { value, ) } + +func TestDictionaryDelete(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 rQualifiedIdentifier = "R" + + elaboration.SetCompositeType( + utils.TestLocation.TypeID(nil, rQualifiedIdentifier), + &sema.CompositeType{ + Location: utils.TestLocation, + Members: &sema.StringMemberOrderedMap{}, + Identifier: rQualifiedIdentifier, + Kind: common.CompositeKindResource, + }, + ) + + inter.Program = &interpreter.Program{ + Elaboration: elaboration, + } + + emptyIntersectionType := interpreter.NewIntersectionStaticType( + nil, + nil, + ) + emptyIntersectionType.LegacyType = interpreter.PrimitiveStaticTypeAnyResource + + storageMapKey := interpreter.StringStorageMapKey("test") + + const fieldName = "bar" + + compositeValue := interpreter.NewCompositeValue( + inter, + emptyLocationRange, + utils.TestLocation, + rQualifiedIdentifier, + common.CompositeKindResource, + []interpreter.CompositeField{ + { + Name: fieldName, + Value: interpreter.NewUnmeteredInt8Value(5), + }, + }, + testAddress, + ) + + dictionaryKey := interpreter.NewUnmeteredStringValue("foo") + + dictionaryValue := interpreter.NewDictionaryValueWithAddress( + inter, + emptyLocationRange, + interpreter.NewDictionaryStaticType( + nil, + interpreter.PrimitiveStaticTypeString, + emptyIntersectionType, + ), + testAddress, + dictionaryKey, + compositeValue, + ) + + storageMap.WriteValue( + inter, + storageMapKey, + dictionaryValue, + ) + + // Migrate + + reporter := newTestReporter() + + migration := NewStorageMigration(inter, storage) + + migration.Migrate( + &AddressSliceIterator{ + Addresses: []common.Address{ + testAddress, + }, + }, + migration.NewValueMigrationsPathMigrator( + reporter, + testInt8Migration{}, + ), + ) + + 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) + + migratedIntegerValue := migratedCompositeValue.GetField(inter, emptyLocationRange, fieldName) + require.IsType(t, interpreter.Int8Value(0), migratedIntegerValue) + migratedIntegerValueInt8 := migratedIntegerValue.(interpreter.Int8Value) + + require.Equal( + t, + interpreter.NewUnmeteredInt8Value(15), + migratedIntegerValueInt8, + ) +} From fdaf0398db3ac64f968ec32c31e5c610b663b275 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Mon, 26 Feb 2024 15:34:40 -0800 Subject: [PATCH 02/12] add DictionaryValue.InsertUnchecked for testing purposes --- runtime/interpreter/value.go | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index a0c06356b0..4b086ecda9 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -19121,15 +19121,30 @@ func (v *DictionaryValue) Insert( interpreter.validateMutation(v.StorageID(), locationRange) + interpreter.checkContainerMutation(v.Type.KeyType, keyValue, locationRange) + interpreter.checkContainerMutation(v.Type.ValueType, value, locationRange) + + return v.InsertUnchecked( + interpreter, + locationRange, + keyValue, + value, + ) +} + +// Deprecated: Use Insert instead +func (v *DictionaryValue) InsertUnchecked( + interpreter *Interpreter, + locationRange LocationRange, + keyValue, value Value, +) OptionalValue { + // length increases by 1 dataSlabs, metaDataSlabs := common.AdditionalAtreeMemoryUsage(v.dictionary.Count(), v.elementSize, false) common.UseMemory(interpreter, common.AtreeMapElementOverhead) common.UseMemory(interpreter, dataSlabs) common.UseMemory(interpreter, metaDataSlabs) - interpreter.checkContainerMutation(v.Type.KeyType, keyValue, locationRange) - interpreter.checkContainerMutation(v.Type.ValueType, value, locationRange) - address := v.dictionary.Address() preventTransfer := map[atree.StorageID]struct{}{ From 628d4febc249693144f11c3ee1d6ffa3fe4c44f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Mon, 26 Feb 2024 15:34:47 -0800 Subject: [PATCH 03/12] simplify test --- migrations/migration_test.go | 36 +++++++++++------------------------- 1 file changed, 11 insertions(+), 25 deletions(-) diff --git a/migrations/migration_test.go b/migrations/migration_test.go index 51b74793f4..2a0fd4a95a 100644 --- a/migrations/migration_test.go +++ b/migrations/migration_test.go @@ -997,27 +997,10 @@ func TestDictionaryDelete(t *testing.T) { nil, nil, ) - emptyIntersectionType.LegacyType = interpreter.PrimitiveStaticTypeAnyResource + emptyIntersectionType.LegacyType = interpreter.PrimitiveStaticTypeAnyStruct storageMapKey := interpreter.StringStorageMapKey("test") - const fieldName = "bar" - - compositeValue := interpreter.NewCompositeValue( - inter, - emptyLocationRange, - utils.TestLocation, - rQualifiedIdentifier, - common.CompositeKindResource, - []interpreter.CompositeField{ - { - Name: fieldName, - Value: interpreter.NewUnmeteredInt8Value(5), - }, - }, - testAddress, - ) - dictionaryKey := interpreter.NewUnmeteredStringValue("foo") dictionaryValue := interpreter.NewDictionaryValueWithAddress( @@ -1029,8 +1012,15 @@ func TestDictionaryDelete(t *testing.T) { emptyIntersectionType, ), testAddress, + ) + + // NOTE: insert the value into the dictionary, + // but use the unchecked variant to avoid type loading + dictionaryValue.InsertUnchecked( // nolint:staticcheck + inter, + emptyLocationRange, dictionaryKey, - compositeValue, + interpreter.NewUnmeteredInt8Value(5), ) storageMap.WriteValue( @@ -1081,12 +1071,8 @@ func TestDictionaryDelete(t *testing.T) { migratedChildValue, ok := migratedDictionaryValue.Get(inter, emptyLocationRange, dictionaryKey) require.True(t, ok) - require.IsType(t, &interpreter.CompositeValue{}, migratedChildValue) - migratedCompositeValue := migratedChildValue.(*interpreter.CompositeValue) - - migratedIntegerValue := migratedCompositeValue.GetField(inter, emptyLocationRange, fieldName) - require.IsType(t, interpreter.Int8Value(0), migratedIntegerValue) - migratedIntegerValueInt8 := migratedIntegerValue.(interpreter.Int8Value) + require.IsType(t, interpreter.Int8Value(0), migratedChildValue) + migratedIntegerValueInt8 := migratedChildValue.(interpreter.Int8Value) require.Equal( t, From 4a6c7bb095b0229c96aa3a3c5ffc8b02fc28a048 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 27 Feb 2024 14:01:20 -0800 Subject: [PATCH 04/12] fix test --- migrations/entitlements/migration_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/migrations/entitlements/migration_test.go b/migrations/entitlements/migration_test.go index a46ae9fe58..6b2125c257 100644 --- a/migrations/entitlements/migration_test.go +++ b/migrations/entitlements/migration_test.go @@ -1313,7 +1313,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 +1346,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) }) } }) From 39647baa6229bc5e5657d582bf3feed13a9f30a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 27 Feb 2024 14:02:21 -0800 Subject: [PATCH 05/12] fix test --- migrations/migration_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/migrations/migration_test.go b/migrations/migration_test.go index 2a0fd4a95a..098a3aa858 100644 --- a/migrations/migration_test.go +++ b/migrations/migration_test.go @@ -703,7 +703,7 @@ func TestMigrationError(t *testing.T) { map[struct { interpreter.StorageKey interpreter.StorageMapKey - }][]string{ + }][]error{ { StorageKey: interpreter.StorageKey{ Address: account, @@ -711,7 +711,7 @@ func TestMigrationError(t *testing.T) { }, StorageMapKey: interpreter.StringStorageMapKey("int8_value"), }: { - "testInt8Migration", + errors.New("error occurred while migrating int8"), }, }, reporter.errors, From 4f272f3a558f07aa694f5472dbdb4890b0db27bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 27 Feb 2024 14:04:15 -0800 Subject: [PATCH 06/12] bring back intersection legacy type and subtype checking for it --- migrations/entitlements/migration_test.go | 4 + runtime/interpreter/interpreter.go | 14 +- runtime/interpreter/statictype.go | 114 ++++++---- runtime/interpreter/statictype_test.go | 87 +++++-- runtime/parser/declaration_test.go | 4 +- runtime/parser/type.go | 4 +- runtime/sema/check_casting_expression.go | 12 +- runtime/sema/check_composite_declaration.go | 2 +- runtime/sema/checker.go | 2 +- runtime/sema/type.go | 237 +++++++++++++++++--- runtime/sema/type_test.go | 12 +- runtime/tests/checker/resources_test.go | 2 +- 12 files changed, 370 insertions(+), 124 deletions(-) diff --git a/migrations/entitlements/migration_test.go b/migrations/entitlements/migration_test.go index 6b2125c257..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, diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index 0f973a34b9..969ba38c54 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 } @@ -4458,7 +4458,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 @@ -4492,7 +4492,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 @@ -4531,10 +4531,7 @@ func (interpreter *Interpreter) ConvertStaticToSemaType(staticType StaticType) ( return ConvertStaticToSemaType( config.MemoryGauge, staticType, - interpreter.GetInterfaceType, - interpreter.GetCompositeType, - interpreter.getEntitlement, - interpreter.getEntitlementMapType, + interpreter, ) } @@ -4554,8 +4551,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/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..9c42adc434 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,33 @@ 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 + if legacyType != nil { + switch legacyType { + case 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 +7862,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 +7872,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 +8149,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 +8170,8 @@ func NewIntersectionType(memoryGauge common.MemoryGauge, types []*InterfaceType) common.UseMemory(memoryGauge, entriesUsage) return &IntersectionType{ - Types: types, + Types: types, + LegacyType: legacyType, //nolint:staticcheck } } @@ -8201,6 +8372,7 @@ func (t *IntersectionType) Map(gauge common.MemoryGauge, typeParamMap map[*TypeP return f(NewIntersectionType( gauge, + t.LegacyType, //nolint:staticcheck intersectionTypes, )) } @@ -8215,8 +8387,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 +8434,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() From c49cdab6fdb5f64b9aacf198698d08ace8e6c524 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 27 Feb 2024 14:16:16 -0800 Subject: [PATCH 07/12] simplify --- runtime/sema/type.go | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/runtime/sema/type.go b/runtime/sema/type.go index 9c42adc434..0481afc307 100644 --- a/runtime/sema/type.go +++ b/runtime/sema/type.go @@ -7836,20 +7836,18 @@ func checkSubTypeWithoutEquality(subType Type, superType Type) bool { // is a subtype of a type `V`: legacyType := typedSubType.LegacyType - if legacyType != nil { - switch legacyType { - case AnyResourceType, AnyStructType, AnyType: - // When `T == AnyResource || T == AnyStruct || T == Any`: not statically. - return false - } + 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. + if intersectionSubType, ok := legacyType.(*CompositeType); ok { + // When `T != AnyResource && T != AnyStruct`: if `T == V`. + // + // The owner may freely unrestrict. - return intersectionSubType == typedSuperType - } + return intersectionSubType == typedSuperType } case *CompositeType: From 09364d8d8afc42ce0867bc790eb89f4be54c02b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 27 Feb 2024 14:16:42 -0800 Subject: [PATCH 08/12] implement PathCapabilityValue.String for debugging/testing purposes --- runtime/interpreter/value_pathcapability.go | 23 +++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/runtime/interpreter/value_pathcapability.go b/runtime/interpreter/value_pathcapability.go index 00dcc31939..ee0dd96b4d 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) 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 { From 3800a1c89b77572a74bb2db33b53b06b7b617b21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 27 Feb 2024 14:40:29 -0800 Subject: [PATCH 09/12] update test --- migrations/migration_test.go | 83 ++++++++++++++++++++++++++++++------ 1 file changed, 69 insertions(+), 14 deletions(-) diff --git a/migrations/migration_test.go b/migrations/migration_test.go index 098a3aa858..40e23852be 100644 --- a/migrations/migration_test.go +++ b/migrations/migration_test.go @@ -949,7 +949,43 @@ func TestContractMigration(t *testing.T) { ) } -func TestDictionaryDelete(t *testing.T) { +// 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() @@ -977,18 +1013,39 @@ func TestDictionaryDelete(t *testing.T) { elaboration := sema.NewElaboration(nil) - const rQualifiedIdentifier = "R" + 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, rQualifiedIdentifier), + utils.TestLocation.TypeID(nil, s2QualifiedIdentifier), &sema.CompositeType{ Location: utils.TestLocation, Members: &sema.StringMemberOrderedMap{}, - Identifier: rQualifiedIdentifier, - Kind: common.CompositeKindResource, + Identifier: s2QualifiedIdentifier, + Kind: common.CompositeKindStructure, }, ) + compositeValue := interpreter.NewCompositeValue( + inter, + emptyLocationRange, + utils.TestLocation, + s1QualifiedIdentifier, + common.CompositeKindStructure, + nil, + testAddress, + ) + inter.Program = &interpreter.Program{ Elaboration: elaboration, } @@ -1014,13 +1071,11 @@ func TestDictionaryDelete(t *testing.T) { testAddress, ) - // NOTE: insert the value into the dictionary, - // but use the unchecked variant to avoid type loading - dictionaryValue.InsertUnchecked( // nolint:staticcheck + dictionaryValue.Insert( inter, emptyLocationRange, dictionaryKey, - interpreter.NewUnmeteredInt8Value(5), + compositeValue, ) storageMap.WriteValue( @@ -1043,7 +1098,7 @@ func TestDictionaryDelete(t *testing.T) { }, migration.NewValueMigrationsPathMigrator( reporter, - testInt8Migration{}, + testCompositeValueMigration{}, ), ) @@ -1071,12 +1126,12 @@ func TestDictionaryDelete(t *testing.T) { migratedChildValue, ok := migratedDictionaryValue.Get(inter, emptyLocationRange, dictionaryKey) require.True(t, ok) - require.IsType(t, interpreter.Int8Value(0), migratedChildValue) - migratedIntegerValueInt8 := migratedChildValue.(interpreter.Int8Value) + require.IsType(t, &interpreter.CompositeValue{}, migratedChildValue) + migratedCompositeValue := migratedChildValue.(*interpreter.CompositeValue) require.Equal( t, - interpreter.NewUnmeteredInt8Value(15), - migratedIntegerValueInt8, + s2QualifiedIdentifier, + migratedCompositeValue.QualifiedIdentifier, ) } From f2fdef6ee5283e4fc3bd6ddeaa8d9abadda65042 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 27 Feb 2024 14:40:39 -0800 Subject: [PATCH 10/12] revert introduction of InsertUnchecked --- runtime/interpreter/value.go | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index 4b086ecda9..bdd8d5d6d1 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -19124,21 +19124,6 @@ func (v *DictionaryValue) Insert( interpreter.checkContainerMutation(v.Type.KeyType, keyValue, locationRange) interpreter.checkContainerMutation(v.Type.ValueType, value, locationRange) - return v.InsertUnchecked( - interpreter, - locationRange, - keyValue, - value, - ) -} - -// Deprecated: Use Insert instead -func (v *DictionaryValue) InsertUnchecked( - interpreter *Interpreter, - locationRange LocationRange, - keyValue, value Value, -) OptionalValue { - // length increases by 1 dataSlabs, metaDataSlabs := common.AdditionalAtreeMemoryUsage(v.dictionary.Count(), v.elementSize, false) common.UseMemory(interpreter, common.AtreeMapElementOverhead) From cff68f0adcd4e6b20699e67f6e22f841f60ed4f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 27 Feb 2024 14:42:30 -0800 Subject: [PATCH 11/12] add comment --- migrations/migration_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/migrations/migration_test.go b/migrations/migration_test.go index 40e23852be..fd28d99748 100644 --- a/migrations/migration_test.go +++ b/migrations/migration_test.go @@ -1050,6 +1050,7 @@ func TestEmptyIntersectionTypeMigration(t *testing.T) { Elaboration: elaboration, } + // NOTE: create an empty intersection type with a legacy type: AnyStruct{} emptyIntersectionType := interpreter.NewIntersectionStaticType( nil, nil, From 288f36d5f6caf008eb82bbe91eb9c60c463a702d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 27 Feb 2024 14:43:59 -0800 Subject: [PATCH 12/12] revert change of order --- runtime/interpreter/value.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index bdd8d5d6d1..a0c06356b0 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -19121,15 +19121,15 @@ func (v *DictionaryValue) Insert( interpreter.validateMutation(v.StorageID(), locationRange) - interpreter.checkContainerMutation(v.Type.KeyType, keyValue, locationRange) - interpreter.checkContainerMutation(v.Type.ValueType, value, locationRange) - // length increases by 1 dataSlabs, metaDataSlabs := common.AdditionalAtreeMemoryUsage(v.dictionary.Count(), v.elementSize, false) common.UseMemory(interpreter, common.AtreeMapElementOverhead) common.UseMemory(interpreter, dataSlabs) common.UseMemory(interpreter, metaDataSlabs) + interpreter.checkContainerMutation(v.Type.KeyType, keyValue, locationRange) + interpreter.checkContainerMutation(v.Type.ValueType, value, locationRange) + address := v.dictionary.Address() preventTransfer := map[atree.StorageID]struct{}{