diff --git a/migrations/entitlements/migration_test.go b/migrations/entitlements/migration_test.go index 0b43d010cb..73f987485e 100644 --- a/migrations/entitlements/migration_test.go +++ b/migrations/entitlements/migration_test.go @@ -28,7 +28,7 @@ import ( "github.com/onflow/cadence" "github.com/onflow/cadence/migrations" - "github.com/onflow/cadence/migrations/account_type" + "github.com/onflow/cadence/migrations/statictypes" "github.com/onflow/cadence/runtime" "github.com/onflow/cadence/runtime/ast" "github.com/onflow/cadence/runtime/common" @@ -1935,7 +1935,7 @@ func TestConvertMigratedAccountTypes(t *testing.T) { ), ) - newValue, err := account_type.NewAccountTypeMigration().Migrate( + newValue, err := statictypes.NewStaticTypeMigration().Migrate( interpreter.AddressPath{ Address: common.ZeroAddress, Path: interpreter.EmptyPathValue, diff --git a/migrations/account_type/migration_test.go b/migrations/statictypes/account_type_migration_test.go similarity index 98% rename from migrations/account_type/migration_test.go rename to migrations/statictypes/account_type_migration_test.go index 7d44372610..d4190bc8e6 100644 --- a/migrations/account_type/migration_test.go +++ b/migrations/statictypes/account_type_migration_test.go @@ -16,7 +16,7 @@ * limitations under the License. */ -package account_type +package statictypes import ( "fmt" @@ -61,7 +61,7 @@ func (t *testReporter) Error( ) { } -func TestTypeValueMigration(t *testing.T) { +func TestAccountTypeInTypeValueMigration(t *testing.T) { t.Parallel() account := common.Address{0x42} @@ -370,7 +370,7 @@ func TestTypeValueMigration(t *testing.T) { }, migration.NewValueMigrationsPathMigrator( reporter, - NewAccountTypeMigration(), + NewStaticTypeMigration(), ), ) @@ -445,7 +445,7 @@ func storeTypeValue( ) } -func TestNestedTypeValueMigration(t *testing.T) { +func TestAccountTypeInNestedTypeValueMigration(t *testing.T) { t.Parallel() account := common.Address{0x42} @@ -694,7 +694,7 @@ func TestNestedTypeValueMigration(t *testing.T) { }, migration.NewValueMigrationsPathMigrator( nil, - NewAccountTypeMigration(), + NewStaticTypeMigration(), ), ) @@ -726,7 +726,7 @@ func TestNestedTypeValueMigration(t *testing.T) { } } -func TestValuesWithStaticTypeMigration(t *testing.T) { +func TestMigratingValuesWithAccountStaticType(t *testing.T) { t.Parallel() @@ -840,7 +840,7 @@ func TestValuesWithStaticTypeMigration(t *testing.T) { }, migration.NewValueMigrationsPathMigrator( nil, - NewAccountTypeMigration(), + NewStaticTypeMigration(), ), ) @@ -874,7 +874,7 @@ func TestValuesWithStaticTypeMigration(t *testing.T) { var testAddress = common.Address{0x42} -func TestRehash(t *testing.T) { +func TestAccountTypeRehash(t *testing.T) { t.Parallel() @@ -981,7 +981,7 @@ func TestRehash(t *testing.T) { }, migration.NewValueMigrationsPathMigrator( reporter, - NewAccountTypeMigration(), + NewStaticTypeMigration(), ), ) diff --git a/migrations/statictypes/composite_type_migration_test.go b/migrations/statictypes/composite_type_migration_test.go new file mode 100644 index 0000000000..aa8ef68120 --- /dev/null +++ b/migrations/statictypes/composite_type_migration_test.go @@ -0,0 +1,214 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package statictypes + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/onflow/cadence/migrations" + "github.com/onflow/cadence/runtime" + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/errors" + "github.com/onflow/cadence/runtime/interpreter" + . "github.com/onflow/cadence/runtime/tests/runtime_utils" + "github.com/onflow/cadence/runtime/tests/utils" +) + +func TestCompositeAndInterfaceTypeMigration(t *testing.T) { + t.Parallel() + + pathDomain := common.PathDomainPublic + + type testCase struct { + storedType interpreter.StaticType + expectedType interpreter.StaticType + } + + newCompositeType := func() interpreter.StaticType { + return interpreter.NewCompositeStaticType( + nil, + nil, + "Bar", + common.NewTypeIDFromQualifiedName( + nil, + fooAddressLocation, + fooBarQualifiedIdentifier, + ), + ) + } + + newInterfaceType := func() interpreter.StaticType { + return interpreter.NewInterfaceStaticType( + nil, + nil, + "Baz", + common.NewTypeIDFromQualifiedName( + nil, + fooAddressLocation, + fooBazQualifiedIdentifier, + ), + ) + } + + testCases := map[string]testCase{ + // base cases + "compositeToInterface": { + storedType: newCompositeType(), + expectedType: newInterfaceType(), + }, + "interfaceToComposite": { + storedType: newInterfaceType(), + expectedType: newCompositeType(), + }, + // optional + "optional": { + storedType: interpreter.NewOptionalStaticType(nil, newInterfaceType()), + expectedType: interpreter.NewOptionalStaticType(nil, newCompositeType()), + }, + // array + "array": { + storedType: interpreter.NewConstantSizedStaticType(nil, newInterfaceType(), 3), + expectedType: interpreter.NewConstantSizedStaticType(nil, newCompositeType(), 3), + }, + // dictionary + "dictionary": { + storedType: interpreter.NewDictionaryStaticType(nil, newInterfaceType(), newInterfaceType()), + expectedType: interpreter.NewDictionaryStaticType(nil, newCompositeType(), newCompositeType()), + }, + } + + // Store values + + ledger := NewTestLedger(nil, nil) + storage := runtime.NewStorage(ledger, nil) + + inter, err := interpreter.NewInterpreter( + nil, + utils.TestLocation, + &interpreter.Config{ + Storage: storage, + AtreeValueValidationEnabled: false, + AtreeStorageValidationEnabled: true, + }, + ) + require.NoError(t, err) + + for name, testCase := range testCases { + storeTypeValue( + inter, + testAddress, + pathDomain, + name, + testCase.storedType, + ) + } + + err = storage.Commit(inter, true) + require.NoError(t, err) + + // Migrate + + migration := migrations.NewStorageMigration(inter, storage) + + reporter := newTestReporter() + + barStaticType := newCompositeType() + bazStaticType := newInterfaceType() + + migration.Migrate( + &migrations.AddressSliceIterator{ + Addresses: []common.Address{ + testAddress, + }, + }, + migration.NewValueMigrationsPathMigrator( + reporter, + NewStaticTypeMigration(). + WithCompositeTypeConverter( + func(staticType *interpreter.CompositeStaticType) interpreter.StaticType { + if staticType.Equal(barStaticType) { + return bazStaticType + } else { + panic(errors.NewUnreachableError()) + } + }, + ). + WithInterfaceTypeConverter( + func(staticType *interpreter.InterfaceStaticType) interpreter.StaticType { + if staticType.Equal(bazStaticType) { + return barStaticType + } else { + panic(errors.NewUnreachableError()) + } + }, + ), + ), + ) + + err = migration.Commit() + require.NoError(t, err) + + // Check reported migrated paths + for identifier, test := range testCases { + addressPath := interpreter.AddressPath{ + Address: testAddress, + Path: interpreter.PathValue{ + Domain: pathDomain, + Identifier: identifier, + }, + } + + if test.expectedType == nil { + assert.NotContains(t, reporter.migratedPaths, addressPath) + } else { + assert.Contains(t, reporter.migratedPaths, addressPath) + } + } + + // Assert the migrated values. + // Traverse through the storage and see if the values are updated now. + + storageMap := storage.GetStorageMap(testAddress, pathDomain.Identifier(), false) + require.NotNil(t, storageMap) + require.Greater(t, storageMap.Count(), uint64(0)) + + iterator := storageMap.Iterator(inter) + + for key, value := iterator.Next(); key != nil; key, value = iterator.Next() { + identifier := string(key.(interpreter.StringAtreeValue)) + + t.Run(identifier, func(t *testing.T) { + testCase, ok := testCases[identifier] + require.True(t, ok) + + var expectedType interpreter.StaticType + if testCase.expectedType != nil { + expectedType = testCase.expectedType + } else { + expectedType = testCase.storedType + } + + expectedValue := interpreter.NewTypeValue(nil, expectedType) + utils.AssertValuesEqual(t, inter, expectedValue, value) + }) + } +} diff --git a/migrations/account_type/dummy_static_type.go b/migrations/statictypes/dummy_statictype.go similarity index 98% rename from migrations/account_type/dummy_static_type.go rename to migrations/statictypes/dummy_statictype.go index c6ebecf280..287f1301fb 100644 --- a/migrations/account_type/dummy_static_type.go +++ b/migrations/statictypes/dummy_statictype.go @@ -16,7 +16,7 @@ * limitations under the License. */ -package account_type +package statictypes import ( "github.com/onflow/cadence/runtime/common" diff --git a/migrations/type_value/migration_test.go b/migrations/statictypes/intersection_type_migration_test.go similarity index 95% rename from migrations/type_value/migration_test.go rename to migrations/statictypes/intersection_type_migration_test.go index 1b30a83f14..ce8bd27be6 100644 --- a/migrations/type_value/migration_test.go +++ b/migrations/statictypes/intersection_type_migration_test.go @@ -16,7 +16,7 @@ * limitations under the License. */ -package type_value +package statictypes import ( "testing" @@ -33,37 +33,9 @@ import ( "github.com/onflow/cadence/runtime/tests/utils" ) -var _ migrations.Reporter = &testReporter{} - -type testReporter struct { - migratedPaths map[interpreter.AddressPath]struct{} -} - -func newTestReporter() *testReporter { - return &testReporter{ - migratedPaths: map[interpreter.AddressPath]struct{}{}, - } -} - -func (t *testReporter) Migrated( - addressPath interpreter.AddressPath, - _ string, -) { - t.migratedPaths[addressPath] = struct{}{} -} - -func (t *testReporter) Error( - _ interpreter.AddressPath, - _ string, - _ error, -) { -} - const fooBarQualifiedIdentifier = "Foo.Bar" const fooBazQualifiedIdentifier = "Foo.Baz" -var testAddress = common.Address{0x42} - var fooAddressLocation = common.NewAddressLocation(nil, testAddress, "Foo") func newIntersectionStaticTypeWithoutInterfaces() *interpreter.IntersectionStaticType { @@ -147,7 +119,7 @@ func newIntersectionStaticTypeWithTwoInterfacesReversed() *interpreter.Intersect ) } -func TestTypeValueMigration(t *testing.T) { +func TestIntersectionTypeMigration(t *testing.T) { t.Parallel() pathDomain := common.PathDomainPublic @@ -429,7 +401,7 @@ func TestTypeValueMigration(t *testing.T) { }, migration.NewValueMigrationsPathMigrator( reporter, - NewTypeValueMigration(), + NewStaticTypeMigration(), ), ) @@ -494,25 +466,10 @@ func TestTypeValueMigration(t *testing.T) { } } -func storeTypeValue( - inter *interpreter.Interpreter, - address common.Address, - domain common.PathDomain, - pathIdentifier string, - staticType interpreter.StaticType, -) { - inter.WriteStored( - address, - domain.Identifier(), - interpreter.StringStorageMapKey(pathIdentifier), - interpreter.NewTypeValue(inter, staticType), - ) -} - -// TestRehash stores a dictionary in storage, +// TestIntersectionTypeRehash stores a dictionary in storage, // which has a key that is a type value with a restricted type that has two interface types, // runs the migration, and ensures the dictionary is still usable -func TestRehash(t *testing.T) { +func TestIntersectionTypeRehash(t *testing.T) { t.Parallel() @@ -609,7 +566,7 @@ func TestRehash(t *testing.T) { }, migration.NewValueMigrationsPathMigrator( reporter, - NewTypeValueMigration(), + NewStaticTypeMigration(), ), ) @@ -770,7 +727,7 @@ func TestRehashNestedIntersectionType(t *testing.T) { }, migration.NewValueMigrationsPathMigrator( reporter, - NewTypeValueMigration(), + NewStaticTypeMigration(), ), ) @@ -906,7 +863,7 @@ func TestRehashNestedIntersectionType(t *testing.T) { }, migration.NewValueMigrationsPathMigrator( reporter, - NewTypeValueMigration(), + NewStaticTypeMigration(), ), ) diff --git a/migrations/account_type/migration.go b/migrations/statictypes/statictype_migration.go similarity index 74% rename from migrations/account_type/migration.go rename to migrations/statictypes/statictype_migration.go index cf9e9b6349..3e048ce920 100644 --- a/migrations/account_type/migration.go +++ b/migrations/statictypes/statictype_migration.go @@ -16,7 +16,7 @@ * limitations under the License. */ -package account_type +package statictypes import ( "github.com/onflow/cadence/migrations" @@ -26,42 +26,58 @@ import ( "github.com/onflow/cadence/runtime/sema" ) -type AccountTypeMigration struct{} +type StaticTypeMigration struct { + compositeTypeConverter CompositeTypeConverterFunc + interfaceTypeConverter InterfaceTypeConverterFunc +} + +type CompositeTypeConverterFunc func(staticType *interpreter.CompositeStaticType) interpreter.StaticType +type InterfaceTypeConverterFunc func(staticType *interpreter.InterfaceStaticType) interpreter.StaticType -var _ migrations.ValueMigration = AccountTypeMigration{} +var _ migrations.ValueMigration = &StaticTypeMigration{} -func NewAccountTypeMigration() AccountTypeMigration { - return AccountTypeMigration{} +func NewStaticTypeMigration() *StaticTypeMigration { + return &StaticTypeMigration{} } -func (AccountTypeMigration) Name() string { - return "AccountTypeMigration" +func (m *StaticTypeMigration) WithCompositeTypeConverter(converterFunc CompositeTypeConverterFunc) *StaticTypeMigration { + m.compositeTypeConverter = converterFunc + return m +} + +func (m *StaticTypeMigration) WithInterfaceTypeConverter(converterFunc InterfaceTypeConverterFunc) *StaticTypeMigration { + m.interfaceTypeConverter = converterFunc + return m +} + +func (*StaticTypeMigration) Name() string { + return "StaticTypeMigration" } // Migrate migrates `AuthAccount` and `PublicAccount` types inside `TypeValue`s, // to the account reference type (&Account). -func (AccountTypeMigration) Migrate( +func (m *StaticTypeMigration) Migrate( _ interpreter.AddressPath, value interpreter.Value, _ *interpreter.Interpreter, ) (newValue interpreter.Value, err error) { switch value := value.(type) { case interpreter.TypeValue: - convertedType := maybeConvertAccountType(value.Type) + convertedType := m.maybeConvertStaticType(value.Type) if convertedType == nil { return } return interpreter.NewTypeValue(nil, convertedType), nil case *interpreter.CapabilityValue: - convertedBorrowType := maybeConvertAccountType(value.BorrowType) + convertedBorrowType := m.maybeConvertStaticType(value.BorrowType) if convertedBorrowType == nil { return } return interpreter.NewUnmeteredCapabilityValue(value.ID, value.Address, convertedBorrowType), nil case *interpreter.AccountCapabilityControllerValue: - convertedBorrowType := maybeConvertAccountType(value.BorrowType) + convertedBorrowType := m.maybeConvertStaticType(value.BorrowType) if convertedBorrowType == nil { return } @@ -70,7 +86,7 @@ func (AccountTypeMigration) Migrate( case *interpreter.StorageCapabilityControllerValue: // Note: A storage capability with Account type shouldn't be possible theoretically. - convertedBorrowType := maybeConvertAccountType(value.BorrowType) + convertedBorrowType := m.maybeConvertStaticType(value.BorrowType) if convertedBorrowType == nil { return } @@ -85,23 +101,23 @@ func (AccountTypeMigration) Migrate( return } -func maybeConvertAccountType(staticType interpreter.StaticType) interpreter.StaticType { +func (m *StaticTypeMigration) maybeConvertStaticType(staticType interpreter.StaticType) interpreter.StaticType { switch staticType := staticType.(type) { case *interpreter.ConstantSizedStaticType: - convertedType := maybeConvertAccountType(staticType.Type) + convertedType := m.maybeConvertStaticType(staticType.Type) if convertedType != nil { return interpreter.NewConstantSizedStaticType(nil, convertedType, staticType.Size) } case *interpreter.VariableSizedStaticType: - convertedType := maybeConvertAccountType(staticType.Type) + convertedType := m.maybeConvertStaticType(staticType.Type) if convertedType != nil { return interpreter.NewVariableSizedStaticType(nil, convertedType) } case *interpreter.DictionaryStaticType: - convertedKeyType := maybeConvertAccountType(staticType.KeyType) - convertedValueType := maybeConvertAccountType(staticType.ValueType) + convertedKeyType := m.maybeConvertStaticType(staticType.KeyType) + convertedValueType := m.maybeConvertStaticType(staticType.ValueType) if convertedKeyType != nil && convertedValueType != nil { return interpreter.NewDictionaryStaticType(nil, convertedKeyType, convertedValueType) } @@ -113,17 +129,16 @@ func maybeConvertAccountType(staticType interpreter.StaticType) interpreter.Stat } case *interpreter.CapabilityStaticType: - convertedBorrowType := maybeConvertAccountType(staticType.BorrowType) + convertedBorrowType := m.maybeConvertStaticType(staticType.BorrowType) if convertedBorrowType != nil { return interpreter.NewCapabilityStaticType(nil, convertedBorrowType) } case *interpreter.IntersectionStaticType: // No need to convert `staticType.Types` as they can only be interfaces. - legacyType := staticType.LegacyType if legacyType != nil { - convertedLegacyType := maybeConvertAccountType(legacyType) + convertedLegacyType := m.maybeConvertStaticType(legacyType) if convertedLegacyType != nil { intersectionType := interpreter.NewIntersectionStaticType(nil, staticType.Types) intersectionType.LegacyType = convertedLegacyType @@ -131,15 +146,21 @@ func maybeConvertAccountType(staticType interpreter.StaticType) interpreter.Stat } } + // If the set has at least two items, + // then force it to be re-stored/re-encoded + if len(staticType.Types) >= 2 { + return staticType + } + case *interpreter.OptionalStaticType: - convertedInnerType := maybeConvertAccountType(staticType.Type) + convertedInnerType := m.maybeConvertStaticType(staticType.Type) if convertedInnerType != nil { return interpreter.NewOptionalStaticType(nil, convertedInnerType) } case *interpreter.ReferenceStaticType: // TODO: Reference of references must not be allowed? - convertedReferencedType := maybeConvertAccountType(staticType.ReferencedType) + convertedReferencedType := m.maybeConvertStaticType(staticType.ReferencedType) if convertedReferencedType != nil { switch convertedReferencedType { @@ -157,15 +178,16 @@ func maybeConvertAccountType(staticType interpreter.StaticType) interpreter.Stat case interpreter.FunctionStaticType: // Non-storable - case *interpreter.CompositeStaticType, - *interpreter.InterfaceStaticType: - // Nothing to do + case *interpreter.CompositeStaticType: + return m.compositeTypeConverter(staticType) + case *interpreter.InterfaceStaticType: + return m.interfaceTypeConverter(staticType) case dummyStaticType: // This is for testing the migration. // i.e: wrapper was only to make it possible to use as a dictionary-key. // Ignore the wrapper, and continue with the inner type. - return maybeConvertAccountType(staticType.PrimitiveStaticType) + return m.maybeConvertStaticType(staticType.PrimitiveStaticType) case interpreter.PrimitiveStaticType: // Is it safe to do so? diff --git a/migrations/type_value/migration.go b/migrations/type_value/migration.go deleted file mode 100644 index af69af0af3..0000000000 --- a/migrations/type_value/migration.go +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Cadence - The resource-oriented smart contract programming language - * - * Copyright Dapper Labs, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package type_value - -import ( - "github.com/onflow/cadence/migrations" - "github.com/onflow/cadence/runtime/errors" - "github.com/onflow/cadence/runtime/interpreter" -) - -type TypeValueMigration struct{} - -var _ migrations.ValueMigration = TypeValueMigration{} - -func NewTypeValueMigration() TypeValueMigration { - return TypeValueMigration{} -} - -func (TypeValueMigration) Name() string { - return "TypeValueMigration" -} - -// Migrate migrates intersection types (formerly, restricted types) inside `TypeValue`s. -func (TypeValueMigration) Migrate( - _ interpreter.AddressPath, - value interpreter.Value, - inter *interpreter.Interpreter, -) (newValue interpreter.Value, err error) { - switch value := value.(type) { - case interpreter.TypeValue: - convertedType := maybeConvertType(value.Type, inter) - if convertedType == nil { - return - } - return interpreter.NewTypeValue(nil, convertedType), nil - } - - return -} - -func maybeConvertType( - staticType interpreter.StaticType, - inter *interpreter.Interpreter, -) interpreter.StaticType { - - switch staticType := staticType.(type) { - case *interpreter.ConstantSizedStaticType: - convertedType := maybeConvertType(staticType.Type, inter) - if convertedType != nil { - return interpreter.NewConstantSizedStaticType(nil, convertedType, staticType.Size) - } - - case *interpreter.VariableSizedStaticType: - convertedType := maybeConvertType(staticType.Type, inter) - if convertedType != nil { - return interpreter.NewVariableSizedStaticType(nil, convertedType) - } - - case *interpreter.DictionaryStaticType: - convertedKeyType := maybeConvertType(staticType.KeyType, inter) - convertedValueType := maybeConvertType(staticType.ValueType, inter) - if convertedKeyType != nil && convertedValueType != nil { - return interpreter.NewDictionaryStaticType(nil, convertedKeyType, convertedValueType) - } - if convertedKeyType != nil { - return interpreter.NewDictionaryStaticType(nil, convertedKeyType, staticType.ValueType) - } - if convertedValueType != nil { - return interpreter.NewDictionaryStaticType(nil, staticType.KeyType, convertedValueType) - } - - case *interpreter.CapabilityStaticType: - convertedBorrowType := maybeConvertType(staticType.BorrowType, inter) - if convertedBorrowType != nil { - return interpreter.NewCapabilityStaticType(nil, convertedBorrowType) - } - - case *interpreter.OptionalStaticType: - convertedInnerType := maybeConvertType(staticType.Type, inter) - if convertedInnerType != nil { - return interpreter.NewOptionalStaticType(nil, convertedInnerType) - } - - case *interpreter.ReferenceStaticType: - // TODO: Reference of references must not be allowed? - convertedReferencedType := maybeConvertType(staticType.ReferencedType, inter) - if convertedReferencedType != nil { - return interpreter.NewReferenceStaticType(nil, staticType.Authorization, convertedReferencedType) - } - - case interpreter.FunctionStaticType: - // Non-storable - - case *interpreter.CompositeStaticType, - *interpreter.InterfaceStaticType, - interpreter.PrimitiveStaticType: - - // Nothing to do - - case *interpreter.IntersectionStaticType: - // No need to convert `staticType.Types` as they can only be interfaces. - - legacyType := staticType.LegacyType - if legacyType != nil { - convertedLegacyType := maybeConvertType(legacyType, inter) - if convertedLegacyType != nil { - intersectionType := interpreter.NewIntersectionStaticType(nil, staticType.Types) - intersectionType.LegacyType = convertedLegacyType - return intersectionType - } - } - - // If the set has at least two items, - // then force it to be re-stored/re-encoded - if len(staticType.Types) >= 2 { - return staticType - } - - default: - panic(errors.NewUnreachableError()) - } - - return nil -}