-
Notifications
You must be signed in to change notification settings - Fork 164
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: union with parsedModules #2311
Open
jeshecdom
wants to merge
14
commits into
main
Choose a base branch
from
jeshecdom/parsed-modules-union
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+2,271
−48
Open
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
85f71dd
fix: union with parsedModules.
jeshecdom bcef0e3
test: Added test that shows AST with stdlib references now passes pre…
jeshecdom b346936
Merge branch 'main' into jeshecdom/parsed-modules-union
jeshecdom 750191a
tests: more tests, including a negative one.
jeshecdom 47bf927
Merge branch 'main' into jeshecdom/parsed-modules-union
jeshecdom 65c9171
refactor: make parsedModules a required argument in `opencontext` fun…
jeshecdom 2feeb97
tests: Added generation of makeX and makeDummyX functions.
jeshecdom 60d0dcb
refactor: change test files to use autogenerated make factory.
jeshecdom afa988f
fix: conflict in imports version.
jeshecdom d700a5d
Merge branch 'main' into jeshecdom/parsed-modules-union
jeshecdom e0bbee8
refactor: tSQualifiedName --> tsQualifiedName
jeshecdom e2d2b0f
refactor: changed comparisons to types for isX guard functions. Set d…
jeshecdom 128f19d
refactor: changed named of yarn command to gen:make-funs
jeshecdom cc4f954
Merge branch 'main' into jeshecdom/parsed-modules-union
jeshecdom File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,308 @@ | ||
import generate from "@babel/generator"; | ||
import type { ParserOptions } from "@babel/parser"; | ||
import { parse } from "@babel/parser"; | ||
import * as t from "@babel/types"; | ||
import * as fs from "fs"; | ||
import * as path from "path"; | ||
|
||
function main() { | ||
const options: ParserOptions = { | ||
sourceType: "module", | ||
plugins: ["typescript"], | ||
}; | ||
|
||
const astModule = fs | ||
.readFileSync(path.join(__dirname, "..", "ast.ts")) | ||
.toString(); | ||
|
||
const astTypeDecls = parse(astModule, options); | ||
const decls = astTypeDecls.program.body | ||
.filter((stmt) => t.isExportNamedDeclaration(stmt)) | ||
.map((stmt) => stmt.declaration) | ||
.filter((stmt) => t.isTSTypeAliasDeclaration(stmt)); | ||
|
||
const declNames = new Set(decls.map((decl) => decl.id.name)); | ||
|
||
const finalFunctions: { name: string; code: string }[] = []; | ||
|
||
// Extract from the declarations all the union types | ||
const unionTypes = decls | ||
.map((decl) => decl.typeAnnotation) | ||
.filter((decl) => t.isTSUnionType(decl)); | ||
|
||
for (const unionType of unionTypes) { | ||
const subtypes = unionType.types | ||
.filter((typeDecl) => t.isTSTypeReference(typeDecl)) | ||
.map((typeDecl) => typeDecl.typeName) | ||
.filter((typeDecl) => t.isIdentifier(typeDecl)); | ||
|
||
for (const subtype of subtypes) { | ||
const subtypeDecl = decls.find( | ||
(decl) => decl.id.name === subtype.name, | ||
); | ||
if (typeof subtypeDecl === "undefined") { | ||
throw new Error(`${subtype.name} is not declared.`); | ||
} | ||
|
||
const subtypeDeclAnnotation = subtypeDecl.typeAnnotation; | ||
if (t.isTSTypeLiteral(subtypeDeclAnnotation)) { | ||
const genFunctions = createMakeAndDummyFunctions( | ||
subtypeDeclAnnotation, | ||
subtypeDecl.id, | ||
declNames, | ||
); | ||
const genFunctionsFiltered = genFunctions.filter((genF) => | ||
finalFunctions.every((f) => f.name !== genF.name), | ||
); | ||
|
||
if (genFunctionsFiltered.length > 0) { | ||
console.log( | ||
`Generated [${genFunctionsFiltered.map((entry) => entry.name).join(", ")}] for ${subtype.name}`, | ||
); | ||
} | ||
|
||
finalFunctions.push(...genFunctionsFiltered); | ||
} else if (t.isTSUnionType(subtypeDeclAnnotation)) { | ||
// Do nothing, since it will be processed later. | ||
} else { | ||
// Unexpected type | ||
throw new Error( | ||
`${subtype.name} is not a reference to a type literal or a union type.`, | ||
); | ||
} | ||
} | ||
} | ||
|
||
// Create the make factory file | ||
const makeFactoryTemplate = fs | ||
.readFileSync(path.join(__dirname, "make-factory.template")) | ||
.toString(); | ||
|
||
const functionCodes = finalFunctions | ||
.map((genFun) => genFun.code) | ||
.join("\n\n"); | ||
const functionNames = finalFunctions | ||
.map((genFun) => genFun.name) | ||
.join(",\n"); | ||
|
||
const makeFactoryCode = makeFactoryTemplate | ||
.replace("<FUNCTIONS>", functionCodes) | ||
.replace("<FUNCTION_NAMES>", functionNames); | ||
|
||
fs.writeFileSync(path.join(__dirname, "make-factory.ts"), makeFactoryCode); | ||
|
||
console.log("Finished."); | ||
} | ||
|
||
function createMakeAndDummyFunctions( | ||
decl: t.TSTypeLiteral, | ||
id: t.Identifier, | ||
decls: Set<string>, | ||
): { name: string; code: string }[] { | ||
const astNamespace = "Ast"; | ||
const astFactoryObject = "astF"; | ||
const createNodeFunName = "createNode"; | ||
const emptySrcInfo = "emptySrcInfo"; | ||
|
||
const rawFieldsArray = decl.members.filter((decl) => | ||
t.isTSPropertySignature(decl), | ||
); | ||
const generalParams: { id: t.Identifier; type: t.TSType }[] = []; | ||
const paramsWithLiteralTypes: { | ||
id: t.Identifier; | ||
type: t.TSLiteralType; | ||
}[] = []; | ||
|
||
// If there is no loc field, | ||
// the makeDummy function cannot be created | ||
const makeDummy = rawFieldsArray.some( | ||
(f) => t.isIdentifier(f.key) && f.key.name === "loc", | ||
); | ||
|
||
for (const field of rawFieldsArray) { | ||
if (!t.isIdentifier(field.key)) { | ||
throw new Error( | ||
`Expected identifier in fields, but found ${field.key.type}`, | ||
); | ||
} | ||
const fieldName = field.key.name; | ||
if (fieldName === "id") { | ||
// The id field should not occur as an argument to the function, | ||
// nor as a parameter to createNode | ||
continue; | ||
} | ||
if (field.typeAnnotation) { | ||
const typeAnnotation = field.typeAnnotation.typeAnnotation; | ||
if (t.isTSLiteralType(typeAnnotation)) { | ||
paramsWithLiteralTypes.push({ | ||
id: field.key, | ||
type: typeAnnotation, | ||
}); | ||
} else { | ||
generalParams.push({ id: field.key, type: typeAnnotation }); | ||
} | ||
} else { | ||
throw new Error( | ||
`Expected field ${fieldName} to have a type annotation`, | ||
); | ||
} | ||
} | ||
|
||
const makeFunName = `make${id.name}`; | ||
const makeDummyFunName = `makeDummy${id.name}`; | ||
// The params to the make functions do not have fields with literal types | ||
// Also, the dummy function needs to filter the loc parameter | ||
const createParam = (entry: { id: t.Identifier; type: t.TSType }) => { | ||
const newId = t.identifier(`p_${entry.id.name}`); | ||
newId.typeAnnotation = t.tsTypeAnnotation( | ||
qualifyType(astNamespace, entry.type, decls), | ||
); | ||
return newId; | ||
}; | ||
const makeFunParamsArray = generalParams.map((entry) => createParam(entry)); | ||
const makeDummyFunParamsArray = generalParams | ||
.filter(({ id, type: _ }) => id.name !== "loc") | ||
.map((entry) => createParam(entry)); | ||
|
||
// The arguments with literal values to the createNode call inside the make functions body | ||
const createNodeLiteralArgs = paramsWithLiteralTypes.map(({ id, type }) => | ||
t.objectProperty(id, type.literal), | ||
); | ||
// The non-literal arguments to the createNode call inside the make functions body | ||
const createNodeArgsForMake = generalParams.map(({ id, type: _ }) => | ||
t.objectProperty(id, t.identifier(`p_${id.name}`)), | ||
); | ||
const createNodeArgsForMakeDummy = generalParams.map(({ id, type: _ }) => | ||
id.name === "loc" | ||
? t.objectProperty(id, t.identifier(emptySrcInfo)) | ||
: t.objectProperty(id, t.identifier(`p_${id.name}`)), | ||
); | ||
const funReturnType = t.tsTypeReference( | ||
t.tsQualifiedName(t.identifier(astNamespace), id), | ||
); | ||
// Function to create the function codes | ||
const createFun = ( | ||
name: string, | ||
params: t.Identifier[], | ||
createNodeArgs: t.ObjectProperty[], | ||
) => { | ||
const body = t.returnStatement( | ||
t.tsAsExpression( | ||
t.callExpression( | ||
t.memberExpression( | ||
t.identifier(astFactoryObject), | ||
t.identifier(createNodeFunName), | ||
), | ||
[t.objectExpression(createNodeArgs)], | ||
), | ||
funReturnType, | ||
), | ||
); | ||
const funDecl = t.functionDeclaration( | ||
t.identifier(name), | ||
params, | ||
t.blockStatement([body]), | ||
); | ||
funDecl.returnType = t.tsTypeAnnotation(funReturnType); | ||
return funDecl; | ||
}; | ||
|
||
const makeFun = createFun(makeFunName, makeFunParamsArray, [ | ||
...createNodeLiteralArgs, | ||
...createNodeArgsForMake, | ||
]); | ||
const makeDummyFun = createFun(makeDummyFunName, makeDummyFunParamsArray, [ | ||
...createNodeLiteralArgs, | ||
...createNodeArgsForMakeDummy, | ||
]); | ||
|
||
if (makeDummy) { | ||
return [ | ||
{ name: makeFunName, code: generate(makeFun).code }, | ||
{ name: makeDummyFunName, code: generate(makeDummyFun).code }, | ||
]; | ||
} else { | ||
console.log( | ||
`[WARNING] Skipped makeDummy for ${id.name}, because there is no loc field in ${id.name}.`, | ||
); | ||
return [{ name: makeFunName, code: generate(makeFun).code }]; | ||
} | ||
} | ||
|
||
function qualifyType( | ||
namespace: string, | ||
typ: t.TSType, | ||
decls: Set<string>, | ||
): t.TSType { | ||
switch (typ.type) { | ||
case "TSTypeReference": { | ||
if (t.isIdentifier(typ.typeName)) { | ||
if (decls.has(typ.typeName.name)) { | ||
return t.tsTypeReference( | ||
t.tsQualifiedName( | ||
t.identifier(namespace), | ||
typ.typeName, | ||
), | ||
); | ||
} else { | ||
// Leave the identifier unchanged, but check if it has type parameters | ||
const typRef = t.tsTypeReference(typ.typeName); | ||
if (typ.typeParameters) { | ||
typRef.typeParameters = t.tsTypeParameterInstantiation( | ||
typ.typeParameters.params.map((t) => | ||
qualifyType(namespace, t, decls), | ||
), | ||
); | ||
} | ||
return typRef; | ||
} | ||
} | ||
// Leave the type as is | ||
return typ; | ||
} | ||
case "TSUnionType": { | ||
return t.tsUnionType( | ||
typ.types.map((t) => qualifyType(namespace, t, decls)), | ||
); | ||
} | ||
case "TSArrayType": { | ||
return t.tsArrayType( | ||
qualifyType(namespace, typ.elementType, decls), | ||
); | ||
} | ||
case "TSTypeOperator": { | ||
const op = t.tsTypeOperator( | ||
qualifyType(namespace, typ.typeAnnotation, decls), | ||
); | ||
op.operator = typ.operator; | ||
return op; | ||
} | ||
case "TSTupleType": { | ||
if ( | ||
// Cannot use guard function isTSNamedTupleMember, because compiler cannot deduce the type inside | ||
// the condition if the guard function is used. | ||
typ.elementTypes.every((ty) => ty.type !== "TSNamedTupleMember") | ||
) { | ||
return t.tsTupleType( | ||
typ.elementTypes.map((t) => | ||
qualifyType(namespace, t, decls), | ||
), | ||
); | ||
} else { | ||
// Currently unsupported | ||
throw new Error( | ||
"TSNamedTupleMember is currently not supported in TSTupleType", | ||
); | ||
} | ||
} | ||
case "TSUndefinedKeyword": | ||
case "TSStringKeyword": | ||
case "TSBooleanKeyword": | ||
case "TSBigIntKeyword": | ||
return typ; | ||
default: | ||
throw new Error(`${typ.type} is not supported`); | ||
} | ||
} | ||
|
||
main(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
// THIS IS AN AUTOGENERATED FILE. TO GENERATE IT AGAIN, EXECUTE yarn gen:make-funs | ||
|
||
import type { FactoryAst } from "../ast-helpers"; | ||
import type * as Ast from "../ast"; | ||
import type { SrcInfo } from "../../grammar"; | ||
import type * as TonCore from "@ton/core"; | ||
import { getSrcInfo } from "../../grammar/src-info"; | ||
|
||
export const getMakeAst = (astF: FactoryAst) => { | ||
|
||
const emptySrcInfo = getSrcInfo(" ", 0, 0, null, "user"); | ||
|
||
<FUNCTIONS> | ||
|
||
return { | ||
<FUNCTION_NAMES> | ||
}; | ||
}; | ||
|
||
export type MakeAstFactory = ReturnType<typeof getMakeAst>; |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,11 +4,12 @@ import { | |
} from "../../types/resolveDescriptors"; | ||
import { WriterContext } from "../Writer"; | ||
import { writeExpression } from "./writeExpression"; | ||
import { openContext } from "../../context/store"; | ||
import { openContext, parseModules } from "../../context/store"; | ||
import { resolveStatements } from "../../types/resolveStatements"; | ||
import { CompilerContext } from "../../context/context"; | ||
import { getParser } from "../../grammar"; | ||
import { getAstFactory } from "../../ast/ast-helpers"; | ||
import type { Source } from "../../imports/source"; | ||
|
||
const code = ` | ||
|
@@ -71,11 +72,14 @@ const golden: string[] = [ | |
describe("writeExpression", () => { | ||
it("should write expression", () => { | ||
const ast = getAstFactory(); | ||
const sources: Source[] = [ | ||
{ code: code, path: "<unknown>", origin: "user" }, | ||
]; | ||
let ctx = openContext( | ||
new CompilerContext(), | ||
[{ code: code, path: "<unknown>", origin: "user" }], | ||
sources, | ||
[], | ||
getParser(ast), | ||
parseModules(sources, getParser(ast)), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pleasure to my eyes ❤️ |
||
); | ||
ctx = resolveDescriptors(ctx, ast); | ||
ctx = resolveStatements(ctx); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
// Jest Snapshot v1, https://goo.gl/fbAQLP | ||
|
||
exports[`pre-compilation of ASTs should fail pre-compilation when source files and a manual AST have declaration clashes 1`] = ` | ||
"Type "Test" already exists | ||
> 1 | | ||
^ | ||
" | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
contract Test { | ||
f: StateInit; | ||
|
||
init() { | ||
self.f = initOf Test(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
import path from "path"; | ||
import type * as A from "../../ast/ast"; | ||
import { getAstFactory } from "../../ast/ast-helpers"; | ||
import { CompilerContext } from "../../context/context"; | ||
import { precompile } from "../../pipeline/precompile"; | ||
import files from "../../stdlib/stdlib"; | ||
import { createVirtualFileSystem } from "../../vfs/createVirtualFileSystem"; | ||
import fs from "fs"; | ||
import { getParser } from "../../grammar"; | ||
import { getMakeAst } from "../../ast/generated/make-factory"; | ||
import type { MakeAstFactory } from "../../ast/generated/make-factory"; | ||
|
||
// This function manually creates a module containing a contract with a reference to StateInit in its single field. | ||
// StateInit is declared in stdlib. | ||
// | ||
// contract Test { | ||
// f: StateInit; | ||
// | ||
// init() { | ||
// self.f = initOf Test(); | ||
// } | ||
// } | ||
|
||
function makeModule(mF: MakeAstFactory): A.Module { | ||
// The contract field | ||
const field = mF.makeDummyFieldDecl( | ||
mF.makeDummyId("f"), | ||
mF.makeDummyTypeId("StateInit"), | ||
undefined, | ||
undefined, | ||
); | ||
// The contract init function | ||
const initOfExpr = mF.makeDummyInitOf(mF.makeDummyId("Test"), []); | ||
const path = mF.makeDummyFieldAccess( | ||
mF.makeDummyId("self"), | ||
mF.makeDummyId("f"), | ||
); | ||
const assignStmt = mF.makeDummyStatementAssign(path, initOfExpr); | ||
const init = mF.makeDummyContractInit([], [assignStmt]); | ||
|
||
// The contract | ||
const contract = mF.makeDummyContract( | ||
mF.makeDummyId("Test"), | ||
[], | ||
[], | ||
undefined, | ||
[field, init], | ||
); | ||
|
||
return mF.makeModule([], [contract]); | ||
} | ||
|
||
describe("pre-compilation of ASTs", () => { | ||
const astF = getAstFactory(); | ||
const mF = getMakeAst(astF); | ||
|
||
it("should pass pre-compilation of AST with references to stdlib", () => { | ||
const ctx = new CompilerContext(); | ||
|
||
// An empty tact file is required so that pre-compile does not complain about | ||
// non-existence of an entry point. | ||
const fileSystem = { | ||
["empty.tact"]: "", | ||
}; | ||
|
||
const project = createVirtualFileSystem("/", fileSystem, false); | ||
const stdlib = createVirtualFileSystem("@stdlib", files); | ||
const parser = getParser(astF); | ||
|
||
precompile(ctx, project, stdlib, "empty.tact", parser, astF, [ | ||
makeModule(mF), | ||
]); | ||
}); | ||
|
||
it("should pass pre-compilation with no manual AST", () => { | ||
const ctx = new CompilerContext(); | ||
|
||
// The dummy.tact file contains exactly the same declarations | ||
// carried out by the function makeModule() | ||
const fileSystem = { | ||
["dummy.tact"]: fs | ||
.readFileSync(path.join(__dirname, "dummy.tact")) | ||
.toString("base64"), | ||
}; | ||
|
||
const project = createVirtualFileSystem("/", fileSystem, false); | ||
const stdlib = createVirtualFileSystem("@stdlib", files); | ||
const parser = getParser(astF); | ||
|
||
precompile(ctx, project, stdlib, "dummy.tact", parser, astF, []); | ||
}); | ||
|
||
it("should fail pre-compilation when source files and a manual AST have declaration clashes", () => { | ||
const ctx = new CompilerContext(); | ||
|
||
// The dummy.tact file contains exactly the same declarations | ||
// carried out by the function makeModule() | ||
const fileSystem = { | ||
["dummy.tact"]: fs | ||
.readFileSync(path.join(__dirname, "dummy.tact")) | ||
.toString("base64"), | ||
}; | ||
|
||
const project = createVirtualFileSystem("/", fileSystem, false); | ||
const stdlib = createVirtualFileSystem("@stdlib", files); | ||
const parser = getParser(astF); | ||
|
||
// So, a clash should occur here, since dummy.tact and makeModule() both declare the contract Test. | ||
expect(() => | ||
precompile(ctx, project, stdlib, "dummy.tact", parser, astF, [ | ||
makeModule(mF), | ||
]), | ||
).toThrowErrorMatchingSnapshot(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would rather throw an exception here, because there might be (or will be) types with identifiers inside that are not (will not be) handled.
Throwing an exception here would be slightly safer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done