Skip to content

Commit

Permalink
Merge pull request #2737 from darkdrag00nv2/map_function_array
Browse files Browse the repository at this point in the history
Introduce `map` in Fixed/Variable sized Array types
  • Loading branch information
SupunS authored Aug 23, 2023
2 parents a8519ad + 519407f commit 8cd7819
Show file tree
Hide file tree
Showing 4 changed files with 757 additions and 0 deletions.
108 changes: 108 additions & 0 deletions runtime/interpreter/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -2482,6 +2482,35 @@ func (v *ArrayValue) GetMember(interpreter *Interpreter, locationRange LocationR
)
},
)

case sema.ArrayTypeMapFunctionName:
return NewHostFunctionValue(
interpreter,
sema.ArrayMapFunctionType(
interpreter,
v.SemaType(interpreter),
),
func(invocation Invocation) Value {
interpreter := invocation.Interpreter

funcArgument, ok := invocation.Arguments[0].(FunctionValue)
if !ok {
panic(errors.NewUnreachableError())
}

transformFunctionType, ok := invocation.ArgumentTypes[0].(*sema.FunctionType)
if !ok {
panic(errors.NewUnreachableError())
}

return v.Map(
interpreter,
invocation.LocationRange,
funcArgument,
transformFunctionType,
)
},
)
}

return nil
Expand Down Expand Up @@ -3058,6 +3087,85 @@ func (v *ArrayValue) Filter(
)
}

func (v *ArrayValue) Map(
interpreter *Interpreter,
locationRange LocationRange,
procedure FunctionValue,
transformFunctionType *sema.FunctionType,
) Value {

elementTypeSlice := []sema.Type{v.semaType.ElementType(false)}
iterationInvocation := func(arrayElement Value) Invocation {
return NewInvocation(
interpreter,
nil,
nil,
[]Value{arrayElement},
elementTypeSlice,
nil,
locationRange,
)
}

procedureStaticType, ok := ConvertSemaToStaticType(interpreter, transformFunctionType).(FunctionStaticType)
if !ok {
panic(errors.NewUnreachableError())
}
returnType := procedureStaticType.ReturnType(interpreter)

var returnArrayStaticType ArrayStaticType
switch v.Type.(type) {
case VariableSizedStaticType:
returnArrayStaticType = NewVariableSizedStaticType(
interpreter,
returnType,
)
case ConstantSizedStaticType:
returnArrayStaticType = NewConstantSizedStaticType(
interpreter,
returnType,
int64(v.Count()),
)
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 {

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

if atreeValue == nil {
return nil
}

value := MustConvertStoredValue(interpreter, atreeValue)

mappedValue := procedure.invoke(iterationInvocation(value))
return mappedValue.Transfer(
interpreter,
locationRange,
atree.Address{},
false,
nil,
nil,
)
},
)
}

