@vibe-lang/runtime 0.2.8 → 0.2.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/package.json +1 -1
  2. package/src/ast/index.ts +6 -0
  3. package/src/lexer/index.ts +2 -0
  4. package/src/parser/index.ts +14 -0
  5. package/src/parser/parse.ts +138 -1
  6. package/src/parser/test/errors/missing-tokens.test.ts +25 -0
  7. package/src/parser/test/errors/model-declaration.test.ts +14 -8
  8. package/src/parser/test/errors/unclosed-delimiters.test.ts +200 -34
  9. package/src/parser/test/errors/unexpected-tokens.test.ts +15 -1
  10. package/src/parser/test/literals.test.ts +29 -0
  11. package/src/parser/test/model-declaration.test.ts +14 -0
  12. package/src/parser/visitor.ts +9 -0
  13. package/src/runtime/async/dependencies.ts +8 -1
  14. package/src/runtime/async/test/dependencies.test.ts +27 -1
  15. package/src/runtime/exec/statements.ts +51 -1
  16. package/src/runtime/index.ts +2 -3
  17. package/src/runtime/modules.ts +1 -1
  18. package/src/runtime/stdlib/index.ts +7 -11
  19. package/src/runtime/stdlib/tools/index.ts +5 -122
  20. package/src/runtime/stdlib/utils/index.ts +58 -0
  21. package/src/runtime/step.ts +4 -0
  22. package/src/runtime/test/core-functions.test.ts +19 -10
  23. package/src/runtime/test/throw.test.ts +220 -0
  24. package/src/runtime/test/tool-execution.test.ts +30 -30
  25. package/src/runtime/types.ts +4 -1
  26. package/src/runtime/validation.ts +6 -0
  27. package/src/semantic/analyzer-context.ts +2 -0
  28. package/src/semantic/analyzer-visitors.ts +149 -2
  29. package/src/semantic/analyzer.ts +1 -0
  30. package/src/semantic/test/fixtures/exports.vibe +25 -0
  31. package/src/semantic/test/function-return-check.test.ts +215 -0
  32. package/src/semantic/test/imports.test.ts +66 -2
  33. package/src/semantic/test/prompt-validation.test.ts +44 -0
