Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
12 changes: 12 additions & 0 deletions bbq/vm/value_array.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,18 @@ func init() {
interpreter.NativeArrayMapFunction,
),
)

registerBuiltinTypeBoundFunction(
typeQualifier,
NewNativeFunctionValueWithDerivedType(
sema.ArrayTypeReduceFunctionName,
func(receiver Value, context interpreter.ValueStaticTypeContext) *sema.FunctionType {
elementType := arrayElementTypeFromValue(receiver, context)
return sema.ArrayReduceFunctionType(elementType)
},
interpreter.NativeArrayReduceFunction,
),
)
}

// Functions available only for variable-sized arrays.
Expand Down
139 changes: 139 additions & 0 deletions interpreter/misc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11281,6 +11281,145 @@ func TestInterpretArrayMap(t *testing.T) {
})
}

func TestInterpretArrayReduce(t *testing.T) {
t.Parallel()

t.Run("with variable sized array - sum", func(t *testing.T) {
t.Parallel()

inter := parseCheckAndPrepare(t, `
let xs = [1, 2, 3, 4, 5]

let sum =
fun (acc: Int, x: Int): Int {
return acc + x
}

fun reduce(): Int {
return xs.reduce(initial: 0, sum)
}
`)

val, err := inter.Invoke("reduce")
require.NoError(t, err)

AssertValuesEqual(
t,
inter,
interpreter.NewUnmeteredIntValueFromInt64(15),
val,
)
})

t.Run("with variable sized array - product", func(t *testing.T) {
t.Parallel()

inter := parseCheckAndPrepare(t, `
let xs = [1, 2, 3, 4]

let product =
fun (acc: Int, x: Int): Int {
return acc * x
}

fun reduce(): Int {
return xs.reduce(initial: 1, product)
}
`)

val, err := inter.Invoke("reduce")
require.NoError(t, err)

AssertValuesEqual(
t,
inter,
interpreter.NewUnmeteredIntValueFromInt64(24),
val,
)
})

t.Run("with empty array", func(t *testing.T) {
t.Parallel()

inter := parseCheckAndPrepare(t, `
let xs: [Int] = []

let sum =
fun (acc: Int, x: Int): Int {
return acc + x
}

fun reduce(): Int {
return xs.reduce(initial: 42, sum)
}
`)

val, err := inter.Invoke("reduce")
require.NoError(t, err)

AssertValuesEqual(
t,
inter,
interpreter.NewUnmeteredIntValueFromInt64(42),
val,
)
})

t.Run("with fixed sized array", func(t *testing.T) {
t.Parallel()

inter := parseCheckAndPrepare(t, `
let xs: [Int; 4] = [10, 20, 30, 40]

let sum =
fun (acc: Int, x: Int): Int {
return acc + x
}

fun reduce(): Int {
return xs.reduce(initial: 0, sum)
}
`)

val, err := inter.Invoke("reduce")
require.NoError(t, err)

AssertValuesEqual(
t,
inter,
interpreter.NewUnmeteredIntValueFromInt64(100),
val,
)
})

t.Run("with type conversion", func(t *testing.T) {
t.Parallel()

inter := parseCheckAndPrepare(t, `
let xs = [1, 2, 3]

let concat =
fun (acc: String, x: Int): String {
return acc.concat(x.toString())
}

fun reduce(): String {
return xs.reduce(initial: "", concat)
}
`)

val, err := inter.Invoke("reduce")
require.NoError(t, err)

AssertValuesEqual(
t,
inter,
interpreter.NewUnmeteredStringValue("123"),
val,
)
})
}

func TestInterpretArrayToVariableSized(t *testing.T) {
t.Parallel()

Expand Down
69 changes: 69 additions & 0 deletions interpreter/value_array.go
Original file line number Diff line number Diff line change
Expand Up @@ -988,6 +988,16 @@ func (v *ArrayValue) GetMethod(context MemberAccessibleContext, name string) Fun
NativeArrayMapFunction,
)

