Skip to content

Commit 78f875b

Browse files
committed
Merge branch 'main' of https://github.com/synnaxlabs/synnax
2 parents ee7beae + 1328d2f commit 78f875b

104 files changed

Lines changed: 6410 additions & 562 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

arc/go/analyzer/function/function_test.go

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ var _ = Describe("Function Analyzer", func() {
334334
last_error $= error
335335
derivative := (error - last_error) / f32(dt)
336336
d := kd * derivative
337-
return p + i + d
337+
return f64(p + i + d)
338338
}
339339
`, resolver)
340340
})
@@ -379,8 +379,70 @@ var _ = Describe("Function Analyzer", func() {
379379
}
380380
}`,
381381
Equal("function 'dog' must return a value of type f64 on all paths")),
382+
Entry("concrete f64 expression returned from f32 function",
383+
`func dog(x f64) f32 { return x * 2.0 }`,
384+
ContainSubstring("cannot return f64 from 'dog': expected f32")),
385+
Entry("concrete f32 expression returned from f64 function",
386+
`func dog(x f32) f64 { return x * 2.0 }`,
387+
ContainSubstring("cannot return f32 from 'dog': expected f64")),
388+
Entry("concrete i64 expression returned from f32 function",
389+
`func dog(x i64) f32 { return x * 2 }`,
390+
ContainSubstring("cannot return i64 from 'dog': expected f32")),
391+
Entry("concrete f64 expression returned from i32 function",
392+
`func dog(x f64) i32 { return x * 2.0 }`,
393+
ContainSubstring("cannot return f64 from 'dog': expected i32")),
394+
Entry("concrete i32 expression returned from f32 function",
395+
`func dog(x i32) f32 { return x * 2 }`,
396+
ContainSubstring("cannot return i32 from 'dog': expected f32")),
382397
)
383398

