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

Introduce toConstantSized to VariableSizedArray type #3028

Merged
merged 5 commits into from
Jan 19, 2024
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
88 changes: 88 additions & 0 deletions runtime/interpreter/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -2600,6 +2600,35 @@
)
},
)

case sema.ArrayTypeToConstantSizedFunctionName:
return NewHostFunctionValue(
interpreter,
sema.ArrayToConstantSizedFunctionType(
v.SemaType(interpreter).ElementType(false),
),
func(invocation Invocation) Value {
interpreter := invocation.Interpreter

typeParameterPair := invocation.TypeParameterTypes.Oldest()
if typeParameterPair == nil {
panic(errors.NewUnreachableError())

Check warning on line 2615 in runtime/interpreter/value.go

View check run for this annotation

Codecov / codecov/patch

runtime/interpreter/value.go#L2615

Added line #L2615 was not covered by tests
}

ty := typeParameterPair.Value

constantSizedArrayType, ok := ty.(*sema.ConstantSizedType)
if !ok {
panic(errors.NewUnreachableError())

Check warning on line 2622 in runtime/interpreter/value.go

View check run for this annotation

Codecov / codecov/patch

runtime/interpreter/value.go#L2622

Added line #L2622 was not covered by tests
}

return v.ToConstantSized(
interpreter,
invocation.LocationRange,
constantSizedArrayType.Size,
)
},
)
}

return nil
Expand Down Expand Up @@ -3302,6 +3331,65 @@
)
}

func (v *ArrayValue) ToConstantSized(
interpreter *Interpreter,
locationRange LocationRange,
expectedConstantSizedArraySize int64,
) Value {
if int64(v.Count()) != expectedConstantSizedArraySize {
return NilOptionalValue
}

var returnArrayStaticType ArrayStaticType
switch v.Type.(type) {
case *VariableSizedStaticType:
returnArrayStaticType = NewConstantSizedStaticType(
interpreter,
v.Type.ElementType(),
expectedConstantSizedArraySize,
)
default:
panic(errors.NewUnreachableError())

Check warning on line 3352 in runtime/interpreter/value.go

View check run for this annotation

Codecov / codecov/patch

runtime/interpreter/value.go#L3351-L3352

Added lines #L3351 - L3352 were not covered by tests
}

iterator, err := v.array.Iterator()
if err != nil {
panic(errors.NewExternalError(err))

Check warning on line 3357 in runtime/interpreter/value.go

View check run for this annotation

Codecov / codecov/patch

runtime/interpreter/value.go#L3357

Added line #L3357 was not covered by tests
}

return NewArrayValueWithIterator(
interpreter,
returnArrayStaticType,
common.ZeroAddress,
uint64(v.Count()),
func() Value {

// Meter computation for iterating the array.
interpreter.ReportComputation(common.ComputationKindLoop, 1)

atreeValue, err := iterator.Next()
if err != nil {
panic(errors.NewExternalError(err))

Check warning on line 3372 in runtime/interpreter/value.go

View check run for this annotation

Codecov / codecov/patch

runtime/interpreter/value.go#L3372

Added line #L3372 was not covered by tests
}

if atreeValue == nil {
return nil
}

value := MustConvertStoredValue(interpreter, atreeValue)

return value.Transfer(
interpreter,
locationRange,
atree.Address{},
false,
nil,
nil,
)
},
)
}

