Skip to content

Commit 6cb5823

Browse files
committed
Include argument and actual types in type errors
1 parent 838f1fb commit 6cb5823

4 files changed

Lines changed: 63 additions & 18 deletions

File tree

ROADMAP.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ Goal: make types expressive enough for real workflows while keeping runtime chec
198198
### v0.15.0 Definition of Done
199199

200200
- [ ] Existing scripts without annotations remain compatible.
201-
- [ ] Type errors include parameter name, expected type, and actual type.
201+
- [x] Type errors include parameter name, expected type, and actual type.
202202
- [ ] Capability contract validation can use the same type primitives.
203203

204204
---

vibes/execution.go

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -715,7 +715,7 @@ func (exec *Execution) callFunction(fn *ScriptFunction, receiver Value, args []V
715715
}
716716
if fn.ReturnTy != nil {
717717
if err := checkValueType(val, fn.ReturnTy); err != nil {
718-
return NewNil(), exec.errorAt(pos, "%s", err.Error())
718+
return NewNil(), exec.errorAt(pos, "%s", formatReturnTypeMismatch(fn.Name, err))
719719
}
720720
}
721721
if returned {
@@ -3247,7 +3247,7 @@ func (exec *Execution) bindFunctionArgs(fn *ScriptFunction, env *Env, args []Val
32473247

32483248
if param.Type != nil {
32493249
if err := checkValueType(val, param.Type); err != nil {
3250-
return exec.errorAt(pos, "%s", err.Error())
3250+
return exec.errorAt(pos, "%s", formatArgumentTypeMismatch(param.Name, err))
32513251
}
32523252
}
32533253
env.Define(param.Name, val)
@@ -3280,7 +3280,35 @@ func checkValueType(val Value, ty *TypeExpr) error {
32803280
if matches {
32813281
return nil
32823282
}
3283-
return fmt.Errorf("expected %s", formatTypeExpr(ty))
3283+
return &typeMismatchError{
3284+
Expected: formatTypeExpr(ty),
3285+
Actual: val.Kind().String(),
3286+
}
3287+
}
3288+
3289+
type typeMismatchError struct {
3290+
Expected string
3291+
Actual string
3292+
}
3293+
3294+
func (e *typeMismatchError) Error() string {
3295+
return fmt.Sprintf("expected %s, got %s", e.Expected, e.Actual)
3296+
}
3297+
3298+
func formatArgumentTypeMismatch(name string, err error) string {
3299+
var mismatch *typeMismatchError
3300+
if errors.As(err, &mismatch) {
3301+
return fmt.Sprintf("argument %s expected %s, got %s", name, mismatch.Expected, mismatch.Actual)
3302+
}
3303+
return fmt.Sprintf("argument %s type check failed: %s", name, err.Error())
3304+
}
3305+
3306+
func formatReturnTypeMismatch(fnName string, err error) string {
3307+
var mismatch *typeMismatchError
3308+
if errors.As(err, &mismatch) {
3309+
return fmt.Sprintf("return value for %s expected %s, got %s", fnName, mismatch.Expected, mismatch.Actual)
3310+
}
3311+
return fmt.Sprintf("return type check failed for %s: %s", fnName, err.Error())
32843312
}
32853313

32863314
func valueMatchesType(val Value, ty *TypeExpr) (bool, error) {
@@ -3677,7 +3705,7 @@ func (s *Script) Call(ctx context.Context, name string, args []Value, opts CallO
36773705
}
36783706
if fn.ReturnTy != nil {
36793707
if err := checkValueType(val, fn.ReturnTy); err != nil {
3680-
return NewNil(), err
3708+
return NewNil(), exec.errorAt(fn.Pos, "%s", formatReturnTypeMismatch(fn.Name, err))
36813709
}
36823710
}
36833711
if err := exec.checkMemoryWith(val); err != nil {

vibes/integration_test.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -592,8 +592,16 @@ func TestTypeErrorCases(t *testing.T) {
592592
if err == nil {
593593
t.Fatalf("arg_type_mismatch: expected error")
594594
}
595-
if !strings.Contains(err.Error(), "expected int") {
596-
t.Fatalf("arg_type_mismatch: unexpected error '%v', want 'expected int'", err)
595+
if !strings.Contains(err.Error(), "argument n expected int, got string") {
596+
t.Fatalf("arg_type_mismatch: unexpected error '%v', want argument+expected+actual", err)
597+
}
598+
599+
_, err = script.Call(context.Background(), "return_type_mismatch", nil, CallOptions{})
600+
if err == nil {
601+
t.Fatalf("return_type_mismatch: expected error")
602+
}
603+
if !strings.Contains(err.Error(), "return value for return_type_mismatch expected int, got string") {
604+
t.Fatalf("return_type_mismatch: unexpected error '%v', want expected+actual", err)
597605
}
598606
}
599607

vibes/runtime_test.go

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1270,47 +1270,53 @@ func TestTypedFunctions(t *testing.T) {
12701270
}
12711271

12721272
_, err = script.Call(context.Background(), "pick_second", []Value{NewString("bad"), NewInt(2)}, CallOptions{})
1273-
if err == nil || !strings.Contains(err.Error(), "expected int") {
1274-
t.Fatalf("expected type error, got %v", err)
1273+
if err == nil ||
1274+
!strings.Contains(err.Error(), "argument n expected int, got string") {
1275+
t.Fatalf("expected argument type error, got %v", err)
12751276
}
12761277

12771278
_, err = script.Call(context.Background(), "bad_return", []Value{NewInt(1)}, CallOptions{})
12781279
if err == nil {
12791280
res, _ := script.Call(context.Background(), "bad_return", []Value{NewInt(1)}, CallOptions{})
12801281
t.Fatalf("expected return type error, got value %v (%v)", res, res.Kind())
12811282
}
1282-
if !strings.Contains(err.Error(), "expected int") {
1283+
if !strings.Contains(err.Error(), "return value for bad_return expected int, got string") {
12831284
t.Fatalf("expected return type error, got %v", err)
12841285
}
12851286

12861287
_, err = script.Call(context.Background(), "union_echo", []Value{NewBool(true)}, CallOptions{})
1287-
if err == nil || !strings.Contains(err.Error(), "expected int | string") {
1288+
if err == nil ||
1289+
!strings.Contains(err.Error(), "argument v expected int | string, got bool") {
12881290
t.Fatalf("expected union arg type error, got %v", err)
12891291
}
12901292

12911293
_, err = script.Call(context.Background(), "union_bad_return", nil, CallOptions{})
1292-
if err == nil || !strings.Contains(err.Error(), "expected int | string") {
1294+
if err == nil ||
1295+
!strings.Contains(err.Error(), "return value for union_bad_return expected int | string, got bool") {
12931296
t.Fatalf("expected union return type error, got %v", err)
12941297
}
12951298

12961299
_, err = script.Call(context.Background(), "ints_only", []Value{
12971300
NewArray([]Value{NewInt(1), NewString("oops")}),
12981301
}, CallOptions{})
1299-
if err == nil || !strings.Contains(err.Error(), "expected array<int>") {
1302+
if err == nil ||
1303+
!strings.Contains(err.Error(), "argument values expected array<int>, got array") {
13001304
t.Fatalf("expected typed array arg error, got %v", err)
13011305
}
13021306

13031307
_, err = script.Call(context.Background(), "totals_by_player", []Value{
13041308
NewHash(map[string]Value{"alice": NewString("oops")}),
13051309
}, CallOptions{})
1306-
if err == nil || !strings.Contains(err.Error(), "expected hash<string, int>") {
1310+
if err == nil ||
1311+
!strings.Contains(err.Error(), "argument totals expected hash<string, int>, got hash") {
13071312
t.Fatalf("expected typed hash arg error, got %v", err)
13081313
}
13091314

13101315
_, err = script.Call(context.Background(), "mixed_items", []Value{
13111316
NewArray([]Value{NewBool(true)}),
13121317
}, CallOptions{})
1313-
if err == nil || !strings.Contains(err.Error(), "expected array<int | string>") {
1318+
if err == nil ||
1319+
!strings.Contains(err.Error(), "argument values expected array<int | string>, got array") {
13141320
t.Fatalf("expected typed union array arg error, got %v", err)
13151321
}
13161322

@@ -1321,7 +1327,8 @@ func TestTypedFunctions(t *testing.T) {
13211327
"role": NewString("captain"),
13221328
}),
13231329
}, CallOptions{})
1324-
if err == nil || !strings.Contains(err.Error(), "expected { active: bool?, id: string, score: int }") {
1330+
if err == nil ||
1331+
!strings.Contains(err.Error(), "argument payload expected { active: bool?, id: string, score: int }, got hash") {
13251332
t.Fatalf("expected shape extra-field error, got %v", err)
13261333
}
13271334

@@ -1332,7 +1339,8 @@ func TestTypedFunctions(t *testing.T) {
13321339
"active": NewBool(true),
13331340
}),
13341341
}, CallOptions{})
1335-
if err == nil || !strings.Contains(err.Error(), "expected { active: bool?, id: string, score: int }") {
1342+
if err == nil ||
1343+
!strings.Contains(err.Error(), "argument payload expected { active: bool?, id: string, score: int }, got hash") {
13361344
t.Fatalf("expected shape field-type error, got %v", err)
13371345
}
13381346

@@ -1346,7 +1354,8 @@ func TestTypedFunctions(t *testing.T) {
13461354
}),
13471355
}),
13481356
}, CallOptions{})
1349-
if err == nil || !strings.Contains(err.Error(), "expected array<{ id: string, stats: { wins: int } }>") {
1357+
if err == nil ||
1358+
!strings.Contains(err.Error(), "argument rows expected array<{ id: string, stats: { wins: int } }>, got array") {
13501359
t.Fatalf("expected nested shape error, got %v", err)
13511360
}
13521361
}

0 commit comments

Comments
 (0)