Skip to content

Commit bb2fa60

Browse files
authored
parse symbols as NewTypes (#38)
1 parent 6e657e3 commit bb2fa60

3 files changed

Lines changed: 57 additions & 7 deletions

File tree

src/newHelperTypeName.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import ts from "typescript";
33
import { createHash } from "node:crypto";
44
import { getCanonicalTypeName } from "./canonicalTypeName";
55

6-
export const newHelperTypeName = (state: ParserState, type: ts.Type) => {
6+
export const newHashedHelperTypeName = (state: ParserState, type: ts.Type) => {
77
// to keep helper type names predictable and not dependent on the order of definition,
88
// we use the first 10 characters of a sha256 hash of the type. If there is an unexpected
99
// collision, we fallback to using an incrementing counter.
@@ -23,3 +23,19 @@ export const newHelperTypeName = (state: ParserState, type: ts.Type) => {
2323
}
2424
return `Ts2Py_${shortHash}`;
2525
};
26+
27+
const getIndexedName = (i: number, prefix: string) => `Ts2Py_${prefix}_${i}`;
28+
29+
export const newIndexedHelperTypeName = (
30+
state: ParserState,
31+
type: ts.Type,
32+
prefix: string,
33+
) => {
34+
let i = 0;
35+
while (state.helperTypeNames.has(getIndexedName(i, prefix))) {
36+
i += 1;
37+
}
38+
const name = getIndexedName(i, prefix);
39+
state.helperTypeNames.set(name, type);
40+
return name;
41+
};

src/parseInlineType.ts

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import ts, { TypeFlags } from "typescript";
22
import { ParserState } from "./ParserState";
3-
import { newHelperTypeName } from "./newHelperTypeName";
3+
import {
4+
newHashedHelperTypeName,
5+
newIndexedHelperTypeName,
6+
} from "./newHelperTypeName";
47
import { parseTypeDefinition } from "./parseTypeDefinition";
58
import { getCanonicalTypeName } from "./canonicalTypeName";
69

@@ -72,9 +75,20 @@ export const tryToParseInlineType = (
7275
// there is no way to represent template literals in Python,
7376
// so we fallback to string
7477
return "str";
75-
} else if (type.flags & TypeFlags.ESSymbol) {
76-
state.imports.add("Any");
77-
return "Any";
78+
} else if (type.flags & TypeFlags.ESSymbolLike) {
79+
const knownType = state.knownTypes.get(type);
80+
if (state.knownTypes.has(type)) {
81+
return knownType;
82+
} else {
83+
// we must create a new type to represent the symbol
84+
state.imports.add("NewType");
85+
const name = newIndexedHelperTypeName(state, type, "symbol");
86+
state.statements.push(
87+
`${name} = NewType(${JSON.stringify(name)}, object)`,
88+
);
89+
state.knownTypes.set(type, name);
90+
return name;
91+
}
7892
} else {
7993
// assume interface or object, we need to create a helper type
8094
if (!globalScope) {
@@ -85,7 +99,7 @@ export const tryToParseInlineType = (
8599
return semanticallyIdenticalType;
86100
} else {
87101
// we must create a new type
88-
const helperName = newHelperTypeName(state, type);
102+
const helperName = newHashedHelperTypeName(state, type);
89103
parseTypeDefinition(state, helperName, type);
90104
state.knownTypes.set(canonicalName, helperName);
91105
return helperName;

src/testing/basic.test.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,10 @@ describe("transpiling basic types", () => {
6262
"from typing_extensions import Union\n\nT = Union[None,float]",
6363
],
6464
["export type T = `a.b.${string}`", "T = str"],
65-
["export type T = symbol", "from typing_extensions import Any\n\nT = Any"],
65+
[
66+
"export type T = symbol",
67+
'from typing_extensions import NewType\n\nTs2Py_symbol_0 = NewType("Ts2Py_symbol_0", object)\n\nT = Ts2Py_symbol_0',
68+
],
6669
])("transpiles %p to %p", async (input, expected) => {
6770
const result = await transpileString(input);
6871
expect(result).toEqual(expected);
@@ -93,4 +96,21 @@ describe("transpiling basic types", () => {
9396
expect(result).not.toContain("exported");
9497
expect(result).toContain("Exported = float");
9598
});
99+
100+
it("re-uses existing symbol definitions", async () => {
101+
const result = await transpileString(`
102+
const a = Symbol("a")
103+
const b = Symbol("b")
104+
export type T1 = symbol;
105+
export type T2 = symbol;
106+
export type T3 = typeof a;
107+
export type T4 = symbol;
108+
export type T5 = typeof b;
109+
`);
110+
expect(result).toContain(`T1 = Ts2Py_symbol_0`);
111+
expect(result).toContain(`T2 = Ts2Py_symbol_0`);
112+
expect(result).toContain(`T3 = Ts2Py_symbol_1`);
113+
expect(result).toContain(`T4 = Ts2Py_symbol_0`);
114+
expect(result).toContain(`T5 = Ts2Py_symbol_2`);
115+
});
96116
});

0 commit comments

Comments
 (0)