diff --git a/ast/expression.go b/ast/expression.go index 097b8a731..9c14f3924 100644 --- a/ast/expression.go +++ b/ast/expression.go @@ -1416,7 +1416,7 @@ func FunctionDocument( } // NOTE: not all functions have a parameter list, - // e.g. the `destroy` special function + // e.g. the `init` (initializer, special function) if parameterList != nil { signatureDoc = append( diff --git a/go.mod b/go.mod index 4ee8098b0..6193bce06 100644 --- a/go.mod +++ b/go.mod @@ -22,10 +22,10 @@ require ( github.com/turbolent/prettier v0.0.0-20220320183459-661cc755135d go.opentelemetry.io/otel v1.8.0 go.uber.org/goleak v1.1.10 - golang.org/x/crypto v0.1.0 - golang.org/x/mod v0.14.0 - golang.org/x/text v0.4.0 - golang.org/x/tools v0.16.0 + golang.org/x/crypto v0.28.0 + golang.org/x/mod v0.17.0 + golang.org/x/text v0.19.0 + golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 ) @@ -56,8 +56,9 @@ require ( github.com/zeebo/assert v1.3.0 // indirect github.com/zeebo/blake3 v0.2.3 // indirect golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect - golang.org/x/sys v0.15.0 // indirect - golang.org/x/term v0.6.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect gonum.org/v1/gonum v0.6.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index c5711d90b..cc2c3058d 100644 --- a/go.sum +++ b/go.sum @@ -23,8 +23,8 @@ github.com/fxamacker/cbor/v2 v2.4.1-0.20230228173756-c0c9f774e40c/go.mod h1:TA1x github.com/fxamacker/circlehash v0.3.0 h1:XKdvTtIJV9t7DDUtsf0RIpC1OcxZtPbmgIH7ekx28WA= github.com/fxamacker/circlehash v0.3.0/go.mod h1:3aq3OfVvsWtkWMb6A1owjOQFA+TLsD5FgJflnaQwtMM= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/itchyny/gojq v0.12.14 h1:6k8vVtsrhQSYgSGg827AD+PVVaB1NLXEdX+dda2oZCc= github.com/itchyny/gojq v0.12.14/go.mod h1:y1G7oO7XkcR1LPZO59KyoCRy08T3j9vDYRV0GgYSS+s= github.com/itchyny/timefmt-go v0.1.5 h1:G0INE2la8S6ru/ZI5JecgyzbbJNs5lG1RcBqa7Jm6GE= @@ -121,8 +121,8 @@ go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= -golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -133,8 +133,8 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= -golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -143,8 +143,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20191109021931-daa7c04131f5/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= -golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -157,20 +157,21 @@ golang.org/x/sys v0.0.0-20200918174421-af09f7315aff/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM= -golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= diff --git a/npm-packages/cadence-parser/package.json b/npm-packages/cadence-parser/package.json index a3c6ba8a5..787f0ba9e 100644 --- a/npm-packages/cadence-parser/package.json +++ b/npm-packages/cadence-parser/package.json @@ -1,6 +1,6 @@ { "name": "@onflow/cadence-parser", - "version": "1.2.1-preview.52", + "version": "1.2.1", "description": "The Cadence parser", "homepage": "https://github.com/onflow/cadence", "repository": { diff --git a/runtime/contract_update_validation_test.go b/runtime/contract_update_validation_test.go index 74afc1c44..3c8fda02d 100644 --- a/runtime/contract_update_validation_test.go +++ b/runtime/contract_update_validation_test.go @@ -83,6 +83,10 @@ func newContractRemovalTransaction(contractName string) string { } func newContractDeploymentTransactor(t *testing.T, config Config) func(code string) error { + return newContractDeploymentTransactorWithVersion(t, config, "") +} + +func newContractDeploymentTransactorWithVersion(t *testing.T, config Config, version string) func(code string) error { rt := NewTestInterpreterRuntimeWithConfig(config) @@ -112,6 +116,9 @@ func newContractDeploymentTransactor(t *testing.T, config Config) func(code stri events = append(events, event) return nil }, + OnMinimumRequiredVersion: func() (string, error) { + return version, nil + }, } nextTransactionLocation := NewTransactionLocationGenerator() @@ -132,7 +139,18 @@ func newContractDeploymentTransactor(t *testing.T, config Config) func(code stri // testDeployAndUpdate deploys a contract in one transaction, // then updates the contract in another transaction func testDeployAndUpdate(t *testing.T, name string, oldCode string, newCode string, config Config) error { - executeTransaction := newContractDeploymentTransactor(t, config) + return testDeployAndUpdateWithVersion(t, name, oldCode, newCode, config, "") +} + +func testDeployAndUpdateWithVersion( + t *testing.T, + name string, + oldCode string, + newCode string, + config Config, + version string, +) error { + executeTransaction := newContractDeploymentTransactorWithVersion(t, config, version) err := executeTransaction(newContractAddTransaction(name, oldCode)) require.NoError(t, err) @@ -3764,3 +3782,51 @@ func TestRuntimeContractUpdateErrorsInOldProgram(t *testing.T) { require.ErrorAs(t, err, &oldProgramError) }) } + +func TestAttachmentsUpdates(t *testing.T) { + t.Parallel() + + testWithValidators(t, + "Keep base type", + func(t *testing.T, config Config) { + + const oldCode = ` + access(all) contract Test { + access(all) attachment A for AnyResource {} + } + ` + + const newCode = ` + access(all) contract Test { + access(all) attachment A for AnyResource {} + } + ` + + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) + require.NoError(t, err) + }, + ) + + testWithValidators(t, + "Change base type", + func(t *testing.T, config Config) { + + const oldCode = ` + access(all) contract Test { + access(all) attachment A for AnyResource {} + } + ` + + const newCode = ` + access(all) contract Test { + access(all) attachment A for AnyStruct {} + } + ` + + err := testDeployAndUpdate(t, "Test", oldCode, newCode, config) + + var expectedErr *stdlib.TypeMismatchError + require.ErrorAs(t, err, &expectedErr) + }, + ) +} diff --git a/runtime/empty.go b/runtime/empty.go index da9364453..0d2902be3 100644 --- a/runtime/empty.go +++ b/runtime/empty.go @@ -260,3 +260,7 @@ func (EmptyRuntimeInterface) ValidateAccountCapabilitiesPublish( ) (bool, error) { panic("unexpected call to ValidateAccountCapabilitiesPublish") } + +func (EmptyRuntimeInterface) MinimumRequiredVersion() (string, error) { + return "0.0.0", nil +} diff --git a/runtime/environment.go b/runtime/environment.go index ef3d0130d..04ffe25d1 100644 --- a/runtime/environment.go +++ b/runtime/environment.go @@ -242,6 +242,8 @@ func (e *interpreterEnvironment) Configure( e.InterpreterConfig.Storage = storage e.coverageReport = coverageReport e.stackDepthLimiter.depth = 0 + + e.configureVersionedFeatures() } func (e *interpreterEnvironment) DeclareValue(valueDeclaration stdlib.StandardLibraryValue, location common.Location) { @@ -1425,3 +1427,19 @@ func (e *interpreterEnvironment) newValidateAccountCapabilitiesPublishHandler() return ok, err } } + +func (e *interpreterEnvironment) configureVersionedFeatures() { + var ( + minimumRequiredVersion string + err error + ) + errors.WrapPanic(func() { + minimumRequiredVersion, err = e.runtimeInterface.MinimumRequiredVersion() + }) + if err != nil { + panic(err) + } + + // No feature flags yet + _ = minimumRequiredVersion +} diff --git a/runtime/interface.go b/runtime/interface.go index 410ee057d..e5d9a22c8 100644 --- a/runtime/interface.go +++ b/runtime/interface.go @@ -161,6 +161,8 @@ type Interface interface { path interpreter.PathValue, capabilityBorrowType *interpreter.ReferenceStaticType, ) (bool, error) + + MinimumRequiredVersion() (string, error) } type MeterInterface interface { diff --git a/runtime/runtime_test.go b/runtime/runtime_test.go index 91b7bcdc8..7f942cbdd 100644 --- a/runtime/runtime_test.go +++ b/runtime/runtime_test.go @@ -11666,3 +11666,71 @@ func TestRuntimeIdentifierLocationToAddressLocationRewrite(t *testing.T) { assert.Equal(t, cadence.NewInt(42), result) } + +func TestRuntimeBuiltInFunctionConfusion(t *testing.T) { + + t.Parallel() + + const contract = ` + access(all) contract Foo { + access(all) resource getType {} + + init() { + Foo.getType() + } + } + ` + + address := common.MustBytesToAddress([]byte{0x1}) + + newRuntimeInterface := func() Interface { + + accountCodes := map[common.AddressLocation][]byte{} + var events []cadence.Event + var loggedMessages []string + + return &TestRuntimeInterface{ + Storage: NewTestLedger(nil, nil), + OnGetSigningAccounts: func() ([]common.Address, error) { + return []common.Address{address}, nil + }, + OnResolveLocation: NewSingleIdentifierLocationResolver(t), + OnUpdateAccountContractCode: func(location common.AddressLocation, code []byte) error { + accountCodes[location] = code + return nil + }, + OnGetAccountContractCode: func(location common.AddressLocation) (code []byte, err error) { + code = accountCodes[location] + return code, nil + }, + OnEmitEvent: func(event cadence.Event) error { + events = append(events, event) + return nil + }, + OnProgramLog: func(message string) { + loggedMessages = append(loggedMessages, message) + }, + } + } + + runtime := NewTestInterpreterRuntime() + + nextTransactionLocation := NewTransactionLocationGenerator() + + err := runtime.ExecuteTransaction( + Script{ + Source: DeploymentTransaction( + "Foo", + []byte(contract), + ), + }, + Context{ + Interface: newRuntimeInterface(), + Location: nextTransactionLocation(), + }, + ) + RequireError(t, err) + + var redeclarationError *sema.RedeclarationError + require.ErrorAs(t, err, &redeclarationError) +} diff --git a/sema/attachments_test.go b/sema/attachments_test.go index d7d5e6244..2e8bed5d9 100644 --- a/sema/attachments_test.go +++ b/sema/attachments_test.go @@ -620,304 +620,42 @@ func TestCheckAttachmentWithMembers(t *testing.T) { }) } -func TestCheckAttachmentConformance(t *testing.T) { +func TestCheckInvalidAttachmentConformance(t *testing.T) { t.Parallel() - t.Run("basic", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, - ` - resource R {} - resource interface I { - } - attachment Test for R: I { - }`, - ) - - require.NoError(t, err) - }) - - t.Run("field", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, - ` - resource R {} - resource interface I { - let x: Int - } - attachment Test for R: I { - let x: Int - init(x: Int) { - self.x = x - } - }`, - ) - - require.NoError(t, err) - }) - - t.Run("method", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, - ` - resource R {} - resource interface I { - fun x(): Int - } - attachment Test for R: I { - fun x(): Int { return 0 } - }`, - ) - - require.NoError(t, err) - }) - - t.Run("field missing", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, - ` - resource R {} - resource interface I { - let x: Int - } - attachment Test for R: I { - }`, - ) - - errs := RequireCheckerErrors(t, err, 1) - - assert.IsType(t, &sema.ConformanceError{}, errs[0]) - }) - - t.Run("field type", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, - ` - resource R {} - resource interface I { - let x: Int - } - attachment Test for R: I { - let x: AnyStruct - init(x: AnyStruct) { - self.x = x - } - }`, - ) - - errs := RequireCheckerErrors(t, err, 1) - - assert.IsType(t, &sema.ConformanceError{}, errs[0]) - }) - - t.Run("method missing", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, - ` - resource R {} - resource interface I { - fun x(): Int - } - attachment Test for R: I { - }`, - ) - - errs := RequireCheckerErrors(t, err, 1) - - assert.IsType(t, &sema.ConformanceError{}, errs[0]) - }) - - t.Run("method type", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, - ` - resource R {} - resource interface I { - fun x(): Int - } - attachment Test for R: I { - fun x(): AnyStruct { return "" } - }`, - ) - - errs := RequireCheckerErrors(t, err, 1) - - assert.IsType(t, &sema.ConformanceError{}, errs[0]) - }) - - t.Run("method missing, exists in base type", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, - ` - resource R { - fun x(): Int { return 3 } - } - resource interface I { - fun x(): Int - } - attachment Test for R: I { - }`, - ) - - errs := RequireCheckerErrors(t, err, 1) - - assert.IsType(t, &sema.ConformanceError{}, errs[0]) - }) - - t.Run("kind mismatch resource", func(t *testing.T) { + t.Run("struct", func(t *testing.T) { t.Parallel() - _, err := ParseAndCheck(t, - ` - resource R {} - struct interface I {} - attachment Test for R: I {}`, - ) - - errs := RequireCheckerErrors(t, err, 1) - - assert.IsType(t, &sema.CompositeKindMismatchError{}, errs[0]) - }) - - t.Run("kind mismatch struct", func(t *testing.T) { + _, err := ParseAndCheck(t, ` + struct S {} - t.Parallel() + struct interface SI {} - _, err := ParseAndCheck(t, - ` - struct R {} - resource interface I {} - attachment Test for R: I {}`, - ) + attachment Test for S: SI {} + `) errs := RequireCheckerErrors(t, err, 1) - assert.IsType(t, &sema.CompositeKindMismatchError{}, errs[0]) - }) - - t.Run("conforms to base", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, - ` - resource interface I {} - attachment A for I: I {}`, - ) - - require.NoError(t, err) - }) - - t.Run("AnyResource base, resource conformance", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, - ` - resource interface I {} - attachment A for AnyResource: I {}`, - ) - - require.NoError(t, err) - }) - - t.Run("AnyStruct base, struct conformance", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, - ` - struct interface I {} - attachment A for AnyStruct: I {}`, - ) - - require.NoError(t, err) + assert.IsType(t, &sema.InvalidAttachmentConformancesError{}, errs[0]) }) - t.Run("AnyStruct base, resource conformance", func(t *testing.T) { + t.Run("resource", func(t *testing.T) { t.Parallel() - _, err := ParseAndCheck(t, - ` - resource interface I {} - attachment A for AnyStruct: I {}`, - ) - - errs := RequireCheckerErrors(t, err, 1) - - assert.IsType(t, &sema.CompositeKindMismatchError{}, errs[0]) - }) - - t.Run("AnyResource base, struct conformance", func(t *testing.T) { + _, err := ParseAndCheck(t, ` + resource R {} - t.Parallel() + resource interface RI {} - _, err := ParseAndCheck(t, - ` - struct interface I {} - attachment A for AnyResource: I {}`, - ) + attachment Test for R: RI {} + `) errs := RequireCheckerErrors(t, err, 1) - assert.IsType(t, &sema.CompositeKindMismatchError{}, errs[0]) - }) - - t.Run("cross-contract concrete base", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, - ` - contract C0 { - resource interface R {} - } - contract C1 { - resource R {} - attachment A for R: C0.R {} - } - `, - ) - - require.NoError(t, err) - }) - - t.Run("cross-contract interface base", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, - ` - contract C0 { - resource R {} - } - contract C1 { - resource interface R {} - attachment A for C0.R: R {} - } - `, - ) - - require.NoError(t, err) + assert.IsType(t, &sema.InvalidAttachmentConformancesError{}, errs[0]) }) } @@ -1296,42 +1034,6 @@ func TestCheckAttachmentSelfTyping(t *testing.T) { require.NoError(t, err) }) - t.Run("return self struct interface", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, - ` - struct R {} - struct interface I {} - attachment Test for R: I { - fun foo(): &{I} { - return self - } - }`, - ) - - require.NoError(t, err) - }) - - t.Run("return self resource interface", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, - ` - resource R {} - resource interface I {} - attachment Test for R: I { - fun foo(): &{I} { - return self - } - }`, - ) - - require.NoError(t, err) - }) - t.Run("self access restricted", func(t *testing.T) { t.Parallel() @@ -3518,7 +3220,7 @@ func TestCheckAttachmentAccessAttachment(t *testing.T) { fmt.Sprintf(` resource R {} resource interface I {} - attachment A for AnyResource: I {} + attachment A for AnyResource {} access(all) fun foo(r: %sR) { r[I] %s @@ -4666,7 +4368,7 @@ func TestCheckAttachmentForEachAttachment(t *testing.T) { assert.IsType(t, &sema.NotDeclaredMemberError{}, errs[0]) }) - t.Run("cannot redeclare forEachAttachment", func(t *testing.T) { + t.Run("cannot redeclare forEachAttachment as function", func(t *testing.T) { t.Parallel() @@ -4682,6 +4384,27 @@ func TestCheckAttachmentForEachAttachment(t *testing.T) { assert.IsType(t, &sema.InvalidDeclarationError{}, errs[0]) }) + t.Run("cannot redeclare forEachAttachment as field", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, + ` + access(all) struct S { + let forEachAttachment: Int + + init() { + self.forEachAttachment = 1 + } + } + `, + ) + + errs := RequireCheckerErrors(t, err, 2) + assert.IsType(t, &sema.InvalidDeclarationError{}, errs[0]) + assert.IsType(t, &sema.TypeMismatchError{}, errs[1]) + }) + t.Run("downcasting reference with entitlements", func(t *testing.T) { t.Parallel() diff --git a/sema/check_composite_declaration.go b/sema/check_composite_declaration.go index 9b201296c..bbd3728aa 100644 --- a/sema/check_composite_declaration.go +++ b/sema/check_composite_declaration.go @@ -674,9 +674,32 @@ func (checker *Checker) declareCompositeType(declaration ast.CompositeLikeDeclar // Resolve conformances - if declaration.Kind() == common.CompositeKindEnum { + switch declaration.Kind() { + case common.CompositeKindEnum: compositeType.EnumRawType = checker.enumRawType(declaration.(*ast.CompositeDeclaration)) - } else { + + case common.CompositeKindAttachment: + + // Attachments may not conform to interfaces + + conformanceList := declaration.ConformanceList() + conformanceCount := len(conformanceList) + if conformanceCount > 0 { + firstConformance := conformanceList[0] + lastConformance := conformanceList[conformanceCount-1] + + checker.report( + &InvalidAttachmentConformancesError{ + Range: ast.NewRange( + checker.memoryGauge, + firstConformance.StartPosition(), + lastConformance.EndPosition(checker.memoryGauge), + ), + }, + ) + } + + default: compositeType.ExplicitInterfaceConformances = checker.explicitInterfaceConformances(declaration, compositeType) } @@ -1681,39 +1704,84 @@ func (checker *Checker) defaultMembersAndOrigins( } predeclaredMembers := checker.predeclaredMembers(containerType) - invalidIdentifiers := make(map[string]bool, len(predeclaredMembers)) - + predeclaredMemberNames := make(map[string]struct{}, len(predeclaredMembers)) for _, predeclaredMember := range predeclaredMembers { name := predeclaredMember.Identifier.Identifier - members.Set(name, predeclaredMember) - invalidIdentifiers[name] = true + predeclaredMemberNames[name] = struct{}{} + } - if predeclaredMember.DeclarationKind == common.DeclarationKindField { - fieldNames = append(fieldNames, name) + var nestedTypes *StringTypeOrderedMap + if containerType, ok := containerType.(ContainerType); ok { + nestedTypes = containerType.GetNestedTypes() + } + + checkRedeclaration := func( + identifier ast.Identifier, + declarationKind common.DeclarationKind, + isPredeclared bool, + ) bool { + name := identifier.Identifier + + if !isPredeclared { + if _, ok := predeclaredMemberNames[name]; ok { + checker.report( + &InvalidDeclarationError{ + Identifier: identifier.Identifier, + Kind: declarationKind, + Range: ast.NewRangeFromPositioned(checker.memoryGauge, identifier), + }, + ) + return false + } + } + + if nestedTypes != nil { + if _, ok := nestedTypes.Get(name); ok { + // TODO: provide previous position + checker.report( + &RedeclarationError{ + Name: name, + Kind: declarationKind, + Pos: identifier.Pos, + }, + ) + + return false + } } + + return true } - checkInvalidIdentifier := func(declaration ast.Declaration) bool { - identifier := declaration.DeclarationIdentifier() - if invalidIdentifiers == nil || !invalidIdentifiers[identifier.Identifier] { - return true + // declare all predeclared members (built-in functions and fields) + for _, predeclaredMember := range predeclaredMembers { + identifier := predeclaredMember.Identifier + name := identifier.Identifier + declarationKind := predeclaredMember.DeclarationKind + + if !checkRedeclaration( + identifier, + declarationKind, + true, + ) { + continue } - checker.report( - &InvalidDeclarationError{ - Identifier: identifier.Identifier, - Kind: declaration.DeclarationKind(), - Range: ast.NewRangeFromPositioned(checker.memoryGauge, identifier), - }, - ) + members.Set(name, predeclaredMember) - return false + if declarationKind == common.DeclarationKindField { + fieldNames = append(fieldNames, name) + } } // declare a member for each field for _, field := range fields { - if !checkInvalidIdentifier(field) { + if !checkRedeclaration( + field.Identifier, + field.DeclarationKind(), + false, + ) { continue } @@ -1785,7 +1853,12 @@ func (checker *Checker) defaultMembersAndOrigins( // declare a member for each function for _, function := range functions { - if !checkInvalidIdentifier(function) { + + if !checkRedeclaration( + function.Identifier, + function.DeclarationKind(), + false, + ) { continue } @@ -2399,7 +2472,7 @@ func (checker *Checker) declareBaseValue(fnAccess Access, baseType Type, attachm } // checkNestedIdentifiers checks that nested identifiers, i.e. fields, functions, -// and nested interfaces and composites, are unique and aren't named `init` or `destroy` +// and nested interfaces and composites, are unique and aren't named `init` func (checker *Checker) checkNestedIdentifiers(members *ast.Members) { positions := map[string]ast.Position{} @@ -2423,7 +2496,7 @@ func (checker *Checker) checkNestedIdentifiers(members *ast.Members) { } // checkNestedIdentifier checks that the nested identifier is unique -// and isn't named `init` or `destroy` +// and isn't named `init` func (checker *Checker) checkNestedIdentifier( identifier ast.Identifier, kind common.DeclarationKind, diff --git a/sema/composite_test.go b/sema/composite_test.go index dc6c8dc92..2c718749b 100644 --- a/sema/composite_test.go +++ b/sema/composite_test.go @@ -2299,3 +2299,55 @@ func TestCheckKeywordsAsFieldNames(t *testing.T) { }) } } + +func TestCheckInvalidFunctionNestedTypeClash(t *testing.T) { + + t.Parallel() + interfacePossibilities := []bool{true, false} + + for _, kind := range common.CompositeKindsWithFieldsAndFunctions { + for _, isInterface := range interfacePossibilities { + + interfaceKeyword := "" + if isInterface { + interfaceKeyword = "interface" + } + + var baseType string + + switch kind { + case common.CompositeKindContract: + // Cannot nest contracts + continue + + case common.CompositeKindAttachment: + if isInterface { + continue + } + baseType = "for AnyStruct" + } + + testName := fmt.Sprintf("%s_%s", kind.Keyword(), interfaceKeyword) + + t.Run(testName, func(t *testing.T) { + + _, err := ParseAndCheck(t, + fmt.Sprintf( + ` + contract C { + %s %s getType %s {} + } + `, + kind.Keyword(), + interfaceKeyword, + baseType, + ), + ) + + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.RedeclarationError{}, errs[0]) + }) + } + } +} diff --git a/sema/contract_test.go b/sema/contract_test.go index e21b6d787..15c00749d 100644 --- a/sema/contract_test.go +++ b/sema/contract_test.go @@ -95,6 +95,36 @@ func TestCheckInvalidContractInterfaceAccountFunction(t *testing.T) { assert.IsType(t, &sema.InvalidDeclarationError{}, errs[0]) } +func TestCheckInvalidContractAccountType(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + contract Test { + struct account {} + } + `) + + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.RedeclarationError{}, errs[0]) +} + +func TestCheckInvalidContractInterfaceAccountType(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + contract interface Test { + struct interface account {} + } + `) + + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.RedeclarationError{}, errs[0]) +} + func TestCheckContractAccountFieldUse(t *testing.T) { t.Parallel() diff --git a/sema/entitlements_test.go b/sema/entitlements_test.go index 3b47df81b..d4fc27d13 100644 --- a/sema/entitlements_test.go +++ b/sema/entitlements_test.go @@ -3032,116 +3032,6 @@ func TestCheckEntitlementInheritance(t *testing.T) { assert.NoError(t, err) }) - - t.Run("attachment inherited default entitled function entitlements on base", func(t *testing.T) { - t.Parallel() - - _, err := ParseAndCheck(t, ` - entitlement E - entitlement F - struct interface I { - access(E) fun foo(): auth(F) &Int { - return &1 as auth(F) &Int - } - } - struct interface I2: I {} - struct S { - access(E) fun foo() {} - } - access(all) attachment A for S: I2 {} - fun test() { - let s = attach A() to S() - let ref = &s as auth(E) &S - let attachmentRef: auth(E) &A = s[A]! - let i: auth(F) &Int = attachmentRef.foo() - } - `) - - assert.NoError(t, err) - }) - - t.Run("attachment inherited default mapped function entitlements on base", func(t *testing.T) { - t.Parallel() - - _, err := ParseAndCheck(t, ` - entitlement E - entitlement F - entitlement mapping M { - E -> F - } - struct interface I { - access(mapping M) fun foo(): auth(mapping M) &Int { - return &1 as auth(mapping M) &Int - } - } - struct interface I2: I {} - struct S { - access(E) fun foo() {} - } - access(all) attachment A for S: I2 {} - fun test() { - let s = attach A() to S() - let ref = &s as auth(E) &S - let attachmentRef: auth(E) &A = s[A]! - let i: auth(F) &Int = attachmentRef.foo() - } - `) - - assert.NoError(t, err) - }) - - t.Run("attachment inherited default mapped function entitlements not on base", func(t *testing.T) { - t.Parallel() - - _, err := ParseAndCheck(t, ` - entitlement E - entitlement F - entitlement mapping M { - E -> F - } - struct interface I { - access(mapping M) fun foo(): auth(mapping M) &Int { - return &1 as auth(mapping M) &Int - } - } - struct interface I2: I {} - struct S {} - access(all) attachment A for S: I2 {} - fun test() { - let s = attach A() to S() - let ref = &s as auth(E) &S - let i: auth(F) &Int = s[A]!.foo() - } - `) - - errs := RequireCheckerErrors(t, err, 1) - require.IsType(t, &sema.InvalidAttachmentEntitlementError{}, errs[0]) - }) - - t.Run("attachment inherited default entitled function entitlements not on base", func(t *testing.T) { - t.Parallel() - - _, err := ParseAndCheck(t, ` - entitlement E - entitlement F - struct interface I { - access(E) fun foo(): auth(F) &Int { - return &1 as auth(F) &Int - } - } - struct interface I2: I {} - struct S {} - access(all) attachment A for S: I2 {} - fun test() { - let s = attach A() to S() - let ref = &s as auth(E) &S - let i: auth(F) &Int = s[A]!.foo() - } - `) - - errs := RequireCheckerErrors(t, err, 1) - require.IsType(t, &sema.InvalidAttachmentEntitlementError{}, errs[0]) - }) } func TestCheckEntitlementTypeAnnotation(t *testing.T) { diff --git a/sema/errors.go b/sema/errors.go index 96edbf076..1760e33eb 100644 --- a/sema/errors.go +++ b/sema/errors.go @@ -1440,6 +1440,23 @@ func (e *InvalidEnumConformancesError) Error() string { return "enums cannot conform to interfaces" } +// InvalidAttachmentConformancesError + +type InvalidAttachmentConformancesError struct { + ast.Range +} + +var _ SemanticError = &InvalidAttachmentConformancesError{} +var _ errors.UserError = &InvalidAttachmentConformancesError{} + +func (*InvalidAttachmentConformancesError) isSemanticError() {} + +func (*InvalidAttachmentConformancesError) IsUserError() {} + +func (e *InvalidAttachmentConformancesError) Error() string { + return "attachments cannot conform to interfaces" +} + // ConformanceError // TODO: report each missing member and mismatch as note diff --git a/sema/nesting_test.go b/sema/nesting_test.go index cd0cd659c..0d43eed35 100644 --- a/sema/nesting_test.go +++ b/sema/nesting_test.go @@ -259,9 +259,10 @@ func TestCheckInvalidCompositeDeclarationNestedDuplicateNames(t *testing.T) { } `) - errs := RequireCheckerErrors(t, err, 1) + errs := RequireCheckerErrors(t, err, 2) assert.IsType(t, &sema.RedeclarationError{}, errs[0]) + assert.IsType(t, &sema.RedeclarationError{}, errs[1]) } func TestCheckCompositeDeclarationNestedConstructorAndType(t *testing.T) { diff --git a/stdlib/account.go b/stdlib/account.go index 66907a6f9..edb2d4de5 100644 --- a/stdlib/account.go +++ b/stdlib/account.go @@ -1731,7 +1731,8 @@ func changeAccountContracts( ) } - validator = validator.WithTypeRemovalEnabled(contractUpdateTypeRemovalEnabled) + validator = validator. + WithTypeRemovalEnabled(contractUpdateTypeRemovalEnabled) err = validator.Validate() handleContractUpdateError(err, newCode) diff --git a/stdlib/cadence_v0.42_to_v1_contract_upgrade_validator.go b/stdlib/cadence_v0.42_to_v1_contract_upgrade_validator.go index 7b13c6491..6d5cd7893 100644 --- a/stdlib/cadence_v0.42_to_v1_contract_upgrade_validator.go +++ b/stdlib/cadence_v0.42_to_v1_contract_upgrade_validator.go @@ -75,10 +75,6 @@ func (validator *CadenceV042ToV1ContractUpdateValidator) Location() common.Locat return validator.underlyingUpdateValidator.location } -func (validator *CadenceV042ToV1ContractUpdateValidator) isTypeRemovalEnabled() bool { - return validator.underlyingUpdateValidator.isTypeRemovalEnabled() -} - func (validator *CadenceV042ToV1ContractUpdateValidator) WithUserDefinedTypeChangeChecker( typeChangeCheckFunc func(oldTypeID common.TypeID, newTypeID common.TypeID) (checked, valid bool), ) *CadenceV042ToV1ContractUpdateValidator { @@ -86,6 +82,10 @@ func (validator *CadenceV042ToV1ContractUpdateValidator) WithUserDefinedTypeChan return validator } +func (validator *CadenceV042ToV1ContractUpdateValidator) isTypeRemovalEnabled() bool { + return validator.underlyingUpdateValidator.isTypeRemovalEnabled() +} + func (validator *CadenceV042ToV1ContractUpdateValidator) WithTypeRemovalEnabled( enabled bool, ) UpdateValidator { @@ -125,6 +125,7 @@ func (validator *CadenceV042ToV1ContractUpdateValidator) Validate() error { checkDeclarationUpdatability( validator, + validator.TypeComparator, oldRootDecl, newRootDecl, validator.checkConformanceV1, diff --git a/stdlib/contract_update_validation.go b/stdlib/contract_update_validation.go index 71e958f84..a67c7ec99 100644 --- a/stdlib/contract_update_validation.go +++ b/stdlib/contract_update_validation.go @@ -149,6 +149,7 @@ func (validator *ContractUpdateValidator) Validate() error { checkDeclarationUpdatability( validator, + validator.TypeComparator, oldRootDecl, newRootDecl, validator.checkConformance, @@ -299,6 +300,7 @@ func collectRemovedTypePragmas( func checkDeclarationUpdatability( validator UpdateValidator, + typeComparator *TypeComparator, oldDeclaration ast.Declaration, newDeclaration ast.Declaration, checkConformance checkConformanceFunc, @@ -327,13 +329,35 @@ func checkDeclarationUpdatability( checkFields(validator, oldDeclaration, newDeclaration) - checkNestedDeclarations(validator, oldDeclaration, newDeclaration, checkConformance) + checkNestedDeclarations( + validator, + typeComparator, + oldDeclaration, + newDeclaration, + checkConformance, + ) if newDecl, ok := newDeclaration.(*ast.CompositeDeclaration); ok { if oldDecl, ok := oldDeclaration.(*ast.CompositeDeclaration); ok { checkConformance(oldDecl, newDecl) } } + + // Check if the base type of the attachment has changed. + if oldAttachment, ok := oldDeclaration.(*ast.AttachmentDeclaration); ok && + oldAttachment.DeclarationKind() == common.DeclarationKindAttachment { + + // NOTE: no need to check declaration kinds match, already checked above + if newAttachment, ok := newDeclaration.(*ast.AttachmentDeclaration); ok { + err := typeComparator.CheckNominalTypeEquality( + oldAttachment.BaseType, + newAttachment.BaseType, + ) + if err != nil { + validator.report(err) + } + } + } } func checkFields( @@ -457,6 +481,7 @@ func checkTypeNotRemoved( func checkNestedDeclarations( validator UpdateValidator, + typeComparator *TypeComparator, oldDeclaration ast.Declaration, newDeclaration ast.Declaration, checkConformance checkConformanceFunc, @@ -503,7 +528,13 @@ func checkNestedDeclarations( continue } - checkDeclarationUpdatability(validator, oldNestedDecl, newNestedDecl, checkConformance) + checkDeclarationUpdatability( + validator, + typeComparator, + oldNestedDecl, + newNestedDecl, + checkConformance, + ) // If there's a matching new decl, then remove the old one from the map. delete(oldNominalTypeDecls, newNestedDecl.Identifier.Identifier) @@ -519,7 +550,13 @@ func checkNestedDeclarations( continue } - checkDeclarationUpdatability(validator, oldNestedDecl, newNestedDecl, checkConformance) + checkDeclarationUpdatability( + validator, + typeComparator, + oldNestedDecl, + newNestedDecl, + checkConformance, + ) // If there's a matching new decl, then remove the old one from the map. delete(oldNominalTypeDecls, newNestedDecl.Identifier.Identifier) @@ -535,7 +572,13 @@ func checkNestedDeclarations( continue } - checkDeclarationUpdatability(validator, oldNestedDecl, newNestedDecl, checkConformance) + checkDeclarationUpdatability( + validator, + typeComparator, + oldNestedDecl, + newNestedDecl, + checkConformance, + ) // If there's a matching new decl, then remove the old one from the map. delete(oldNominalTypeDecls, newNestedDecl.Identifier.Identifier) diff --git a/test_utils/runtime_utils/testinterface.go b/test_utils/runtime_utils/testinterface.go index 9b3b8199e..f874d7bbf 100644 --- a/test_utils/runtime_utils/testinterface.go +++ b/test_utils/runtime_utils/testinterface.go @@ -131,6 +131,7 @@ type TestRuntimeInterface struct { path interpreter.PathValue, capabilityBorrowType *interpreter.ReferenceStaticType, ) (bool, error) + OnMinimumRequiredVersion func() (string, error) lastUUID uint64 accountIDs map[common.Address]uint64 @@ -663,3 +664,10 @@ func (i *TestRuntimeInterface) ValidateAccountCapabilitiesPublish( capabilityBorrowType, ) } + +func (i *TestRuntimeInterface) MinimumRequiredVersion() (string, error) { + if i.OnMinimumRequiredVersion == nil { + return "", nil + } + return i.OnMinimumRequiredVersion() +}