399+
It("Should reject f64 channel multiplied by f32 channel", func() {
400+
resolver := symbol.MapResolver{
401+
"ch_f64": {Name: "ch_f64", Kind: symbol.KindChannel, Type: types.Chan(types.F64()), ID: 1},
402+
"ch_f32": {Name: "ch_f32", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 2},
403+
}
404+
analyzeExpectError(
405+
`func calc() f64 { return ch_f64 * ch_f32 }`,
406+
resolver,
407+
ContainSubstring("cannot use f64 and f32 in * operation"),
408+
)
409+
})
410+
411+
It("Should reject f32 channel multiplied by f64 channel", func() {
412+
resolver := symbol.MapResolver{
413+
"ch_f32": {Name: "ch_f32", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 1},
414+
"ch_f64": {Name: "ch_f64", Kind: symbol.KindChannel, Type: types.Chan(types.F64()), ID: 2},
415+
}
416+
ctx := analyzeProgram(`func calc() f64 { return ch_f32 * ch_f64 }`, resolver)
417+
Expect(*ctx.Diagnostics).ToNot(BeEmpty())
418+
Expect(ctx.Diagnostics.String()).To(ContainSubstring("cannot use f32 and f64 in * operation"))
419+
})
420+
421+
It("Should reject f32 return when literals mask f64 channel in denominator", func() {
422+
resolver := symbol.MapResolver{
423+
"input_power": {Name: "input_power", Kind: symbol.KindChannel, Type: types.Chan(types.I64()), ID: 1},
424+
"drive_speed_fb": {Name: "drive_speed_fb", Kind: symbol.KindChannel, Type: types.Chan(types.F64()), ID: 2},
425+
}
426+
ctx := analyzeProgram(
427+
`func calc() f32 { return f32(input_power*60)/(2*(3.14159)*(drive_speed_fb)) }`,
428+
resolver,
429+
)
430+
Expect(*ctx.Diagnostics).ToNot(BeEmpty())
431+
Expect(ctx.Diagnostics.String()).To(ContainSubstring("cannot use f32 and f64 in / operation"))
432+
})
433+
434+
It("Should reject f32 expression returned from f64 function with channel inputs", func() {
435+
resolver := symbol.MapResolver{
436+
"input_power": {Name: "input_power", Kind: symbol.KindChannel, Type: types.Chan(types.I64()), ID: 1},
437+
"drive_speed_fb": {Name: "drive_speed_fb", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 2},
438+
}
439+
analyzeExpectError(
440+
`func calc() f64 { return f32(input_power*60)/(2*(3.14159)*(drive_speed_fb)) }`,
441+
resolver,
442+
ContainSubstring("cannot return f32 from 'calc': expected f64"),
443+
)
444+
})
445+
384446
Context("complete return coverage", func() {
385447
It("should accept if-else with returns on all paths", func() {
386448
analyzeExpectSuccess(`

arc/go/analyzer/statement/statement.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -337,8 +337,7 @@ func analyzeReturnStatement(ctx context.Context[parser.IReturnStatementContext])
337337
}
338338
} else {
339339
isLiteral := isLiteralExpression(context.Child(ctx, returnExpr))
340-
useLiteralRules := isLiteral || (actualReturnType.IsNumeric() && expectedReturnType.IsNumeric())
341-
if useLiteralRules {
340+
if isLiteral {
342341
if !atypes.LiteralAssignmentCompatible(expectedReturnType, actualReturnType) {
343342
ctx.Diagnostics.Add(diagnostics.Errorf(ctx.AST,
344343
"cannot return %s from '%s': expected %s",

arc/go/analyzer/types/infer_expression.go

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -75,40 +75,49 @@ func InferRelational(ctx context.Context[parser.IRelationalExpressionContext]) t
7575
return types.Type{}
7676
}
7777

78+
func inferBinaryType(elemType, nextElem types.Type) (types.Type, bool) {
79+
if elemType.Kind == types.KindVariable && nextElem.Kind != types.KindVariable {
80+
elemType = nextElem
81+
}
82+
// Only early-return on incompatibility when both types are concrete.
83+
// When either is a type variable (literal), we must keep iterating
84+
// to discover later concrete types that determine the actual type.
85+
bothConcrete := elemType.Kind != types.KindVariable && nextElem.Kind != types.KindVariable
86+
if bothConcrete && !Compatible(elemType, nextElem) {
87+
return elemType, true
88+
}
89+
return elemType, false
90+
}
91+
7892
func InferAdditive(ctx context.Context[parser.IAdditiveExpressionContext]) types.Type {
7993
multiplicatives := ctx.AST.AllMultiplicativeExpression()
8094
if len(multiplicatives) == 0 {
8195
return types.Type{}
8296
}
8397
if len(multiplicatives) > 1 {
8498
firstType := InferMultiplicative(context.Child(ctx, multiplicatives[0]))
85-
// Track if any operand is a series - if so, result is a series
8699
isSeries := firstType.Kind == types.KindSeries
87-
// Use series element type when available, as it's more concrete than scalar type variables
88100
elemType := firstType.Unwrap()
89101

90102
for i := 1; i < len(multiplicatives); i++ {
91103
nextType := InferMultiplicative(context.Child(ctx, multiplicatives[i]))
92104
if nextType.Kind == types.KindSeries {
93105
isSeries = true
94-
// Prefer series element type over scalar type variable
95106
nextElem := nextType.Unwrap()
96107
if nextElem.Kind != types.KindVariable {
97108
elemType = nextElem
98109
}
99-
if !Compatible(elemType, nextElem) {
110+
bothConcrete := elemType.Kind != types.KindVariable && nextElem.Kind != types.KindVariable
111+
if bothConcrete && !Compatible(elemType, nextElem) {
100112
if isSeries {
101113
return types.Series(elemType)
102114
}
103115
return elemType
104116
}
105117
} else {
106-
nextElem := nextType.Unwrap()
107-
// Prefer concrete type over type variable (literal)
108-
if elemType.Kind == types.KindVariable && nextElem.Kind != types.KindVariable {
109-
elemType = nextElem
110-
}
111-
if !Compatible(elemType, nextElem) {
118+
var earlyReturn bool
119+
elemType, earlyReturn = inferBinaryType(elemType, nextType.Unwrap())
120+
if earlyReturn {
112121
if isSeries {
113122
return types.Series(elemType)
114123
}
@@ -131,32 +140,27 @@ func InferMultiplicative(ctx context.Context[parser.IMultiplicativeExpressionCon
131140
}
132141
if len(powers) > 1 {
133142
firstType := InferPower(context.Child(ctx, powers[0]))
134-
// Track if any operand is a series - if so, result is a series
135143
isSeries := firstType.Kind == types.KindSeries
136-
// Use series element type when available, as it's more concrete than scalar type variables
137144
elemType := firstType.Unwrap()
138145

139146
for i := 1; i < len(powers); i++ {
140147
nextType := InferPower(context.Child(ctx, powers[i]))
141148
if nextType.Kind == types.KindSeries {
142149
isSeries = true
143-
// Prefer series element type over scalar type variable
144150
nextElem := nextType.Unwrap()
145151
if nextElem.Kind != types.KindVariable {
146152
elemType = nextElem
147153
}
148-
if !Compatible(elemType, nextElem) {
154+
bothConcrete := elemType.Kind != types.KindVariable && nextElem.Kind != types.KindVariable
155+
if bothConcrete && !Compatible(elemType, nextElem) {
149156
resultType := lo.Ternary(isSeries, types.Series(elemType), elemType)
150157
ctx.TypeMap[ctx.AST] = resultType
151158
return resultType
152159
}
153160
} else {
154-
nextElem := nextType.Unwrap()
155-
// Prefer concrete type over type variable (literal)
156-
if elemType.Kind == types.KindVariable && nextElem.Kind != types.KindVariable {
157-
elemType = nextElem
158-
}
159-
if !Compatible(elemType, nextElem) {
161+
var earlyReturn bool
162+
elemType, earlyReturn = inferBinaryType(elemType, nextType.Unwrap())
163+
if earlyReturn {
160164
resultType := lo.Ternary(isSeries, types.Series(elemType), elemType)
161165
ctx.TypeMap[ctx.AST] = resultType
162166
return resultType

arc/go/analyzer/types/infer_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,26 @@ var _ = Describe("Type Inference", func() {
248248
})
249249
})
250250

251+
Context("literals between concrete channel types", func() {
252+
It("should infer f64 when concrete f64 channel follows two literals", func() {
253+
// 2 * 3.14159 * pressure where pressure is f64
254+
// InferMultiplicative should NOT early-return on the two literals
255+
// and should discover the f64 from pressure
256+
t := inferExprType(bCtx, testResolver, "2*(3.14159)*(pressure)")
257+
Expect(t.Kind).To(Equal(types.KindF64))
258+
})
259+
260+
It("should infer f32 when concrete f32 channel follows two literals", func() {
261+
t := inferExprType(bCtx, testResolver, "2*(3.14159)*(temp_sensor)")
262+
Expect(t.Kind).To(Equal(types.KindF32))
263+
})
264+
265+
It("should infer i64 when concrete i64 channel follows two literals", func() {
266+
t := inferExprType(bCtx, testResolver, "2*3*(i64_ch)")
267+
Expect(t.Kind).To(Equal(types.KindI64))
268+
})
269+
})
270+
251271
Context("literal-left across mixed additive and multiplicative", func() {
252272
It("should infer f32 for literal / f32 channel (multiplicative within additive)", func() {
253273
t := inferExprType(bCtx, testResolver, "1000.0 / temp_sensor + 1")

arc/go/compiler/compiler_test.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3609,4 +3609,88 @@ var _ = Describe("Compiler", func() {
36093609
}`, int32(6)),
36103610
)
36113611
})
3612+
3613+
Describe("Mixed numeric type channel arithmetic", func() {
3614+
It("Should compile torque-like expression with i64 and f32 channels", func() {
3615+
bindMockChannelModule(r, map[string]any{
3616+
"read_i64": func(_ context.Context, channelID uint32) int64 { return 500 },
3617+
"read_f32": func(_ context.Context, channelID uint32) float32 { return float32(1000.0) },
3618+
})
3619+
resolver := symbol.MapResolver(map[string]symbol.Symbol{
3620+
"input_power": {Name: "input_power", Kind: symbol.KindChannel, Type: types.Chan(types.I64()), ID: 1},
3621+
"drive_speed_fb": {Name: "drive_speed_fb", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 2},
3622+
})
3623+
output := MustSucceed(compileWithHostImports(`
3624+
func calculation() f32 {
3625+
return f32(input_power*60)/(2*(3.14159)*(drive_speed_fb))
3626+
}
3627+
`, resolver))
3628+
_, err := r.Instantiate(ctx, output.WASM)
3629+
Expect(err).ToNot(HaveOccurred())
3630+
})
3631+
3632+
It("Should compile torque-like expression with f64 cast and f64 return", func() {
3633+
bindMockChannelModule(r, map[string]any{
3634+
"read_i64": func(_ context.Context, channelID uint32) int64 { return 500 },
3635+
"read_f64": func(_ context.Context, channelID uint32) float64 { return 1000.0 },
3636+
})
3637+
resolver := symbol.MapResolver(map[string]symbol.Symbol{
3638+
"input_power": {Name: "input_power", Kind: symbol.KindChannel, Type: types.Chan(types.I64()), ID: 1},
3639+
"drive_speed_fb": {Name: "drive_speed_fb", Kind: symbol.KindChannel, Type: types.Chan(types.F64()), ID: 2},
3640+
})
3641+
output := MustSucceed(compileWithHostImports(`
3642+
func calculation() f64 {
3643+
return f64(input_power*60)/(2*(3.14159)*(drive_speed_fb))
3644+
}
3645+
`, resolver))
3646+
mod := MustSucceed(r.Instantiate(ctx, output.WASM))
3647+
calculation := mod.ExportedFunction("calculation")
3648+
Expect(calculation).ToNot(BeNil())
3649+
results := MustSucceed(calculation.Call(ctx))
3650+
Expect(results).To(HaveLen(1))
3651+
result := math.Float64frombits(results[0])
3652+
Expect(result).To(BeNumerically("~", 4.7746, 0.001))
3653+
})
3654+
3655+
})
3656+
3657+
Describe("Exact user reproduction from data dump", func() {
3658+
for _, inputType := range []struct {
3659+
name string
3660+
t types.Type
3661+
}{
3662+
{"i64", types.I64()},
3663+
{"f32", types.F32()},
3664+
{"f64", types.F64()},
3665+
} {
3666+
It(fmt.Sprintf("Torque with input_power_calc_test=%s drive_speed_fb=f32", inputType.name), func() {
3667+
bindMockChannelModule(r, map[string]any{
3668+
"read_f32": func(_ context.Context, id uint32) float32 { return float32(1000.0) },
3669+
"read_f64": func(_ context.Context, id uint32) float64 { return 500.0 },
3670+
"read_i64": func(_ context.Context, id uint32) int64 { return 500 },
3671+
})
3672+
resolver := symbol.MapResolver(map[string]symbol.Symbol{
3673+
"input_power_calc_test": {
3674+
Name: "input_power_calc_test",
3675+
Kind: symbol.KindChannel,
3676+
Type: types.Chan(inputType.t),
3677+
ID: 1,
3678+
},
3679+
"drive_speed_fb": {
3680+
Name: "drive_speed_fb",
3681+
Kind: symbol.KindChannel,
3682+
Type: types.Chan(types.F32()),
3683+
ID: 2,
3684+
},
3685+
})
3686+
output := MustSucceed(compileWithHostImports(`
3687+
func calculation() f64 {
3688+
return f64(input_power_calc_test*60)/(2*(3.14159)*f64(drive_speed_fb))
3689+
}
3690+
`, resolver))
3691+
_, err := r.Instantiate(ctx, output.WASM)
3692+
Expect(err).ToNot(HaveOccurred())
3693+
})
3694+
}
3695+
})
36123696
})