@@ -0,0 +1,215 @@
1
+ /**
2
+ * Tests for semantic analysis of function return type checking.
3
+ * Functions and tools with return types must return or throw on all code paths.
4
+ */
5
+ import { describe, expect, test } from 'bun:test';
6
+ import { parse } from '../../parser/parse';
7
+ import { analyze } from '../index';
8
+
9
+ function getErrors(code: string) {
10
+ const ast = parse(code);
11
+ return analyze(ast);
12
+ }
13
+
14
+ describe('Semantic Analysis - Function Return Type Checking', () => {
15
+ describe('functions with return type', () => {
16
+ test('explicit return passes', () => {
17
+ const errors = getErrors(`
18
+ function add(a: number, b: number): number {
19
+ return a + b
20
+ }
21
+ `);
22
+ expect(errors).toHaveLength(0);
23
+ });
24
+
25
+ test('implicit return (expression at end) passes', () => {
26
+ const errors = getErrors(`
27
+ function add(a: number, b: number): number {
28
+ let sum = a + b
29
+ sum
30
+ }
31
+ `);
32
+ expect(errors).toHaveLength(0);
33
+ });
34
+
35
+ test('throw statement passes', () => {
36
+ const errors = getErrors(`
37
+ function fail(): number {
38
+ throw "always fails"
39
+ }
40
+ `);
41
+ expect(errors).toHaveLength(0);
42
+ });
43
+
44
+ test('if/else both branches return passes', () => {
45
+ const errors = getErrors(`
46
+ function abs(x: number): number {
47
+ if x < 0 {
48
+ return 0 - x
49
+ } else {
50
+ return x
51
+ }
52
+ }
53
+ `);
54
+ expect(errors).toHaveLength(0);
55
+ });
56
+
57
+ test('if/else both branches throw passes', () => {
58
+ const errors = getErrors(`
59
+ function check(x: number): number {
60
+ if x < 0 {
61
+ throw "negative"
62
+ } else {
63
+ throw "non-negative"
64
+ }
65
+ }
66
+ `);
67
+ expect(errors).toHaveLength(0);
68
+ });
69
+
70
+ test('if/else mixed return and throw passes', () => {
71
+ const errors = getErrors(`
72
+ function validate(x: number): number {
73
+ if x < 0 {
74
+ throw "cannot be negative"
75
+ } else {
76
+ return x
77
+ }
78
+ }
79
+ `);
80
+ expect(errors).toHaveLength(0);
81
+ });
82
+
83
+ test('empty body errors', () => {
84
+ const errors = getErrors(`
85
+ function empty(): number {
86
+ }
87
+ `);
88
+ expect(errors).toHaveLength(1);
89
+ expect(errors[0].message).toContain("not all code paths return or throw");
90
+ });
91
+
92
+ test('if without else errors', () => {
93
+ const errors = getErrors(`
94
+ function maybe(x: number): number {
95
+ if x > 0 {
96
+ return x
97
+ }
98
+ }
99
+ `);
100
+ expect(errors).toHaveLength(1);
101
+ expect(errors[0].message).toContain("not all code paths return or throw");
102
+ });
103
+
104
+ test('if with else where only one branch returns errors', () => {
105
+ const errors = getErrors(`
106
+ function partial(x: number): number {
107
+ if x > 0 {
108
+ return x
109
+ } else {
110
+ let y = x
111
+ }
112
+ }
113
+ `);
114
+ expect(errors).toHaveLength(1);
115
+ expect(errors[0].message).toContain("not all code paths return or throw");
116
+ });
117
+
118
+ test('loop does not guarantee return', () => {
119
+ const errors = getErrors(`
120
+ function loop(items: number[]): number {
121
+ for i in items {
122
+ return i
123
+ }
124
+ }
125
+ `);
126
+ expect(errors).toHaveLength(1);
127
+ expect(errors[0].message).toContain("not all code paths return or throw");
128
+ });
129
+ });
130
+
131
+ describe('functions without return type', () => {
132
+ test('no return type - no check required', () => {
133
+ const errors = getErrors(`
134
+ function greet(name: text) {
135
+ print("Hello " + name)
136
+ }
137
+ `);
138
+ expect(errors).toHaveLength(0);
139
+ });
140
+ });
141
+
142
+ describe('tools', () => {
143
+ test('tool with return passes', () => {
144
+ const errors = getErrors(`
145
+ tool greet(name: text): text {
146
+ return "Hello " + name
147
+ }
148
+ `);
149
+ expect(errors).toHaveLength(0);
150
+ });
151
+
152
+ test('tool with implicit return (expression) passes', () => {
153
+ const errors = getErrors(`
154
+ tool greet(name: text): text {
155
+ ts(name) { return "Hello, " + name }
156
+ }
157
+ `);
158
+ expect(errors).toHaveLength(0);
159
+ });
160
+
161
+ test('tool with throw passes', () => {
162
+ const errors = getErrors(`
163
+ tool fail(msg: text): text {
164
+ throw msg
165
+ }
166
+ `);
167
+ expect(errors).toHaveLength(0);
168
+ });
169
+
170
+ test('tool without return errors', () => {
171
+ const errors = getErrors(`
172
+ tool broken(x: number): number {
173
+ let y = x + 1
174
+ }
175
+ `);
176
+ expect(errors).toHaveLength(1);
177
+ expect(errors[0].message).toContain("not all code paths return or throw");
178
+ });
179
+ });
180
+
181
+ describe('nested control flow', () => {
182
+ test('nested if/else both returning passes', () => {
183
+ const errors = getErrors(`
184
+ function nested(x: number, y: number): number {
185
+ if x > 0 {
186
+ if y > 0 {
187
+ return x + y
188
+ } else {
189
+ return x - y
190
+ }
191
+ } else {
192
+ return 0 - x
193
+ }
194
+ }
195
+ `);
196
+ expect(errors).toHaveLength(0);
197
+ });
198
+
199
+ test('nested if/else with missing inner else errors', () => {
200
+ const errors = getErrors(`
201
+ function nested(x: number, y: number): number {
202
+ if x > 0 {
203
+ if y > 0 {
204
+ return x + y
205
+ }
206
+ } else {
207
+ return 0
208
+ }
209
+ }
210
+ `);
211
+ expect(errors).toHaveLength(1);
212
+ expect(errors[0].message).toContain("not all code paths return or throw");
213
+ });
214
+ });
215
+ });
@@ -1,11 +1,15 @@
1
1
  import { describe, expect, test } from 'bun:test';
2
2
  import { parse } from '../../parser/parse';
3
3
  import { SemanticAnalyzer } from '../analyzer';
4
+ import { join } from 'path';
4
5
 
