Skip to content

Commit 51095be

Browse files
committed
split completion types in typechecker
1 parent bda4d4c commit 51095be

File tree

2 files changed

+52
-21
lines changed

2 files changed

+52
-21
lines changed

src/type-logic.ts

+49-18
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ type Type =
88
| { kind: 'union'; of: NonUnion[] } // constraint: nothing in the union dominates anything else in the union
99
| { kind: 'list'; of: Type }
1010
| { kind: 'record' } // TODO more precision
11-
| { kind: 'completion'; of: Type }
11+
| { kind: 'normal completion'; of: Type }
12+
| { kind: 'abrupt completion' }
1213
| { kind: 'real' }
1314
| { kind: 'integer' }
1415
| { kind: 'non-negative integer' }
@@ -50,7 +51,7 @@ const simpleKinds = new Set<Type['kind']>([
5051
const dominateGraph: Partial<Record<Type['kind'], Type['kind'][]>> = {
5152
// @ts-expect-error TS does not know about __proto__
5253
__proto__: null,
53-
record: ['completion'],
54+
record: ['normal completion', 'abrupt completion'],
5455
real: [
5556
'integer',
5657
'non-negative integer',
@@ -97,7 +98,7 @@ export function dominates(a: Type, b: Type): boolean {
9798
}
9899
if (
99100
(a.kind === 'list' && b.kind === 'list') ||
100-
(a.kind === 'completion' && b.kind === 'completion')
101+
(a.kind === 'normal completion' && b.kind === 'normal completion')
101102
) {
102103
return dominates(a.of, b.of);
103104
}
@@ -170,7 +171,7 @@ export function join(a: Type, b: Type): Type {
170171
}
171172
if (
172173
(a.kind === 'list' && b.kind === 'list') ||
173-
(a.kind === 'completion' && b.kind === 'completion')
174+
(a.kind === 'normal completion' && b.kind === 'normal completion')
174175
) {
175176
return { kind: a.kind, of: join(a.of, b.of) };
176177
}
@@ -193,7 +194,7 @@ export function meet(a: Type, b: Type): Type {
193194
}
194195
if (
195196
(a.kind === 'list' && b.kind === 'list') ||
196-
(a.kind === 'completion' && b.kind === 'completion')
197+
(a.kind === 'normal completion' && b.kind === 'normal completion')
197198
) {
198199
return { kind: a.kind, of: meet(a.of, b.of) };
199200
}
@@ -224,13 +225,16 @@ export function serialize(type: Type): string {
224225
case 'record': {
225226
return 'Record';
226227
}
227-
case 'completion': {
228+
case 'normal completion': {
228229
if (type.of.kind === 'never') {
229-
return 'an abrupt Completion Record';
230+
return 'never';
230231
} else if (type.of.kind === 'unknown') {
231-
return 'a Completion Record';
232+
return 'a normal completion';
232233
}
233-
return 'a Completion Record normally holding ' + serialize(type.of);
234+
return 'a normal completion containing ' + serialize(type.of);
235+
}
236+
case 'abrupt completion': {
237+
return 'an abrupt completion';
234238
}
235239
case 'real': {
236240
return 'mathematical value';
@@ -338,8 +342,17 @@ export function typeFromExpr(expr: Expr, biblio: Biblio): Type {
338342
const remaining = stripWhitespace(items.slice(1));
339343
if (remaining.length === 1 && ['call', 'sdo-call'].includes(remaining[0].name)) {
340344
const callType = typeFromExpr(remaining[0], biblio);
341-
if (callType.kind === 'completion') {
342-
return callType.of;
345+
if (isCompletion(callType)) {
346+
const normal: Type =
347+
callType.kind === 'normal completion'
348+
? callType.of
349+
: callType.kind === 'abrupt completion'
350+
? // the expression `? _abrupt_` strictly speaking has type `never`
351+
// however we mostly use `never` to mean user error, so use unknown instead
352+
// this should only ever happen after `Return`
353+
{ kind: 'unknown' }
354+
: callType.of.find(k => k.kind === 'normal completion')?.of ?? { kind: 'unknown' };
355+
return normal;
343356
}
344357
}
345358
}
@@ -418,15 +431,23 @@ export function typeFromExprType(type: BiblioType): Type {
418431
}
419432
case 'completion': {
420433
if (type.completionType === 'abrupt') {
421-
return { kind: 'completion', of: { kind: 'never' } };
422-
}
423-
return {
424-
kind: 'completion',
425-
of:
434+
return { kind: 'abrupt completion' };
435+
} else {
436+
const normalType: Type =
426437
type.typeOfValueIfNormal == null
427438
? { kind: 'unknown' }
428-
: typeFromExprType(type.typeOfValueIfNormal),
429-
};
439+
: typeFromExprType(type.typeOfValueIfNormal);
440+
if (type.completionType === 'normal') {
441+
return { kind: 'normal completion', of: normalType };
442+
} else if (type.completionType === 'mixed') {
443+
return {
444+
kind: 'union',
445+
of: [{ kind: 'normal completion', of: normalType }, { kind: 'abrupt completion' }],
446+
};
447+
} else {
448+
throw new Error('unreachable: completion kind ' + type.completionType);
449+
}
450+
}
430451
}
431452
case 'opaque': {
432453
const text = type.type;
@@ -514,6 +535,16 @@ export function typeFromExprType(type: BiblioType): Type {
514535
return { kind: 'unknown' };
515536
}
516537

538+
export function isCompletion(
539+
type: Type,
540+
): type is Type & { kind: 'normal completion' | 'abrupt completion' | 'union' } {
541+
return (
542+
type.kind === 'normal completion' ||
543+
type.kind === 'abrupt completion' ||
544+
(type.kind === 'union' && type.of.some(isCompletion))
545+
);
546+
}
547+
517548
export function stripWhitespace(items: NonSeq[]) {
518549
items = [...items];
519550
while (items[0]?.name === 'text' && /^\s+$/.test(items[0].contents)) {

test/typecheck.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -1511,7 +1511,7 @@ describe('type system', () => {
15111511
await assertTypeError(
15121512
'an ECMAScript language value',
15131513
'NormalCompletion(42)',
1514-
'argument type (a Completion Record) does not look plausibly assignable to parameter type (ECMAScript language value)',
1514+
'argument type (a normal completion) does not look plausibly assignable to parameter type (ECMAScript language value)',
15151515
[completionBiblio],
15161516
);
15171517

@@ -1528,14 +1528,14 @@ describe('type system', () => {
15281528
await assertTypeError(
15291529
'either a normal completion containing a Boolean or an abrupt completion',
15301530
'*false*',
1531-
'argument (false) does not look plausibly assignable to parameter type (a Completion Record normally holding Boolean)',
1531+
'argument (false) does not look plausibly assignable to parameter type (a normal completion containing Boolean or an abrupt completion)',
15321532
[completionBiblio],
15331533
);
15341534

15351535
await assertTypeError(
15361536
'a Boolean',
15371537
'NormalCompletion(*false*)',
1538-
'argument type (a Completion Record) does not look plausibly assignable to parameter type (Boolean)',
1538+
'argument type (a normal completion) does not look plausibly assignable to parameter type (Boolean)',
15391539
[completionBiblio],
15401540
);
15411541

0 commit comments

Comments
 (0)