Skip to content

Commit 6ca5023

Browse files
author
Batiste Bieler
committed
Forgot files
1 parent bc58b58 commit 6ca5023

5 files changed

Lines changed: 172 additions & 2 deletions

File tree

docs/TYPESCRIPT_GAP_ANALYSIS.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ Purpose:
3434
| Mapped types (`{ [K in keyof T]: T[K] }`) | Done |
3535
| Utility aliases: `Partial`, `Required`, `Pick`, `Omit` | Done |
3636
| Conditional types | Done with Blop syntax: `T extends U => X else Y` |
37+
| Utility alias: `ReturnType<T>` | Done |
3738

3839
---
3940

@@ -51,7 +52,7 @@ Purpose:
5152
|---|---|
5253
| Function overloads | Better API typing ergonomics |
5354
| Template literal types | Stronger string-level typing |
54-
| Remaining stdlib utility aliases (`ReturnType`, `Parameters`) | Practical TS parity surface |
55+
| Remaining stdlib utility aliases (`Parameters`) | Practical TS parity surface |
5556

5657
### Tier 3 — Advanced
5758

@@ -70,7 +71,7 @@ Purpose:
7071
## Recommended Next Order
7172

7273
1. `infer` keyword
73-
2. Utility aliases based on conditional types (`ReturnType`, `Parameters`)
74+
2. Utility alias based on conditional types (`Parameters`)
7475
3. Function overloads
7576
4. Template literal types
7677

@@ -91,6 +92,7 @@ Purpose:
9192
- Exhaustiveness tests: `src/tests/typeSystem/exhaustivenessChecking.test.js`
9293
- Mapped type tests: `src/tests/typeSystem/mappedTypes.test.js`
9394
- Conditional type tests: `src/tests/typeSystem/conditionalTypes.test.js`
95+
- ReturnType tests: `src/tests/typeSystem/returnType.test.js`
9496
- Predicate tests: `src/tests/typeSystem/typePredicates.test.js`
9597
- Keyof tests: `src/tests/typeSystem/keyofType.test.js`
9698
- Index signature tests: `src/tests/typeSystem/indexSignatures.test.js`

src/inference/typeSystem.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,29 @@ function resolveGenericType(type, aliasMap) {
7373
return resolvedA;
7474
}
7575