5
- function analyze(source: string) {
6
+ // Test fixtures directory
7
+ const fixturesDir = join(__dirname, 'fixtures');
8
+
9
+ function analyze(source: string, basePath?: string) {
6
10
  const ast = parse(source);
7
11
  const analyzer = new SemanticAnalyzer();
8
- return analyzer.analyze(ast, source);
12
+ return analyzer.analyze(ast, source, basePath);
9
13
  }
10
14
 
11
15
  describe('Semantic Analysis - Import Declarations', () => {
@@ -146,3 +150,63 @@ describe('Semantic Analysis - TsBlock', () => {
146
150
  expect(errors).toHaveLength(3);
147
151
  });
148
152
  });
153
+
154
+ describe('Semantic Analysis - Vibe Import Validation', () => {
155
+ const mainFile = join(fixturesDir, 'main.vibe');
156
+
157
+ test('valid import of exported function from vibe file', () => {
158
+ const errors = analyze(`
159
+ import { greet } from "./exports.vibe"
160
+ let msg = greet("world")
161
+ `, mainFile);
162
+ expect(errors).toHaveLength(0);
163
+ });
164
+
165
+ test('valid import of multiple exports from vibe file', () => {
166
+ const errors = analyze(`
167
+ import { greet, add, VERSION } from "./exports.vibe"
168
+ let msg = greet("world")
169
+ `, mainFile);
170
+ expect(errors).toHaveLength(0);
171
+ });
172
+
173
+ test('valid import of exported model from vibe file', () => {
174
+ const errors = analyze(`
175
+ import { testModel } from "./exports.vibe"
176
+ let x: text = "test"
177
+ `, mainFile);
178
+ expect(errors).toHaveLength(0);
179
+ });
180
+
181
+ test('error: import non-existent function from vibe file', () => {
182
+ const errors = analyze(`
183
+ import { nonExistent } from "./exports.vibe"
184
+ `, mainFile);
185
+ expect(errors).toHaveLength(1);
186
+ expect(errors[0].message).toMatch(/'nonExistent' is not exported from/);
187
+ });
188
+
189
+ test('error: import non-exported private function from vibe file', () => {
190
+ const errors = analyze(`
191
+ import { privateHelper } from "./exports.vibe"
192
+ `, mainFile);
193
+ expect(errors).toHaveLength(1);
194
+ expect(errors[0].message).toMatch(/'privateHelper' is not exported from/);
195
+ });
196
+
197
+ test('error: import non-exported constant from vibe file', () => {
198
+ const errors = analyze(`
199
+ import { INTERNAL_SECRET } from "./exports.vibe"
200
+ `, mainFile);
201
+ expect(errors).toHaveLength(1);
202
+ expect(errors[0].message).toMatch(/'INTERNAL_SECRET' is not exported from/);
203
+ });
204
+
205
+ test('error: import mix of valid and invalid from vibe file', () => {
206
+ const errors = analyze(`
207
+ import { greet, fakeFunction, VERSION } from "./exports.vibe"
208
+ `, mainFile);
209
+ expect(errors).toHaveLength(1);
210
+ expect(errors[0].message).toMatch(/'fakeFunction' is not exported from/);
211
+ });
212
+ });
@@ -340,4 +340,48 @@ describe('Semantic Analyzer - Prompt Parameter Validation', () => {
340
340
  expect(errors).toEqual([]);
341
341
  });
342
342
  });
343
+
344
+ // ============================================================================
345
+ // Expansion syntax in prompt-returning functions
346
+ // ============================================================================
347
+
348
+ describe('expansion syntax in prompt-returning functions', () => {
349
+ test('function returning prompt can use !{} expansion', () => {
350
+ const errors = getErrors(`
351
+ function makePrompt(name: text): prompt {
352
+ return "Hello !{name}, how are you?"
353
+ }
354
+ `);
355
+ expect(errors).toEqual([]);
356
+ });
357
+
358
+ test('function returning prompt can use !{} in template literal', () => {
359
+ const errors = getErrors(`
360
+ function makePrompt(data: text): prompt {
361
+ return \`Process this: !{data}\`
362
+ }
363
+ `);
364
+ expect(errors).toEqual([]);
365
+ });
366
+
367
+ test('function returning text cannot use !{} expansion', () => {
368
+ const errors = getErrors(`
369
+ function makeText(name: text): text {
370
+ return "Hello !{name}"
371
+ }
372
+ `);
373
+ expect(errors.length).toBeGreaterThan(0);
374
+ expect(errors[0]).toContain('Expansion syntax');
375
+ });
376
+
377
+ test('function with no return type cannot use !{} expansion', () => {
378
+ const errors = getErrors(`
379
+ function noType(name: text) {
380
+ return "Hello !{name}"
381
+ }
382
+ `);
383
+ expect(errors.length).toBeGreaterThan(0);
384
+ expect(errors[0]).toContain('Expansion syntax');
385
+ });
386
+ });
343
387
  });