Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend subtyping rule for restricted type #1480

Merged
merged 4 commits into from
Mar 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions runtime/sema/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -5123,12 +5123,11 @@ func checkSubTypeWithoutEquality(subType Type, superType Type) bool {

case *CompositeType:
// An unrestricted type `T`
// is a subtype of a restricted type `U{Vs}`: if `T == U`.
// is a subtype of a restricted type `U{Vs}`: if `T <: U`.
//
// The owner may freely restrict.

return typedSubType == typedSuperType.Type

return IsSubType(typedSubType, typedSuperType.Type)
}

switch subType {
Expand Down
66 changes: 62 additions & 4 deletions runtime/tests/checker/restriction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ func TestCheckRestrictedType(t *testing.T) {

t.Run("struct: no restrictions", func(t *testing.T) {

t.Parallel()

_, err := ParseAndCheck(t, `
struct S {}

Expand All @@ -55,6 +57,8 @@ func TestCheckRestrictedType(t *testing.T) {

t.Run("resource: one restriction", func(t *testing.T) {

t.Parallel()

_, err := ParseAndCheck(t, `
resource interface I1 {}

Expand All @@ -70,6 +74,8 @@ func TestCheckRestrictedType(t *testing.T) {

t.Run("struct: one restriction", func(t *testing.T) {

t.Parallel()

_, err := ParseAndCheck(t, `
struct interface I1 {}

Expand All @@ -85,6 +91,8 @@ func TestCheckRestrictedType(t *testing.T) {

t.Run("reference to resource restriction", func(t *testing.T) {

t.Parallel()

_, err := ParseAndCheck(t, `
resource R {}

Expand All @@ -97,6 +105,8 @@ func TestCheckRestrictedType(t *testing.T) {

t.Run("reference to struct restriction", func(t *testing.T) {

t.Parallel()

_, err := ParseAndCheck(t, `
struct S {}

Expand All @@ -109,6 +119,8 @@ func TestCheckRestrictedType(t *testing.T) {

t.Run("resource: non-conformance restriction", func(t *testing.T) {

t.Parallel()

_, err := ParseAndCheck(t, `
resource interface I {}

Expand All @@ -125,6 +137,8 @@ func TestCheckRestrictedType(t *testing.T) {

t.Run("struct: non-conformance restriction", func(t *testing.T) {

t.Parallel()

_, err := ParseAndCheck(t, `
struct interface I {}

Expand All @@ -141,6 +155,8 @@ func TestCheckRestrictedType(t *testing.T) {

t.Run("resource: duplicate restriction", func(t *testing.T) {

t.Parallel()

_, err := ParseAndCheck(t, `
resource interface I {}

Expand All @@ -157,6 +173,8 @@ func TestCheckRestrictedType(t *testing.T) {

t.Run("struct: duplicate restriction", func(t *testing.T) {

t.Parallel()

_, err := ParseAndCheck(t, `
struct interface I {}

Expand All @@ -173,6 +191,8 @@ func TestCheckRestrictedType(t *testing.T) {

t.Run("restricted resource, with structure interface restriction", func(t *testing.T) {

t.Parallel()

_, err := ParseAndCheck(t, `
struct interface I {}

Expand All @@ -188,6 +208,8 @@ func TestCheckRestrictedType(t *testing.T) {

t.Run("restricted struct, with resource interface restriction", func(t *testing.T) {

t.Parallel()

_, err := ParseAndCheck(t, `
resource interface I {}

Expand All @@ -203,6 +225,8 @@ func TestCheckRestrictedType(t *testing.T) {

t.Run("resource: non-concrete restricted type", func(t *testing.T) {

t.Parallel()

_, err := ParseAndCheck(t, `
resource interface I {}

Expand All @@ -219,6 +243,8 @@ func TestCheckRestrictedType(t *testing.T) {

t.Run("struct: non-concrete restricted type", func(t *testing.T) {

t.Parallel()

_, err := ParseAndCheck(t, `
struct interface I {}

Expand All @@ -235,6 +261,8 @@ func TestCheckRestrictedType(t *testing.T) {

t.Run("restricted resource interface ", func(t *testing.T) {

t.Parallel()

_, err := ParseAndCheck(t, `
resource interface I {}

Expand All @@ -243,14 +271,15 @@ func TestCheckRestrictedType(t *testing.T) {
let r: @I{} <- create R()
`)

errs := ExpectCheckerErrors(t, err, 2)
errs := ExpectCheckerErrors(t, err, 1)

assert.IsType(t, &sema.InvalidRestrictedTypeError{}, errs[0])
assert.IsType(t, &sema.TypeMismatchError{}, errs[1])
})

t.Run("restricted struct interface", func(t *testing.T) {

t.Parallel()

_, err := ParseAndCheck(t, `
struct interface I {}

Expand All @@ -259,10 +288,39 @@ func TestCheckRestrictedType(t *testing.T) {
let s: I{} = S()
`)

errs := ExpectCheckerErrors(t, err, 2)
errs := ExpectCheckerErrors(t, err, 1)

assert.IsType(t, &sema.InvalidRestrictedTypeError{}, errs[0])
assert.IsType(t, &sema.TypeMismatchError{}, errs[1])
})

t.Run("restricted type requirement", func(t *testing.T) {

t.Parallel()

_, err := ParseAndCheck(t, `
contract interface CI {
resource interface RI {}

resource R: RI {}

fun createR(): @R
}

contract C: CI {
resource R: CI.RI {}

fun createR(): @R {
return <- create R()
}
}

fun test() {
let r <- C.createR()
let r2: @CI.R{CI.RI} <- r
destroy r2
}
`)
require.NoError(t, err)
})
}

Expand Down
173 changes: 130 additions & 43 deletions runtime/tests/interpreter/transfer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,52 +34,139 @@ func TestInterpretTransferCheck(t *testing.T) {

t.Parallel()

ty := &sema.CompositeType{
Location: utils.TestLocation,
Identifier: "Fruit",
Kind: common.CompositeKindStructure,
}

valueDeclarations := stdlib.StandardLibraryValues{
{
Name: "fruit",
Type: ty,
// NOTE: not an instance of the type
ValueFactory: func(_ *interpreter.Interpreter) interpreter.Value {
return interpreter.NewStringValue("fruit")
t.Run("String value as composite", func(t *testing.T) {

t.Parallel()

ty := &sema.CompositeType{
Location: utils.TestLocation,
Identifier: "Fruit",
Kind: common.CompositeKindStructure,
}

valueDeclarations := stdlib.StandardLibraryValues{
{
Name: "fruit",
Type: ty,
// NOTE: not an instance of the type
ValueFactory: func(_ *interpreter.Interpreter) interpreter.Value {
return interpreter.NewStringValue("fruit")
},
Kind: common.DeclarationKindConstant,
},
Kind: common.DeclarationKindConstant,
},
}

typeDeclarations := stdlib.StandardLibraryTypes{
{
Name: ty.Identifier,
Type: ty,
Kind: common.DeclarationKindStructure,
},
}

inter, err := parseCheckAndInterpretWithOptions(t,
`
fun test() {
let alsoFruit: Fruit = fruit
}
`,
ParseCheckAndInterpretOptions{
CheckerOptions: []sema.Option{
sema.WithPredeclaredValues(valueDeclarations.ToSemaValueDeclarations()),
sema.WithPredeclaredTypes(typeDeclarations.ToTypeDeclarations()),
}

typeDeclarations := stdlib.StandardLibraryTypes{
{
Name: ty.Identifier,
Type: ty,
Kind: common.DeclarationKindStructure,
},
Options: []interpreter.Option{
interpreter.WithPredeclaredValues(valueDeclarations.ToInterpreterValueDeclarations()),
}

inter, err := parseCheckAndInterpretWithOptions(t,
`
fun test() {
let alsoFruit: Fruit = fruit
}
`,
ParseCheckAndInterpretOptions{
CheckerOptions: []sema.Option{
sema.WithPredeclaredValues(valueDeclarations.ToSemaValueDeclarations()),
sema.WithPredeclaredTypes(typeDeclarations.ToTypeDeclarations()),
},
Options: []interpreter.Option{
interpreter.WithPredeclaredValues(valueDeclarations.ToInterpreterValueDeclarations()),
},
},
},
)
require.NoError(t, err)
)
require.NoError(t, err)

_, err = inter.Invoke("test")
require.Error(t, err)

require.ErrorAs(t, err, &interpreter.ValueTransferTypeError{})
})

t.Run("contract and restricted type", func(t *testing.T) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests passed the type checking (correctly) and only failed at run-time due to the defensive value transfer check, which in addition to the static check, also checks subtyping for the underlying value.


t.Parallel()

inter, err := parseCheckAndInterpretWithOptions(t,
`
contract interface CI {
resource interface RI {}

resource R: RI {}

fun createR(): @R
}

contract C: CI {
resource R: CI.RI {}

_, err = inter.Invoke("test")
require.Error(t, err)
fun createR(): @R {
return <- create R()
}
}

fun test() {
let r <- C.createR()
let r2: @CI.R <- r as @CI.R
let r3: @CI.R{CI.RI} <- r2
destroy r3
}
`,
ParseCheckAndInterpretOptions{
Options: []interpreter.Option{
makeContractValueHandler(nil, nil, nil),
},
},
)
require.NoError(t, err)

_, err = inter.Invoke("test")
require.NoError(t, err)
})

t.Run("contract and restricted type, reference", func(t *testing.T) {

t.Parallel()

inter, err := parseCheckAndInterpretWithOptions(t,
`
contract interface CI {
resource interface RI {}

resource R: RI {}

fun createR(): @R
}

contract C: CI {
resource R: CI.RI {}

fun createR(): @R {
return <- create R()
}
}

fun test() {
let r <- C.createR()
let ref: &CI.R = &r as &CI.R
let restrictedRef: &CI.R{CI.RI} = ref
destroy r
}
`,
ParseCheckAndInterpretOptions{
Options: []interpreter.Option{
makeContractValueHandler(nil, nil, nil),
},
},
)
require.NoError(t, err)

require.ErrorAs(t, err, &interpreter.ValueTransferTypeError{})
_, err = inter.Invoke("test")
require.NoError(t, err)
})
}