76+
// ReturnType<F> — built-in special case: extract function return type
77+
if (baseName === 'ReturnType' && type.typeArgs.length >= 1) {
78+
const resolvedF = resolveAliasType(resolveGenericType(type.typeArgs[0], aliasMap), aliasMap);
79+
80+
if (resolvedF instanceof FunctionType) {
81+
return resolvedF.returnType;
82+
}
83+
84+
if (resolvedF instanceof UnionType) {
85+
const returnTypes = [];
86+
for (const member of resolvedF.types) {
87+
const resolvedMember = resolveAliasType(resolveGenericType(member, aliasMap), aliasMap);
88+
if (!(resolvedMember instanceof FunctionType)) {
89+
return NeverType;
90+
}
91+
returnTypes.push(resolvedMember.returnType);
92+
}
93+
return createUnion(returnTypes);
94+
}
95+
96+
return NeverType;
97+
}
98+
7699
const instantiated = aliasMap.instantiate(baseName, type.typeArgs);
77100
return instantiated !== undefined ? instantiated : type;
78101
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { describe, test } from 'vitest';
2+
import { compileSource } from '../../compile.js';
3+
import { dedent, expectCompiles, expectCompilationError } from '../testHelpers.js';
4+
5+
function compile(src) {
6+
return compileSource(dedent(src), 'test.blop', true);
7+
}
8+
9+
describe('conditional types', () => {
10+
test('generic conditional resolves true branch', () => {
11+
expectCompiles(`
12+
type IsString<T> = T extends string => true else false
13+
x: IsString<string> = true
14+
`);
15+
});
16+
17+
test('generic conditional resolves false branch', () => {
18+
expectCompiles(`
19+
type IsString<T> = T extends string => true else false
20+
x: IsString<number> = false
21+
`);
22+
});
23+
24+
test('mismatched branch assignment errors', () => {
25+
expectCompilationError(
26+
`
27+
type IsString<T> = T extends string => true else false
28+
x: IsString<number> = true
29+
`,
30+
'Cannot assign true to IsString<number>'
31+
);
32+
});
33+
34+
test('conditional can return never in false branch', () => {
35+
expectCompiles(`
36+
type OnlyString<T> = T extends string => T else never
37+
x: OnlyString<string> = 'ok'
38+
`);
39+
});
40+
41+
test('conditional never false branch rejects values', () => {
42+
expectCompilationError(
43+
`
44+
type OnlyString<T> = T extends string => T else never
45+
x: OnlyString<number> = 1
46+
`,
47+
'Cannot assign 1 to OnlyString<number>'
48+
);
49+
});
50+
51+
test('conditional can be used inside mapped utility composition', () => {
52+
const result = compile(`
53+
type IsString<T> = T extends string => true else false
54+
x = 1
55+
`);
56+
if (!result.success) {
57+
throw new Error(`Expected no errors but got: ${JSON.stringify(result.errors)}`);
58+
}
59+
});
60+
});
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { describe, test } from 'vitest';
2+
import { compileSource } from '../../compile.js';
3+
import { dedent, expectCompilationError } from '../testHelpers.js';
4+
5+
function compile(src) {
6+
return compileSource(dedent(src), 'test.blop', true);
7+
}
8+
9+
function expectNoErrors(src) {
10+
const result = compile(src);
11+
if (!result.success) {
12+
throw new Error(`Expected no errors but got: ${JSON.stringify(result.errors)}`);
13+
}
14+
}
15+
16+
describe('exhaustiveness checking for if/elseif chains', () => {
17+
test('flags use after exhaustive early-return chain on literal union', () => {
18+
expectCompilationError(
19+
`
20+
def parseKind(kind: 'a' | 'b'): string {
21+
if kind == 'a' {
22+
return 'A'
23+
} elseif kind == 'b' {
24+
return 'B'
25+
}
26+
return kind
27+
}
28+
`,
29+
"has been narrowed to 'never'"
30+
);
31+
});
32+
33+
test('does not flag when chain is not exhaustive', () => {
34+
expectNoErrors(`
35+
def parseKind(kind: 'a' | 'b' | 'c'): string {
36+
if kind == 'a' {
37+
return 'A'
38+
} elseif kind == 'b' {
39+
return 'B'
40+
}
41+
return 'C'
42+
}
43+
`);
44+
});
45+
});
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { describe, test } from 'vitest';
2+
import { expectCompiles, expectCompilationError, dedent } from '../testHelpers.js';
3+
4+
describe('ReturnType<T>', () => {
5+
test('extracts return type from function type alias', () => {
6+
expectCompiles(dedent(`
7+
type Fn = (name: string) => number
8+
type R = ReturnType<Fn>
9+
x: R = 42
10+
`));
11+
});
12+
13+
test('works with inline function type argument', () => {
14+
expectCompiles(dedent(`
15+
type R = ReturnType<(a: number) => string>
16+
x: R = 'ok'
17+
`));
18+
});
19+
20+
test('supports union of function types', () => {
21+
expectCompiles(dedent(`
22+
type F1 = (x: string) => number
23+
type F2 = (x: number) => string
24+
type F = F1 | F2
25+
type R = ReturnType<F>
26+
a: R = 1
27+
b: R = 'ok'
28+
`));
29+
});
30+
31+
test('non-function argument falls back to never', () => {
32+
expectCompilationError(
33+
dedent(`
34+
type R = ReturnType<string>
35+
x: R = 1
36+
`),
37+
'Cannot assign 1 to R'
38+
);
39+
});
40+
});

0 commit comments

Comments
 (0)