Skip to content

Commit

Permalink
Merge pull request #3028 from darkdrag00nv2/2530-var-const-array
Browse files Browse the repository at this point in the history
  • Loading branch information
turbolent authored Jan 19, 2024
2 parents 23a0f59 + a38010d commit b1869bb
Show file tree
Hide file tree
Showing 4 changed files with 502 additions and 0 deletions.
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 @@ func (v *ArrayValue) GetMember(interpreter *Interpreter, locationRange LocationR
)
},
)

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())
}

ty := typeParameterPair.Value

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

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

return nil
Expand Down Expand Up @@ -3302,6 +3331,65 @@ func (v *ArrayValue) ToVariableSized(
)
}

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())
}

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

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))
}

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 @@ Returns a new variable-sized array with the copy of the contents of the given ar
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 @@ func getArrayMembers(arrayType ArrayType) map[string]MemberResolver {
)
},
}

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 ArrayToVariableSizedFunctionType(elementType Type) *FunctionType {
)
}

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())
}

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

0 comments on commit b1869bb

Please sign in to comment.