arc/go/runtime/scheduler/scheduler_test.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"github.com/synnaxlabs/arc/ir/testutil"
2020
"github.com/synnaxlabs/arc/runtime/node"
2121
"github.com/synnaxlabs/arc/runtime/scheduler"
22+
"github.com/synnaxlabs/x/errors"
2223
"github.com/synnaxlabs/x/telem"
2324
)
2425

@@ -1267,7 +1268,7 @@ var _ = Describe("Scheduler", func() {
12671268
Describe("Error Handling", func() {
12681269
It("Should pass errors to error handler", func() {
12691270
nodeA := mock("A")
1270-
testErr := fmt.Errorf("test error")
1271+
testErr := errors.New("test error")
12711272
nodeA.ErrorOnNext(testErr)
12721273

12731274
prog := testutil.NewIRBuilder().
@@ -1290,7 +1291,7 @@ var _ = Describe("Scheduler", func() {
12901291
nodeB := mock("B")
12911292

12921293
nodeA.OnNext = func(ctx node.Context) {
1293-
ctx.ReportError(fmt.Errorf("error from A"))
1294+
ctx.ReportError(errors.New("error from A"))
12941295
ctx.MarkChanged("output")
12951296
}
12961297

@@ -1308,7 +1309,7 @@ var _ = Describe("Scheduler", func() {
13081309

13091310
It("Should return normally after error", func() {
13101311
nodeA := mock("A")
1311-
nodeA.ErrorOnNext(fmt.Errorf("node error"))
1312+
nodeA.ErrorOnNext(errors.New("node error"))
13121313

13131314
prog := testutil.NewIRBuilder().
13141315
Node("A").

arc/go/stl/wasm/wasm_test.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -688,7 +688,7 @@ var _ = Describe("WASM", func() {
688688
Describe("Series Literal Edge Cases", func() {
689689
DescribeTable("empty series",
690690
func(elemType string, outType types.Type) {
691-
expectOutput("empty_series", types.I32(), `{
691+
expectOutput("empty_series", types.I64(), `{
692692
s series `+elemType+` := []
693693
return len(s)
694694
}`, stl.SymbolResolver, int32(0))
@@ -894,19 +894,19 @@ var _ = Describe("WASM", func() {
894894
Describe("Series Length Operations", func() {
895895
DescribeTable("len() function",
896896
expectOutput[int32],
897-
Entry("empty series", "len_empty", types.I32(), `{
897+
Entry("empty series", "len_empty", types.I64(), `{
898898
s series f64 := []
899899
return len(s)
900900
}`, stl.SymbolResolver, int32(0)),
901-
Entry("single element", "len_one", types.I32(), `{
901+
Entry("single element", "len_one", types.I64(), `{
902902
s series f64 := [1.0]
903903
return len(s)
904904
}`, stl.SymbolResolver, int32(1)),
905-
Entry("five elements", "len_five", types.I32(), `{
905+
Entry("five elements", "len_five", types.I64(), `{
906906
s series f64 := [1.0, 2.0, 3.0, 4.0, 5.0]
907907
return len(s)
908908
}`, stl.SymbolResolver, int32(5)),
909-
Entry("after operation", "len_after_op", types.I32(), `{
909+
Entry("after operation", "len_after_op", types.I64(), `{
910910
a series f64 := [1.0, 2.0, 3.0]
911911
b series f64 := [4.0, 5.0, 6.0]
912912
c series f64 := a + b
@@ -918,19 +918,19 @@ var _ = Describe("WASM", func() {
918918
Describe("String Operations Extended", func() {
919919
DescribeTable("string len() function",
920920
expectOutput[int32],
921-
Entry("empty string", "len_empty_str", types.I32(), `{
921+
Entry("empty string", "len_empty_str", types.I64(), `{
922922
return len("")
923923
}`, stl.SymbolResolver, int32(0)),
924-
Entry("simple string", "len_str", types.I32(), `{
924+
Entry("simple string", "len_str", types.I64(), `{
925925
return len("hello")
926926
}`, stl.SymbolResolver, int32(5)),
927-
Entry("concatenated strings", "len_concat", types.I32(), `{
927+
Entry("concatenated strings", "len_concat", types.I64(), `{
928928
return len("ab" + "cd")
929929
}`, stl.SymbolResolver, int32(4)),
930-
Entry("triple concatenation", "len_triple", types.I32(), `{
930+
Entry("triple concatenation", "len_triple", types.I64(), `{
931931
return len("a" + "b" + "c")
932932
}`, stl.SymbolResolver, int32(3)),
933-
Entry("variable concatenation", "len_var_concat", types.I32(), `{
933+
Entry("variable concatenation", "len_var_concat", types.I64(), `{
934934
a str := "hello"
935935
b str := " world"
936936
return len(a + b)

0 commit comments

Comments
 (0)