case sema.ArrayTypeReduceFunctionName:
return NewBoundHostFunctionValue(
context,
v,
sema.ArrayReduceFunctionType(
v.SemaType(context).ElementType(false),
),
NativeArrayReduceFunction,
)

case sema.ArrayTypeToVariableSizedFunctionName:
return NewBoundHostFunctionValue(
context,
Expand Down Expand Up @@ -1696,6 +1706,50 @@ func (v *ArrayValue) Map(
)
}

func (v *ArrayValue) Reduce(
context InvocationContext,
initial Value,
reducer FunctionValue,
) Value {

elementType := v.SemaType(context).ElementType(false)

reducerFunctionType := reducer.FunctionType(context)
parameterTypes := reducerFunctionType.ParameterTypes()
returnType := reducerFunctionType.ReturnTypeAnnotation.Type

accumulator := initial

v.Iterate(
context,
func(element Value) (resume bool) {

// Meter computation for iterating the array.
common.UseComputation(
context,
common.LoopComputationUsage,
)

argumentTypes := []sema.Type{returnType, elementType}

accumulator = invokeFunctionValue(
context,
reducer,
[]Value{accumulator, element},
argumentTypes,
parameterTypes,
returnType,
nil,
)

return true
},
false,
)

return accumulator
}

func (v *ArrayValue) ForEach(
context IterableValueForeachContext,
_ sema.Type,
Expand Down Expand Up @@ -2049,6 +2103,21 @@ var NativeArrayMapFunction = NativeFunction(
},
)

var NativeArrayReduceFunction = NativeFunction(
func(
context NativeFunctionContext,
_ TypeArgumentsIterator,
receiver Value,
args []Value,
) Value {
thisArray := AssertValueOfType[*ArrayValue](receiver)
initial := args[0]
funcValue := AssertValueOfType[FunctionValue](args[1])

return thisArray.Reduce(context, initial, funcValue)
},
)

var NativeArrayToVariableSizedFunction = NativeFunction(
func(
context NativeFunctionContext,
Expand Down
97 changes: 97 additions & 0 deletions sema/arrays_dictionaries_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1329,6 +1329,103 @@ func TestCheckResourceArrayMapInvalid(t *testing.T) {
assert.IsType(t, &sema.InvalidResourceArrayMemberError{}, errs[0])
}

func TestCheckArrayReduce(t *testing.T) {

t.Parallel()

_, err := ParseAndCheck(t, `
fun test() {
let x = [1, 2, 3]
let sum =
fun (acc: Int, x: Int): Int {
return acc + x
}

let y: Int = x.reduce(initial: 0, sum)
}

fun testFixedSize() {
let x : [Int; 5] = [1, 2, 3, 21, 30]
let product =
fun (acc: Int, x: Int): Int {
return acc * x
}

let y: Int = x.reduce(initial: 1, product)
}
`)

require.NoError(t, err)
}

func TestCheckArrayReduceInvalidArgs(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.reduce(initial: 0, 100)
}
`,
[]sema.SemanticError{
&sema.TypeMismatchError{},
},
)

testInvalidArgs(`
fun test() {
let x = [1, 2, 3]
let sumInt16 =
fun (acc: Int16, x: Int16): Int16 {
return acc + x
}

let y: Int = x.reduce(initial: 0, sumInt16)
}
`,
[]sema.SemanticError{
&sema.TypeMismatchError{},
},
)
}

func TestCheckResourceArrayReduceInvalid(t *testing.T) {

t.Parallel()

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

fun test(): Int {
let xs <- [<-create X()]
let reducer =
fun (acc: Int, x: @X): Int {
destroy x
return acc + 1
}

let count: Int = xs.reduce(initial: 0, reducer)
destroy xs
return count
}
`)

errs := RequireCheckerErrors(t, err, 1)

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

func TestCheckArrayContains(t *testing.T) {

t.Parallel()
Expand Down
Loading
Loading