// NumberValue
type NumberValue interface {
ComparableValue
Expand Down
81 changes: 81 additions & 0 deletions runtime/sema/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -1802,6 +1802,12 @@ Returns a new array whose elements are filtered by applying the filter function
Available if the array element type is not resource-kinded.
`

const ArrayTypeMapFunctionName = "map"

const arrayTypeMapFunctionDocString = `
Returns a new array whose elements are produced by applying the mapper function on each element of the original array.
`

func getArrayMembers(arrayType ArrayType) map[string]MemberResolver {

members := map[string]MemberResolver{
Expand Down Expand Up @@ -1945,6 +1951,31 @@ func getArrayMembers(arrayType ArrayType) map[string]MemberResolver {
)
},
},
ArrayTypeMapFunctionName: {
Kind: common.DeclarationKindFunction,
Resolve: func(memoryGauge common.MemoryGauge, identifier string, targetRange ast.Range, report func(error)) *Member {
elementType := arrayType.ElementType(false)

// TODO: maybe allow for resource element type as a reference.
if elementType.IsResourceType() {
report(
&InvalidResourceArrayMemberError{
Name: identifier,
DeclarationKind: common.DeclarationKindFunction,
Range: targetRange,
},
)
}

return NewPublicFunctionMember(
memoryGauge,
arrayType,
identifier,
ArrayMapFunctionType(memoryGauge, arrayType),
arrayTypeMapFunctionDocString,
)
},
},
}

// TODO: maybe still return members but report a helpful error?
Expand Down Expand Up @@ -2289,6 +2320,56 @@ func ArrayFilterFunctionType(memoryGauge common.MemoryGauge, elementType Type) *
}
}

func ArrayMapFunctionType(memoryGauge common.MemoryGauge, arrayType ArrayType) *FunctionType {
// For [T] or [T; N]
// fun map(_ function: ((T): U)): [U]
// or
// fun map(_ function: ((T): U)): [U; N]

typeParameter := &TypeParameter{
Name: "U",
}

typeU := &GenericType{
TypeParameter: typeParameter,
}

var returnArrayType Type
switch arrayType := arrayType.(type) {
case *VariableSizedType:
returnArrayType = NewVariableSizedType(memoryGauge, typeU)
case *ConstantSizedType:
returnArrayType = NewConstantSizedType(memoryGauge, typeU, arrayType.Size)
default:
panic(errors.NewUnreachableError())
}

// transformFuncType: elementType -> U
transformFuncType := &FunctionType{
Parameters: []Parameter{
{
Identifier: "element",
TypeAnnotation: NewTypeAnnotation(arrayType.ElementType(false)),
},
},
ReturnTypeAnnotation: NewTypeAnnotation(typeU),
}

return &FunctionType{
TypeParameters: []*TypeParameter{
typeParameter,
},
Parameters: []Parameter{
{
Label: ArgumentLabelNotRequired,
Identifier: "transform",
TypeAnnotation: NewTypeAnnotation(transformFuncType),
},
},
ReturnTypeAnnotation: NewTypeAnnotation(returnArrayType),
}
}

// VariableSizedType is a variable sized array type
type VariableSizedType struct {
Type Type
Expand Down
98 changes: 98 additions & 0 deletions runtime/tests/checker/arrays_dictionaries_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1225,6 +1225,104 @@ func TestCheckResourceArrayFilterInvalid(t *testing.T) {
assert.IsType(t, &sema.InvalidResourceArrayMemberError{}, errs[0])
}

func TestCheckArrayMap(t *testing.T) {

t.Parallel()

_, err := ParseAndCheck(t, `
fun test() {
let x = [1, 2, 3]
let trueForEven =
fun (_ x: Int): Bool {
return x % 2 == 0
}
let y: [Bool] = x.map(trueForEven)
}
fun testFixedSize() {
let x : [Int; 5] = [1, 2, 3, 21, 30]
let trueForEvenInt =
fun (_ x: Int): Bool {
return x % 2 == 0
}
let y: [Bool; 5] = x.map(trueForEvenInt)
}
`)

require.NoError(t, err)
}

func TestCheckArrayMapInvalidArgs(t *testing.T) {

t.Parallel()

testInvalidArgs := func(code string, expectedErrors []sema.SemanticError) {
_, err := ParseAndCheck(t, code)

errs := RequireCheckerErrors(t, err, len(expectedErrors))

for i, e := range expectedErrors {
assert.IsType(t, e, errs[i])
}
}

testInvalidArgs(`
fun test() {
let x = [1, 2, 3]
let y = x.map(100)
}
`,
[]sema.SemanticError{
&sema.TypeMismatchError{},
&sema.TypeParameterTypeInferenceError{}, // since we're not passing a function.
},
)

testInvalidArgs(`
fun test() {
let x = [1, 2, 3]
let trueForEvenInt16 =
fun (_ x: Int16): Bool {
return x % 2 == 0
}
let y: [Bool] = x.map(trueForEvenInt16)
}
`,
[]sema.SemanticError{
&sema.TypeMismatchError{},
},
)
}

func TestCheckResourceArrayMapInvalid(t *testing.T) {

t.Parallel()

_, err := ParseAndCheck(t, `
resource X {}
fun test(): [Bool] {
let xs <- [<-create X()]
let allResources =
fun (_ x: @X): Bool {
destroy x
return true
}
let mappedXs: [Bool] = xs.map(allResources)
destroy xs
return mappedXs
}
`)

errs := RequireCheckerErrors(t, err, 1)

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

func TestCheckArrayContains(t *testing.T) {

t.Parallel()
Expand Down
Loading

0 comments on commit 8cd7819

Please sign in to comment.