Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion src/newHelperTypeName.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import ts from "typescript";
import { createHash } from "node:crypto";
import { getCanonicalTypeName } from "./canonicalTypeName";

export const newHelperTypeName = (state: ParserState, type: ts.Type) => {
export const newHashedHelperTypeName = (state: ParserState, type: ts.Type) => {
// to keep helper type names predictable and not dependent on the order of definition,
// we use the first 10 characters of a sha256 hash of the type. If there is an unexpected
// collision, we fallback to using an incrementing counter.
Expand All @@ -23,3 +23,19 @@ export const newHelperTypeName = (state: ParserState, type: ts.Type) => {
}
return `Ts2Py_${shortHash}`;
};

const getIndexedName = (i: number, prefix: string) => `Ts2Py_${prefix}_${i}`;

export const newIndexedHelperTypeName = (
state: ParserState,
type: ts.Type,
prefix: string,
) => {
let i = 0;
while (state.helperTypeNames.has(getIndexedName(i, prefix))) {
i += 1;
}
const name = getIndexedName(i, prefix);
state.helperTypeNames.set(name, type);
return name;
};
24 changes: 19 additions & 5 deletions src/parseInlineType.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import ts, { TypeFlags } from "typescript";
import { ParserState } from "./ParserState";
import { newHelperTypeName } from "./newHelperTypeName";
import {
newHashedHelperTypeName,
newIndexedHelperTypeName,
} from "./newHelperTypeName";
import { parseTypeDefinition } from "./parseTypeDefinition";
import { getCanonicalTypeName } from "./canonicalTypeName";

Expand Down Expand Up @@ -72,9 +75,20 @@ export const tryToParseInlineType = (
// there is no way to represent template literals in Python,
// so we fallback to string
return "str";
} else if (type.flags & TypeFlags.ESSymbol) {
state.imports.add("Any");
return "Any";
} else if (type.flags & TypeFlags.ESSymbolLike) {
const knownType = state.knownTypes.get(type);
if (state.knownTypes.has(type)) {
return knownType;
} else {
// we must create a new type to represent the symbol
state.imports.add("NewType");
const name = newIndexedHelperTypeName(state, type, "symbol");
state.statements.push(
`${name} = NewType(${JSON.stringify(name)}, object)`,
);
state.knownTypes.set(type, name);
return name;
}
} else {
// assume interface or object, we need to create a helper type
if (!globalScope) {
Expand All @@ -85,7 +99,7 @@ export const tryToParseInlineType = (
return semanticallyIdenticalType;
} else {
// we must create a new type
const helperName = newHelperTypeName(state, type);
const helperName = newHashedHelperTypeName(state, type);
parseTypeDefinition(state, helperName, type);
state.knownTypes.set(canonicalName, helperName);
return helperName;
Expand Down
22 changes: 21 additions & 1 deletion src/testing/basic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,10 @@ describe("transpiling basic types", () => {
"from typing_extensions import Union\n\nT = Union[None,float]",
],
["export type T = `a.b.${string}`", "T = str"],
["export type T = symbol", "from typing_extensions import Any\n\nT = Any"],
[
"export type T = symbol",
'from typing_extensions import NewType\n\nTs2Py_symbol_0 = NewType("Ts2Py_symbol_0", object)\n\nT = Ts2Py_symbol_0',
],
])("transpiles %p to %p", async (input, expected) => {
const result = await transpileString(input);
expect(result).toEqual(expected);
Expand Down Expand Up @@ -93,4 +96,21 @@ describe("transpiling basic types", () => {
expect(result).not.toContain("exported");
expect(result).toContain("Exported = float");
});

it("re-uses existing symbol definitions", async () => {
const result = await transpileString(`
const a = Symbol("a")
const b = Symbol("b")
export type T1 = symbol;
export type T2 = symbol;
export type T3 = typeof a;
export type T4 = symbol;
export type T5 = typeof b;
`);
expect(result).toContain(`T1 = Ts2Py_symbol_0`);
expect(result).toContain(`T2 = Ts2Py_symbol_0`);
expect(result).toContain(`T3 = Ts2Py_symbol_1`);
expect(result).toContain(`T4 = Ts2Py_symbol_0`);
expect(result).toContain(`T5 = Ts2Py_symbol_2`);
});
});