@hatchingpoint/point 0.0.5 → 0.0.6
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.
- package/README.md +18 -5
- package/package.json +1 -1
- package/src/core/ast.ts +40 -2
- package/src/core/check.ts +178 -67
- package/src/core/cli.ts +332 -51
- package/src/core/context.ts +213 -36
- package/src/core/emit-javascript.ts +124 -0
- package/src/core/emit-typescript.ts +38 -5
- package/src/core/format.ts +4 -102
- package/src/core/incremental.ts +53 -0
- package/src/core/index.ts +5 -0
- package/src/core/lexer.ts +11 -6
- package/src/core/parser.ts +11 -612
- package/src/core/semantic-source.ts +26 -0
- package/src/core/serialize.ts +18 -0
- package/src/core/test-only/core-text-parser.ts +415 -0
- package/src/core/test-only/format-core.ts +120 -0
- package/src/core/test-only/index.ts +3 -0
- package/src/core/test-only/legacy-lowering.ts +1047 -0
- package/src/semantic/ast.ts +230 -0
- package/src/semantic/callables.ts +51 -0
- package/src/semantic/context.ts +347 -0
- package/src/semantic/desugar.ts +665 -0
- package/src/semantic/expressions.ts +347 -0
- package/src/semantic/format.ts +222 -0
- package/src/semantic/index.ts +10 -0
- package/src/semantic/metadata.ts +37 -0
- package/src/semantic/naming.ts +33 -0
- package/src/semantic/parse.ts +945 -0
- package/src/semantic/serialize.ts +18 -0
package/README.md
CHANGED
|
@@ -2,20 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
Point is an AI-first general-purpose language core for coding-agent-native software engineering.
|
|
4
4
|
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
Requires [Bun](https://bun.sh) on PATH.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g @hatchingpoint/point
|
|
11
|
+
point check examples/math.point
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Pair with the [Point Language](https://marketplace.visualstudio.com/items?itemName=hatchingpoint.point) extension in VS Code or Cursor.
|
|
15
|
+
|
|
16
|
+
Point's public source language is semantic product logic. The compiler lowers that source into an internal typed core and emits TypeScript for existing JavaScript infrastructure.
|
|
17
|
+
|
|
5
18
|
This package is the source of truth for Point. It exposes:
|
|
6
19
|
|
|
7
20
|
- `point` CLI through `src/cli.ts`
|
|
8
21
|
- core language APIs through `@hatchingpoint/point/core`
|
|
9
|
-
-
|
|
22
|
+
- semantic parser/lowering, core checker, formatter, and TypeScript emitter APIs
|
|
10
23
|
|
|
11
24
|
Core workflow:
|
|
12
25
|
|
|
13
26
|
```bash
|
|
14
|
-
bun run
|
|
15
|
-
bun run
|
|
16
|
-
bun run
|
|
27
|
+
bun run fmt-check
|
|
28
|
+
bun run check
|
|
29
|
+
bun run build
|
|
17
30
|
```
|
|
18
31
|
|
|
19
|
-
`
|
|
32
|
+
`bun run build` emits TypeScript into `generated/` for React, Vue, Bun, Node, and Vite projects.
|
|
20
33
|
|
|
21
34
|
When Point is extracted, this package can move into a standalone repo with the same package name and public entrypoints.
|
package/package.json
CHANGED
package/src/core/ast.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { PointSemanticProgram } from "../semantic/ast.ts";
|
|
2
|
+
|
|
1
3
|
export type PointCorePrimitiveType = "Text" | "Int" | "Float" | "Bool" | "Void";
|
|
2
4
|
|
|
3
5
|
export interface PointSourcePosition {
|
|
@@ -16,10 +18,13 @@ export interface PointCoreProgram {
|
|
|
16
18
|
module?: string;
|
|
17
19
|
declarations: PointCoreDeclaration[];
|
|
18
20
|
span?: PointSourceSpan;
|
|
21
|
+
semantic?: PointSemanticProgramMetadata;
|
|
22
|
+
semanticSource?: PointSemanticProgram;
|
|
19
23
|
}
|
|
20
24
|
|
|
21
25
|
export type PointCoreDeclaration =
|
|
22
26
|
| PointCoreImportDeclaration
|
|
27
|
+
| PointCoreExternalDeclaration
|
|
23
28
|
| PointCoreValueDeclaration
|
|
24
29
|
| PointCoreFunctionDeclaration
|
|
25
30
|
| PointCoreTypeDeclaration;
|
|
@@ -47,6 +52,18 @@ export interface PointCoreFunctionDeclaration {
|
|
|
47
52
|
returnType: PointCoreTypeExpression;
|
|
48
53
|
body: PointCoreStatement[];
|
|
49
54
|
span?: PointSourceSpan;
|
|
55
|
+
semantic?: PointSemanticDeclarationMetadata;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface PointCoreExternalDeclaration {
|
|
59
|
+
kind: "external";
|
|
60
|
+
name: string;
|
|
61
|
+
params: PointCoreParameter[];
|
|
62
|
+
returnType: PointCoreTypeExpression;
|
|
63
|
+
from: string;
|
|
64
|
+
importName?: string;
|
|
65
|
+
span?: PointSourceSpan;
|
|
66
|
+
semantic?: PointSemanticDeclarationMetadata;
|
|
50
67
|
}
|
|
51
68
|
|
|
52
69
|
export interface PointCoreTypeDeclaration {
|
|
@@ -54,12 +71,25 @@ export interface PointCoreTypeDeclaration {
|
|
|
54
71
|
name: string;
|
|
55
72
|
fields: PointCoreParameter[];
|
|
56
73
|
span?: PointSourceSpan;
|
|
74
|
+
semantic?: PointSemanticDeclarationMetadata;
|
|
57
75
|
}
|
|
58
76
|
|
|
59
77
|
export interface PointCoreParameter {
|
|
60
78
|
name: string;
|
|
61
79
|
type: PointCoreTypeExpression;
|
|
62
80
|
span?: PointSourceSpan;
|
|
81
|
+
semanticName?: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface PointSemanticProgramMetadata {
|
|
85
|
+
source: "semantic";
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface PointSemanticDeclarationMetadata {
|
|
89
|
+
kind: "record" | "calculation" | "rule" | "label" | "external" | "action" | "policy" | "view" | "route" | "workflow" | "command";
|
|
90
|
+
name: string;
|
|
91
|
+
outputName?: string;
|
|
92
|
+
effects?: string[];
|
|
63
93
|
}
|
|
64
94
|
|
|
65
95
|
export interface PointCoreTypeExpression {
|
|
@@ -81,7 +111,7 @@ export type PointCoreStatement =
|
|
|
81
111
|
| {
|
|
82
112
|
kind: "assignment";
|
|
83
113
|
name: string;
|
|
84
|
-
operator: "=" | "+=";
|
|
114
|
+
operator: "=" | "+=" | "-=";
|
|
85
115
|
value: PointCoreExpression;
|
|
86
116
|
span?: PointSourceSpan;
|
|
87
117
|
}
|
|
@@ -92,14 +122,22 @@ export type PointCoreStatement =
|
|
|
92
122
|
elseBody: PointCoreStatement[];
|
|
93
123
|
span?: PointSourceSpan;
|
|
94
124
|
}
|
|
125
|
+
| {
|
|
126
|
+
kind: "for";
|
|
127
|
+
itemName: string;
|
|
128
|
+
iterable: PointCoreExpression;
|
|
129
|
+
body: PointCoreStatement[];
|
|
130
|
+
span?: PointSourceSpan;
|
|
131
|
+
}
|
|
95
132
|
| { kind: "expression"; value: PointCoreExpression; span?: PointSourceSpan };
|
|
96
133
|
|
|
97
134
|
export type PointCoreExpression =
|
|
98
|
-
| { kind: "literal"; value: string | number | boolean; span?: PointSourceSpan }
|
|
135
|
+
| { kind: "literal"; value: string | number | boolean | null; span?: PointSourceSpan }
|
|
99
136
|
| { kind: "identifier"; name: string; span?: PointSourceSpan }
|
|
100
137
|
| { kind: "list"; items: PointCoreExpression[]; span?: PointSourceSpan }
|
|
101
138
|
| { kind: "record"; fields: PointCoreRecordField[]; span?: PointSourceSpan }
|
|
102
139
|
| { kind: "property"; target: PointCoreExpression; name: string; span?: PointSourceSpan }
|
|
140
|
+
| { kind: "await"; value: PointCoreExpression; span?: PointSourceSpan }
|
|
103
141
|
| {
|
|
104
142
|
kind: "binary";
|
|
105
143
|
operator: PointCoreBinaryOperator;
|
package/src/core/check.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
PointCoreDeclaration,
|
|
3
|
-
PointCoreExpression,
|
|
4
|
-
|
|
3
|
+
PointCoreExpression,
|
|
4
|
+
PointCoreExternalDeclaration,
|
|
5
|
+
PointCoreFunctionDeclaration,
|
|
5
6
|
PointCoreProgram,
|
|
6
7
|
PointCoreStatement,
|
|
7
8
|
PointCoreTypeDeclaration,
|
|
@@ -27,7 +28,7 @@ type DiagnosticMetadata = Partial<Pick<PointCoreDiagnostic, "expected" | "actual
|
|
|
27
28
|
type ScopeEntry = { type: PointCoreTypeExpression; mutable: boolean };
|
|
28
29
|
type Scope = Map<string, ScopeEntry>;
|
|
29
30
|
|
|
30
|
-
const PRIMITIVE_TYPES = new Set(["Text", "Int", "Float", "Bool", "Void", "List"]);
|
|
31
|
+
const PRIMITIVE_TYPES = new Set(["Text", "Int", "Float", "Bool", "Void", "List", "Maybe", "Error", "Or"]);
|
|
31
32
|
|
|
32
33
|
export function checkPointCore(program: PointCoreProgram): PointCoreDiagnostic[] {
|
|
33
34
|
const checker = new CoreChecker(program);
|
|
@@ -39,7 +40,7 @@ class CoreChecker {
|
|
|
39
40
|
private readonly types = new Set(PRIMITIVE_TYPES);
|
|
40
41
|
private readonly typeDeclarations = new Map<string, PointCoreTypeDeclaration>();
|
|
41
42
|
private readonly globals: Scope = new Map();
|
|
42
|
-
private readonly functions = new Map<string, PointCoreFunctionDeclaration>();
|
|
43
|
+
private readonly functions = new Map<string, PointCoreFunctionDeclaration | PointCoreExternalDeclaration>();
|
|
43
44
|
|
|
44
45
|
constructor(private readonly program: PointCoreProgram) {}
|
|
45
46
|
|
|
@@ -59,12 +60,18 @@ class CoreChecker {
|
|
|
59
60
|
this.typeDeclarations.set(declaration.name, declaration);
|
|
60
61
|
}
|
|
61
62
|
if (declaration.kind === "value") this.addGlobal(declaration);
|
|
62
|
-
if (declaration.kind === "function") {
|
|
63
|
+
if (declaration.kind === "function") {
|
|
63
64
|
if (this.functions.has(declaration.name)) {
|
|
64
65
|
this.push("duplicate-function", `Duplicate function ${declaration.name}`, `fn.${declaration.name}`, declaration.span);
|
|
65
66
|
}
|
|
66
|
-
this.functions.set(declaration.name, declaration);
|
|
67
|
-
}
|
|
67
|
+
this.functions.set(declaration.name, declaration);
|
|
68
|
+
}
|
|
69
|
+
if (declaration.kind === "external") {
|
|
70
|
+
if (this.functions.has(declaration.name)) {
|
|
71
|
+
this.push("duplicate-function", `Duplicate function ${declaration.name}`, `external.${declaration.name}`, declaration.span);
|
|
72
|
+
}
|
|
73
|
+
this.functions.set(declaration.name, declaration);
|
|
74
|
+
}
|
|
68
75
|
}
|
|
69
76
|
}
|
|
70
77
|
|
|
@@ -76,7 +83,12 @@ class CoreChecker {
|
|
|
76
83
|
}
|
|
77
84
|
|
|
78
85
|
private checkDeclaration(declaration: PointCoreDeclaration) {
|
|
79
|
-
if (declaration.kind === "import") return;
|
|
86
|
+
if (declaration.kind === "import") return;
|
|
87
|
+
if (declaration.kind === "external") {
|
|
88
|
+
for (const param of declaration.params) this.checkType(param.type, `external.${declaration.name}.${param.name}.type`);
|
|
89
|
+
this.checkType(declaration.returnType, `external.${declaration.name}.return`);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
80
92
|
if (declaration.kind === "type") {
|
|
81
93
|
for (const field of declaration.fields) this.checkType(field.type, `type.${declaration.name}.${field.name}`);
|
|
82
94
|
return;
|
|
@@ -126,16 +138,41 @@ class CoreChecker {
|
|
|
126
138
|
this.checkAssignment(statement, fn, locals);
|
|
127
139
|
return;
|
|
128
140
|
}
|
|
129
|
-
if (statement.kind === "if") {
|
|
130
|
-
this.checkExpressionAssignable(statement.condition, typeRef("Bool"), `fn.${fn.name}.if.condition`, locals);
|
|
131
|
-
const thenLocals = new Map(locals);
|
|
132
|
-
for (const child of statement.thenBody) this.checkStatement(child, fn, thenLocals);
|
|
133
|
-
const elseLocals = new Map(locals);
|
|
134
|
-
for (const child of statement.elseBody) this.checkStatement(child, fn, elseLocals);
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
|
|
141
|
+
if (statement.kind === "if") {
|
|
142
|
+
this.checkExpressionAssignable(statement.condition, typeRef("Bool"), `fn.${fn.name}.if.condition`, locals);
|
|
143
|
+
const thenLocals = new Map(locals);
|
|
144
|
+
for (const child of statement.thenBody) this.checkStatement(child, fn, thenLocals);
|
|
145
|
+
const elseLocals = new Map(locals);
|
|
146
|
+
for (const child of statement.elseBody) this.checkStatement(child, fn, elseLocals);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (statement.kind === "for") {
|
|
150
|
+
this.checkForStatement(statement, fn, locals);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
this.typeOfExpression(statement.value, locals, `fn.${fn.name}.expression`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private checkForStatement(
|
|
157
|
+
statement: Extract<PointCoreStatement, { kind: "for" }>,
|
|
158
|
+
fn: PointCoreFunctionDeclaration,
|
|
159
|
+
locals: Scope,
|
|
160
|
+
) {
|
|
161
|
+
const path = `fn.${fn.name}.for.${statement.itemName}`;
|
|
162
|
+
const iterableType = this.typeOfExpression(statement.iterable, locals, `${path}.iterable`);
|
|
163
|
+
if (!iterableType) return;
|
|
164
|
+
if (iterableType.name !== "List" || iterableType.args.length !== 1) {
|
|
165
|
+
this.push("iteration-type-mismatch", "for requires a List<T> iterable", path, statement.span, {
|
|
166
|
+
expected: "List<T>",
|
|
167
|
+
actual: formatType(iterableType),
|
|
168
|
+
repair: "Iterate over a List<T> value or change this expression to a list.",
|
|
169
|
+
});
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
const loopLocals = new Map(locals);
|
|
173
|
+
loopLocals.set(statement.itemName, { type: iterableType.args[0]!, mutable: false });
|
|
174
|
+
for (const child of statement.body) this.checkStatement(child, fn, loopLocals);
|
|
175
|
+
}
|
|
139
176
|
|
|
140
177
|
private checkAssignment(
|
|
141
178
|
statement: Extract<PointCoreStatement, { kind: "assignment" }>,
|
|
@@ -158,25 +195,48 @@ class CoreChecker {
|
|
|
158
195
|
repair: `Declare ${statement.name} with var if it needs to change.`,
|
|
159
196
|
});
|
|
160
197
|
}
|
|
161
|
-
if (statement.operator === "+=" && !isNumeric(String(target.type.name))) {
|
|
162
|
-
this.push("operator-type-mismatch",
|
|
163
|
-
expected: "Int or Float target",
|
|
164
|
-
actual: formatType(target.type),
|
|
165
|
-
repair:
|
|
166
|
-
});
|
|
167
|
-
}
|
|
198
|
+
if ((statement.operator === "+=" || statement.operator === "-=") && !isNumeric(String(target.type.name))) {
|
|
199
|
+
this.push("operator-type-mismatch", `${statement.operator} requires a numeric target`, path, statement.span, {
|
|
200
|
+
expected: "Int or Float target",
|
|
201
|
+
actual: formatType(target.type),
|
|
202
|
+
repair: `Use ${statement.operator} only with Int or Float values.`,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
168
205
|
this.checkExpressionAssignable(statement.value, target.type, `${path}.value`, locals);
|
|
169
206
|
}
|
|
170
207
|
|
|
171
|
-
private checkExpressionAssignable(
|
|
172
|
-
expression: PointCoreExpression,
|
|
173
|
-
expected: PointCoreTypeExpression,
|
|
174
|
-
path: string,
|
|
175
|
-
scope: Scope,
|
|
176
|
-
) {
|
|
177
|
-
if (
|
|
178
|
-
|
|
179
|
-
|
|
208
|
+
private checkExpressionAssignable(
|
|
209
|
+
expression: PointCoreExpression,
|
|
210
|
+
expected: PointCoreTypeExpression,
|
|
211
|
+
path: string,
|
|
212
|
+
scope: Scope,
|
|
213
|
+
) {
|
|
214
|
+
if (expected.name === "Maybe" && expected.args.length === 1) {
|
|
215
|
+
if (expression.kind === "literal" && expression.value === null) return;
|
|
216
|
+
if (expression.kind !== "record" && expression.kind !== "list") {
|
|
217
|
+
const actual = this.typeOfExpression(expression, scope, path);
|
|
218
|
+
if (actual && sameType(actual, expected)) return;
|
|
219
|
+
}
|
|
220
|
+
this.checkExpressionAssignable(expression, expected.args[0]!, path, scope);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (expected.name === "Or" && expected.args.length > 0) {
|
|
224
|
+
const diagnosticsBefore = this.diagnostics.length;
|
|
225
|
+
const actual = this.typeOfExpression(expression, scope, path);
|
|
226
|
+
if (!actual) return;
|
|
227
|
+
if (sameType(actual, expected)) return;
|
|
228
|
+
if (expected.args.some((candidate) => sameType(candidate, actual))) return;
|
|
229
|
+
this.push("type-mismatch", `Expected ${formatType(expected)}, got ${formatType(actual)}`, path, expression.span, {
|
|
230
|
+
expected: formatType(expected),
|
|
231
|
+
actual: formatType(actual),
|
|
232
|
+
repair: `Return or assign one of: ${expected.args.map(formatType).join(", ")}.`,
|
|
233
|
+
});
|
|
234
|
+
if (this.diagnostics.length > diagnosticsBefore + 1) return;
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
if (expression.kind === "list") {
|
|
238
|
+
this.checkListAssignable(expression, expected, path, scope);
|
|
239
|
+
return;
|
|
180
240
|
}
|
|
181
241
|
if (expression.kind === "record") {
|
|
182
242
|
this.checkRecordAssignable(expression, expected, path, scope);
|
|
@@ -251,15 +311,17 @@ class CoreChecker {
|
|
|
251
311
|
}
|
|
252
312
|
}
|
|
253
313
|
|
|
254
|
-
private typeOfExpression(
|
|
255
|
-
expression: PointCoreExpression,
|
|
256
|
-
scope: Scope,
|
|
257
|
-
path: string,
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
314
|
+
private typeOfExpression(
|
|
315
|
+
expression: PointCoreExpression,
|
|
316
|
+
scope: Scope,
|
|
317
|
+
path: string,
|
|
318
|
+
awaitedCall = false,
|
|
319
|
+
): PointCoreTypeExpression | null {
|
|
320
|
+
if (expression.kind === "literal") {
|
|
321
|
+
if (expression.value === null) return { kind: "typeRef", name: "Void", args: [], span: expression.span };
|
|
322
|
+
const valueType =
|
|
323
|
+
typeof expression.value === "string"
|
|
324
|
+
? "Text"
|
|
263
325
|
: typeof expression.value === "boolean"
|
|
264
326
|
? "Bool"
|
|
265
327
|
: Number.isInteger(expression.value)
|
|
@@ -286,19 +348,45 @@ class CoreChecker {
|
|
|
286
348
|
if (expression.kind === "binary") {
|
|
287
349
|
return this.typeOfBinaryExpression(expression, scope, path);
|
|
288
350
|
}
|
|
289
|
-
if (expression.kind === "property") {
|
|
290
|
-
return this.typeOfPropertyExpression(expression, scope, path);
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
351
|
+
if (expression.kind === "property") {
|
|
352
|
+
return this.typeOfPropertyExpression(expression, scope, path);
|
|
353
|
+
}
|
|
354
|
+
if (expression.kind === "await") {
|
|
355
|
+
return this.typeOfExpression(expression.value, scope, path, true);
|
|
356
|
+
}
|
|
357
|
+
if (expression.callee === "Error") {
|
|
358
|
+
if (expression.args.length !== 1) {
|
|
359
|
+
this.push("arity-mismatch", "Error expects 1 message argument", path, expression.span, {
|
|
360
|
+
expected: "1 arg",
|
|
361
|
+
actual: `${expression.args.length} args`,
|
|
362
|
+
repair: "Construct errors as Error(\"message\").",
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
const message = expression.args[0];
|
|
366
|
+
if (message) this.checkExpressionAssignable(message, typeRef("Text"), `${path}.message`, scope);
|
|
367
|
+
return typeRef("Error", [], expression.span);
|
|
368
|
+
}
|
|
369
|
+
if (expression.callee === "Ok") {
|
|
370
|
+
return expression.args[0] ? this.typeOfExpression(expression.args[0], scope, `${path}.value`) : typeRef("Void", [], expression.span);
|
|
371
|
+
}
|
|
372
|
+
const target = this.functions.get(expression.callee);
|
|
373
|
+
if (!target) {
|
|
374
|
+
this.push("unknown-function", `Unknown function ${expression.callee}`, path, expression.span, {
|
|
375
|
+
actual: expression.callee,
|
|
376
|
+
expected: [...this.functions.keys()],
|
|
377
|
+
repair: `Define fn ${expression.callee}(...) or call an existing function.`,
|
|
378
|
+
});
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
if ((target.semantic?.kind === "action" || target.semantic?.kind === "workflow") && !awaitedCall) {
|
|
382
|
+
this.push("missing-await", `Action ${expression.callee} must be awaited`, path, expression.span, {
|
|
383
|
+
expected: `await ${expression.callee}(...)`,
|
|
384
|
+
actual: `${expression.callee}(...)`,
|
|
385
|
+
repair: "Prefix this action call with await.",
|
|
386
|
+
relatedRefs: [this.refFor(`fn.${target.name}`)],
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
if (target.params.length !== expression.args.length) {
|
|
302
390
|
this.push("arity-mismatch", `Function ${expression.callee} expects ${target.params.length} args`, path, expression.span, {
|
|
303
391
|
expected: `${target.params.length} args`,
|
|
304
392
|
actual: `${expression.args.length} args`,
|
|
@@ -338,9 +426,17 @@ class CoreChecker {
|
|
|
338
426
|
scope: Scope,
|
|
339
427
|
path: string,
|
|
340
428
|
): PointCoreTypeExpression | null {
|
|
341
|
-
const targetType = this.typeOfExpression(expression.target, scope, `${path}.target`);
|
|
342
|
-
if (!targetType) return null;
|
|
343
|
-
|
|
429
|
+
const targetType = this.typeOfExpression(expression.target, scope, `${path}.target`);
|
|
430
|
+
if (!targetType) return null;
|
|
431
|
+
if (targetType.name === "Maybe" && targetType.args.length === 1) {
|
|
432
|
+
this.push("nullable-field-access", `Cannot access field ${expression.name} on nullable ${formatType(targetType)}`, path, expression.span, {
|
|
433
|
+
expected: formatType(targetType.args[0]!),
|
|
434
|
+
actual: formatType(targetType),
|
|
435
|
+
repair: "Check that this Maybe value is present before accessing its fields.",
|
|
436
|
+
});
|
|
437
|
+
return null;
|
|
438
|
+
}
|
|
439
|
+
const declaration = this.typeDeclarations.get(String(targetType.name));
|
|
344
440
|
if (!declaration) {
|
|
345
441
|
this.push("not-a-record", `${formatType(targetType)} has no fields`, path, expression.span, {
|
|
346
442
|
actual: formatType(targetType),
|
|
@@ -414,14 +510,28 @@ class CoreChecker {
|
|
|
414
510
|
repair: `Declare type ${type.name} or use an existing type.`,
|
|
415
511
|
});
|
|
416
512
|
}
|
|
417
|
-
if (type.name === "List" && type.args.length !== 1) {
|
|
513
|
+
if (type.name === "List" && type.args.length !== 1) {
|
|
418
514
|
this.push("invalid-type-arity", "List requires one type argument", path, type.span, {
|
|
419
515
|
expected: "List<T>",
|
|
420
516
|
actual: formatType(type),
|
|
421
517
|
repair: "Use List<Text>, List<Int>, or another concrete item type.",
|
|
422
518
|
});
|
|
423
|
-
}
|
|
424
|
-
if (type.name
|
|
519
|
+
}
|
|
520
|
+
if (type.name === "Maybe" && type.args.length !== 1) {
|
|
521
|
+
this.push("invalid-type-arity", "Maybe requires one type argument", path, type.span, {
|
|
522
|
+
expected: "Maybe<T>",
|
|
523
|
+
actual: formatType(type),
|
|
524
|
+
repair: "Use Maybe<Text>, Maybe<User>, or another concrete optional type.",
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
if (type.name === "Or" && type.args.length < 2) {
|
|
528
|
+
this.push("invalid-type-arity", "Or requires at least two type arguments", path, type.span, {
|
|
529
|
+
expected: "A or B",
|
|
530
|
+
actual: formatType(type),
|
|
531
|
+
repair: "Use syntax such as User or Error.",
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
if (type.name !== "List" && type.name !== "Maybe" && type.name !== "Or" && type.args.length > 0 && !this.typeDeclarations.has(String(type.name))) {
|
|
425
535
|
this.push("invalid-type-arity", `${type.name} does not accept type arguments`, path, type.span, {
|
|
426
536
|
expected: String(type.name),
|
|
427
537
|
actual: formatType(type),
|
|
@@ -468,11 +578,12 @@ function sameType(left: PointCoreTypeExpression, right: PointCoreTypeExpression)
|
|
|
468
578
|
return left.name === right.name && leftArgs.length === rightArgs.length && leftArgs.every((arg, index) => sameType(arg, rightArgs[index]!));
|
|
469
579
|
}
|
|
470
580
|
|
|
471
|
-
function formatType(type: PointCoreTypeExpression): string {
|
|
472
|
-
const args = type.args ?? [];
|
|
473
|
-
if (args.length === 0) return String(type.name);
|
|
474
|
-
|
|
475
|
-
}
|
|
581
|
+
function formatType(type: PointCoreTypeExpression): string {
|
|
582
|
+
const args = type.args ?? [];
|
|
583
|
+
if (args.length === 0) return String(type.name);
|
|
584
|
+
if (type.name === "Or") return args.map(formatType).join(" or ");
|
|
585
|
+
return `${type.name}<${args.map(formatType).join(", ")}>`;
|
|
586
|
+
}
|
|
476
587
|
|
|
477
588
|
function typeRef(name: string, args: PointCoreTypeExpression[] = [], span?: PointSourceSpan): PointCoreTypeExpression {
|
|
478
589
|
return { kind: "typeRef", name, args, span };
|