// NumberValue
type NumberValue interface {
ComparableValue
Expand Down
89 changes: 89 additions & 0 deletions runtime/sema/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -2131,6 +2131,13 @@
Available if the array is constant sized and the element type is not resource-kinded.
`

const ArrayTypeToConstantSizedFunctionName = "toConstantSized"

const arrayTypeToConstantSizedFunctionDocString = `
Returns a new constant-sized array with the copy of the contents of the given array.
Available if the array is variable-sized and the element type is not resource-kinded.
`

var insertMutateEntitledAccess = NewEntitlementSetAccess(
[]*EntitlementType{
InsertType,
Expand Down Expand Up @@ -2497,6 +2504,31 @@
)
},
}

members[ArrayTypeToConstantSizedFunctionName] = MemberResolver{
Kind: common.DeclarationKindFunction,
Resolve: func(memoryGauge common.MemoryGauge, identifier string, targetRange ast.Range, report func(error)) *Member {
elementType := arrayType.ElementType(false)

if elementType.IsResourceType() {
report(
&InvalidResourceArrayMemberError{
Name: identifier,
DeclarationKind: common.DeclarationKindFunction,
Range: targetRange,
},
)
}

return NewPublicFunctionMember(
memoryGauge,
arrayType,
identifier,
ArrayToConstantSizedFunctionType(elementType),
arrayTypeToConstantSizedFunctionDocString,
)
},
}
}

if _, ok := arrayType.(*ConstantSizedType); ok {
Expand Down Expand Up @@ -2677,6 +2709,63 @@
)
}

func ArrayToConstantSizedFunctionType(elementType Type) *FunctionType {
// Ideally this should have a typebound of [T; _] but since we don't know
// the size of the ConstantSizedArray, we omit specifying the bound.
typeParameter := &TypeParameter{
Name: "T",
}

typeAnnotation := NewTypeAnnotation(
&GenericType{
TypeParameter: typeParameter,
},
)

return &FunctionType{
Purity: FunctionPurityView,
TypeParameters: []*TypeParameter{
typeParameter,
},
ReturnTypeAnnotation: NewTypeAnnotation(
&OptionalType{
Type: typeAnnotation.Type,
},
),
TypeArgumentsCheck: func(
memoryGauge common.MemoryGauge,
typeArguments *TypeParameterTypeOrderedMap,
astTypeArguments []*ast.TypeAnnotation,
invocationRange ast.HasPosition,
report func(error),
) {
typeArg, ok := typeArguments.Get(typeParameter)
if !ok || typeArg == nil {
// checker should prevent this
panic(errors.NewUnreachableError())

Check warning on line 2745 in runtime/sema/type.go

View check run for this annotation

Codecov / codecov/patch

runtime/sema/type.go#L2744-L2745

Added lines #L2744 - L2745 were not covered by tests
}

constArrayType, ok := typeArg.(*ConstantSizedType)
if !ok || constArrayType.Type != elementType {
errorRange := invocationRange
if len(astTypeArguments) > 0 {
errorRange = astTypeArguments[0]
}

report(&InvalidTypeArgumentError{
TypeArgumentName: typeParameter.Name,
Range: ast.NewRangeFromPositioned(memoryGauge, errorRange),
Details: fmt.Sprintf(
"Type argument for %s must be [%s; _]",
ArrayTypeToConstantSizedFunctionName,
elementType,
),
})
}
},
}
}

func ArrayReverseFunctionType(arrayType ArrayType) *FunctionType {
return &FunctionType{
Parameters: []Parameter{},
Expand Down
106 changes: 106 additions & 0 deletions runtime/tests/checker/arrays_dictionaries_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2551,3 +2551,109 @@ func TestCheckResourceArrayToVariableSizedInvalid(t *testing.T) {

assert.IsType(t, &sema.InvalidResourceArrayMemberError{}, errs[0])
}

func TestCheckArrayToConstantSized(t *testing.T) {

t.Parallel()

_, err := ParseAndCheck(t, `
fun testInt() {
let x: [Int] = [1, 2, 3, 100]
let y: [Int; 4]? = x.toConstantSized<[Int;4]>()
}

fun testString() {
let x: [String] = ["ab", "cd", "ef", "gh"]
let y: [String; 4]? = x.toConstantSized<[String; 4]>()
let y_incorrect_size: [String; 3]? = x.toConstantSized<[String; 3]>()
}
`)

require.NoError(t, err)
}

func TestCheckArrayToConstantSizedInvalidArgs(t *testing.T) {

t.Parallel()

_, err := ParseAndCheck(t, `
fun test() {
let x: [Int16] = [1, 2, 3]
let y = x.toConstantSized<[Int16; 3]>(100)
}
`)

errs := RequireCheckerErrors(t, err, 1)

assert.IsType(t, &sema.ExcessiveArgumentsError{}, errs[0])
}

func TestCheckArrayToConstantSizedInvalidTypeArgument(t *testing.T) {

t.Parallel()

_, err := ParseAndCheck(t, `
fun test() {
let x: [Int16] = [1, 2, 3]
let y = x.toConstantSized<String>()
}
`)

errs := RequireCheckerErrors(t, err, 1)

assert.IsType(t, &sema.InvalidTypeArgumentError{}, errs[0])
}

func TestCheckArrayToConstantSizedInvalidTypeArgumentInnerType(t *testing.T) {

t.Parallel()

_, err := ParseAndCheck(t, `
fun test() {
let x: [Int16] = [1, 2, 3]
let y = x.toConstantSized<[Int; 3]>()
}
`)

errs := RequireCheckerErrors(t, err, 1)

assert.IsType(t, &sema.InvalidTypeArgumentError{}, errs[0])
}

func TestCheckConstantSizedArrayToConstantSizedInvalid(t *testing.T) {

t.Parallel()

_, err := ParseAndCheck(t, `
fun test() : [Int; 3]? {
let xs: [Int; 3] = [1, 2, 3]

return xs.toConstantSized<[Int; 3]>()
}
`)

errs := RequireCheckerErrors(t, err, 1)

assert.IsType(t, &sema.NotDeclaredMemberError{}, errs[0])
}

func TestCheckResourceArrayToConstantSizedInvalid(t *testing.T) {

t.Parallel()

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

fun test() : @[X;1]? {
let xs: @[X] <- [<-create X()]

let constsized_xs <- xs.toConstantSized<@[X; 1]>()
destroy xs
return <-constsized_xs
}
`)

errs := RequireCheckerErrors(t, err, 1)

assert.IsType(t, &sema.InvalidResourceArrayMemberError{}, errs[0])
}
Loading
Loading