diff --git a/runtime/sema/check_composite_declaration.go b/runtime/sema/check_composite_declaration.go index bca89a2497..b5697881e4 100644 --- a/runtime/sema/check_composite_declaration.go +++ b/runtime/sema/check_composite_declaration.go @@ -644,11 +644,11 @@ func (checker *Checker) declareCompositeMembersAndValue( // func (checker *Checker) checkMemberStorability(members map[string]*Member) { - seenMembers := map[*Member]bool{} + storableResults := map[*Member]bool{} for _, member := range members { - if member.IsStorable(seenMembers) { + if member.IsStorable(storableResults) { continue } diff --git a/runtime/sema/type.go b/runtime/sema/type.go index 0fc583ae2a..c3349150f7 100644 --- a/runtime/sema/type.go +++ b/runtime/sema/type.go @@ -4617,7 +4617,10 @@ var authAccountRemovePublicKeyFunctionType = &FunctionType{ var authAccountSaveFunctionType = func() *FunctionType { - typeParameter := &TypeParameter{Name: "T"} + typeParameter := &TypeParameter{ + Name: "T", + TypeBound: &StorableType{}, + } return &FunctionType{ TypeParameters: []*TypeParameter{ @@ -4645,7 +4648,10 @@ var authAccountSaveFunctionType = func() *FunctionType { var authAccountLoadFunctionType = func() *FunctionType { - typeParameter := &TypeParameter{Name: "T"} + typeParameter := &TypeParameter{ + Name: "T", + TypeBound: &StorableType{}, + } return &FunctionType{ TypeParameters: []*TypeParameter{ @@ -6145,6 +6151,10 @@ func IsSubType(subType Type, superType Type) bool { } } } + + case *StorableType: + storableResults := map[*Member]bool{} + return subType.IsStorable(storableResults) } // TODO: enforce type arguments, remove this rule @@ -6789,3 +6799,70 @@ func (t *CapabilityType) GetMember(identifier string, _ ast.Range, _ func(error) return nil } } + +// StorableType is the supertype of all types which are storable. +// +// It is only used as e.g. a type bound, but is not accessible +// to user programs, i.e. can't be used in type annotations +// for e.g. parameters, return types, fields, etc. +// +type StorableType struct{} + +func (*StorableType) IsType() {} + +func (*StorableType) String() string { + return "Storable" +} + +func (*StorableType) QualifiedString() string { + return "Storable" +} + +func (*StorableType) ID() TypeID { + return "Storable" +} + +func (*StorableType) Equal(other Type) bool { + _, ok := other.(*StorableType) + return ok +} + +func (*StorableType) IsResourceType() bool { + + // NOTE: Subtypes may be either resource types or not. + // + // Returning false here is safe, because this type is + // only used as e.g. a type bound, but is not accessible + // to user programs, i.e. can't be used in type annotations + // for e.g. parameters, return types, fields, etc. + + return false +} + +func (*StorableType) IsInvalidType() bool { + return false +} + +func (*StorableType) IsStorable(_ map[*Member]bool) bool { + return true +} + +func (*StorableType) IsEquatable() bool { + return false +} + +func (*StorableType) TypeAnnotationState() TypeAnnotationState { + return TypeAnnotationStateValid +} + +func (*StorableType) ContainsFirstLevelInterfaceType() bool { + return false +} + +func (*StorableType) Unify(_ Type, _ map[*TypeParameter]Type, _ func(err error), _ ast.Range) bool { + return false +} + +func (t *StorableType) Resolve(_ map[*TypeParameter]Type) Type { + return t +} diff --git a/runtime/tests/checker/account_test.go b/runtime/tests/checker/account_test.go index 3540d10294..d1865e5ab7 100644 --- a/runtime/tests/checker/account_test.go +++ b/runtime/tests/checker/account_test.go @@ -62,50 +62,57 @@ func TestCheckAccount_save(t *testing.T) { // NOTE: all domains are statically valid at the moment - testName := fmt.Sprintf( - "AuthAccount.save: implicit type argument, %s", - domain.Name(), - ) - t.Run(testName, func(t *testing.T) { + domainName := domain.Name() + domainIdentifier := domain.Identifier() + + testName := func(kind string) string { + return fmt.Sprintf( + "implicit type argument, %s, %s", + domainName, + kind, + ) + } - t.Run("resource", func(t *testing.T) { + t.Run(testName("resource"), func(t *testing.T) { - _, err := ParseAndCheckAccount(t, - fmt.Sprintf( - ` - resource R {} + t.Parallel() - fun test() { - let r <- create R() - authAccount.save(<-r, to: /%s/r) - } - `, - domain.Identifier(), - ), - ) + _, err := ParseAndCheckAccount(t, + fmt.Sprintf( + ` + resource R {} - require.NoError(t, err) - }) + fun test() { + let r <- create R() + authAccount.save(<-r, to: /%s/r) + } + `, + domainIdentifier, + ), + ) - t.Run("struct", func(t *testing.T) { + require.NoError(t, err) + }) - _, err := ParseAndCheckAccount(t, - fmt.Sprintf( - ` - struct S {} + t.Run(testName("struct"), func(t *testing.T) { - fun test() { - let s = S() - authAccount.save(s, to: /%s/s) - } - `, - domain.Identifier(), - ), - ) + t.Parallel() - require.NoError(t, err) + _, err := ParseAndCheckAccount(t, + fmt.Sprintf( + ` + struct S {} - }) + fun test() { + let s = S() + authAccount.save(s, to: /%s/s) + } + `, + domainIdentifier, + ), + ) + + require.NoError(t, err) }) } @@ -114,18 +121,24 @@ func TestCheckAccount_save(t *testing.T) { // NOTE: all domains are statically valid at the moment - testName := fmt.Sprintf( - "AuthAccount.save: explicit type argument, %s", - domain.Name(), - ) + domainName := domain.Name() + domainIdentifier := domain.Identifier() - t.Run(testName, func(t *testing.T) { + testName := func(kind string) string { + return fmt.Sprintf( + "explicit type argument, %s, %s", + domainName, + kind, + ) + } - t.Run("resource", func(t *testing.T) { + t.Run(testName("resource"), func(t *testing.T) { - _, err := ParseAndCheckAccount(t, - fmt.Sprintf( - ` + t.Parallel() + + _, err := ParseAndCheckAccount(t, + fmt.Sprintf( + ` resource R {} fun test() { @@ -133,31 +146,32 @@ func TestCheckAccount_save(t *testing.T) { authAccount.save<@R>(<-r, to: /%s/r) } `, - domain.Identifier(), - ), - ) + domainIdentifier, + ), + ) - require.NoError(t, err) - }) + require.NoError(t, err) + }) - t.Run("struct", func(t *testing.T) { + t.Run(testName("struct"), func(t *testing.T) { - _, err := ParseAndCheckAccount(t, - fmt.Sprintf( - ` - struct S {} + t.Parallel() - fun test() { - let s = S() - authAccount.save(s, to: /%s/s) - } - `, - domain.Identifier(), - ), - ) + _, err := ParseAndCheckAccount(t, + fmt.Sprintf( + ` + struct S {} - require.NoError(t, err) - }) + fun test() { + let s = S() + authAccount.save(s, to: /%s/s) + } + `, + domainIdentifier, + ), + ) + + require.NoError(t, err) }) } @@ -165,17 +179,24 @@ func TestCheckAccount_save(t *testing.T) { // NOTE: all domains are statically valid at the moment - testName := fmt.Sprintf( - "AuthAccount.save: explicit type argument, incorrect, %s", - domain.Name(), - ) - t.Run(testName, func(t *testing.T) { + domainName := domain.Name() + domainIdentifier := domain.Identifier() - t.Run("resource", func(t *testing.T) { + testName := func(kind string) string { + return fmt.Sprintf( + "explicit type argument, incorrect, %s, %s", + domainName, + kind, + ) + } - _, err := ParseAndCheckAccount(t, - fmt.Sprintf( - ` + t.Run(testName("resource"), func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheckAccount(t, + fmt.Sprintf( + ` resource R {} resource T {} @@ -185,41 +206,107 @@ func TestCheckAccount_save(t *testing.T) { authAccount.save<@T>(<-r, to: /%s/r) } `, - domain.Identifier(), - ), - ) + domainIdentifier, + ), + ) - errs := ExpectCheckerErrors(t, err, 2) + errs := ExpectCheckerErrors(t, err, 2) - require.IsType(t, &sema.TypeParameterTypeMismatchError{}, errs[0]) - require.IsType(t, &sema.TypeMismatchError{}, errs[1]) - }) + require.IsType(t, &sema.TypeParameterTypeMismatchError{}, errs[0]) + require.IsType(t, &sema.TypeMismatchError{}, errs[1]) + }) - t.Run("struct", func(t *testing.T) { + t.Run(testName("struct"), func(t *testing.T) { - _, err := ParseAndCheckAccount(t, - fmt.Sprintf( - ` - struct S {} + t.Parallel() - struct T {} + _, err := ParseAndCheckAccount(t, + fmt.Sprintf( + ` + struct S {} - fun test() { - let s = S() - authAccount.save(s, to: /%s/s) - } - `, - domain.Identifier(), - ), - ) + struct T {} - errs := ExpectCheckerErrors(t, err, 2) + fun test() { + let s = S() + authAccount.save(s, to: /%s/s) + } + `, + domainIdentifier, + ), + ) - require.IsType(t, &sema.TypeParameterTypeMismatchError{}, errs[0]) - require.IsType(t, &sema.TypeMismatchError{}, errs[1]) - }) + errs := ExpectCheckerErrors(t, err, 2) + + require.IsType(t, &sema.TypeParameterTypeMismatchError{}, errs[0]) + require.IsType(t, &sema.TypeMismatchError{}, errs[1]) + }) + } + + for _, domain := range common.AllPathDomainsByIdentifier { + + // NOTE: all domains are statically valid at the moment + + domainName := domain.Name() + domainIdentifier := domain.Identifier() + + testName := func(kind string) string { + return fmt.Sprintf( + "invalid non-storable, %s, %s", + domainName, + kind, + ) + } + + t.Run(testName("explicit type argument"), func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheckAccount(t, + fmt.Sprintf( + ` + fun one(): Int { + return 1 + } + + fun test() { + authAccount.save<((): Int)>(one, to: /%s/one) + } + `, + domainIdentifier, + ), + ) + + errs := ExpectCheckerErrors(t, err, 1) + + require.IsType(t, &sema.TypeMismatchError{}, errs[0]) + }) + + t.Run(testName("implicit type argument"), func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheckAccount(t, + fmt.Sprintf( + ` + fun one(): Int { + return 1 + } + + fun test() { + authAccount.save(one, to: /%s/one) + } + `, + domainIdentifier, + ), + ) + + errs := ExpectCheckerErrors(t, err, 1) + + require.IsType(t, &sema.TypeMismatchError{}, errs[0]) }) } + } func TestCheckAccount_load(t *testing.T) {