Feature: ADL Program types walker (#586)

This commit is contained in:
Timothee Guerin 2021-06-18 09:43:28 -07:00 коммит произвёл GitHub
Родитель 2ec0c6c455
Коммит 34ea3a669a
4 изменённых файлов: 164 добавлений и 7 удалений

Просмотреть файл

@ -46,6 +46,20 @@ import {
UnionType,
} from "./types.js";
export interface Checker {
getTypeForNode(node: Node): Type;
checkProgram(program: Program): void;
getGlobalNamespaceType(): NamespaceType;
getLiteralType(node: StringLiteralNode): StringLiteralType;
getLiteralType(node: NumericLiteralNode): NumericLiteralType;
getLiteralType(node: BooleanLiteralNode): BooleanLiteralType;
getLiteralType(node: LiteralNode): LiteralType;
getTypeName(type: Type): string;
getNamespaceString(type: NamespaceType | undefined): string;
}
/**
* A map keyed by a set of objects.
*
@ -92,11 +106,20 @@ interface PendingModelInfo {
type: ModelType;
}
export function createChecker(program: Program) {
export function createChecker(program: Program): Checker {
let templateInstantiation: Type[] = [];
let instantiatingTemplate: Node | undefined;
let currentSymbolId = 0;
const symbolLinks = new Map<number, SymbolLinks>();
const root = createType({
kind: "Namespace",
name: "",
node: program.globalNamespace,
models: new Map(),
operations: new Map(),
namespaces: new Map(),
});
const errorType: ErrorType = { kind: "Intrinsic", name: "ErrorType" };
// This variable holds on to the model type that is currently
@ -133,7 +156,7 @@ export function createChecker(program: Program) {
getLiteralType,
getTypeName,
getNamespaceString,
checkOperation,
getGlobalNamespaceType,
};
function getTypeForNode(node: Node): Type {
@ -197,8 +220,7 @@ export function createChecker(program: Program) {
function getNamespaceString(type: NamespaceType | undefined): string {
if (!type) return "";
const parent = type.namespace;
return parent ? `${getNamespaceString(parent)}.${type.name}` : type.name;
return parent && parent.name !== "" ? `${getNamespaceString(parent)}.${type.name}` : type.name;
}
function getEnumName(e: EnumType): string {
@ -472,7 +494,8 @@ export function createChecker(program: Program) {
function getParentNamespaceType(
node: ModelStatementNode | NamespaceStatementNode | OperationStatementNode | EnumStatementNode
): NamespaceType | undefined {
if (!node.namespaceSymbol) return undefined;
if (node === root.node) return undefined;
if (!node.namespaceSymbol) return root;
const symbolLinks = getSymbolLinks(node.namespaceSymbol);
compilerAssert(symbolLinks.type, "Parent namespace isn't typed yet.", node);
@ -494,6 +517,10 @@ export function createChecker(program: Program) {
return type;
}
function getGlobalNamespaceType() {
return root;
}
function checkTupleExpression(node: TupleExpressionNode): TupleType {
return createType({
kind: "Tuple",

Просмотреть файл

@ -1,7 +1,7 @@
import { dirname, extname, isAbsolute, resolve } from "path";
import resolveModule from "resolve";
import { createBinder, createSymbolTable } from "./binder.js";
import { createChecker } from "./checker.js";
import { Checker, createChecker } from "./checker.js";
import { createDiagnostic, createSourceFile, DiagnosticTarget, NoTarget } from "./diagnostics.js";
import { Message } from "./messages.js";
import { CompilerOptions } from "./options.js";
@ -30,7 +30,7 @@ export interface Program {
sourceFiles: ADLScriptNode[];
literalTypes: Map<string | number | boolean, LiteralType>;
host: CompilerHost;
checker?: ReturnType<typeof createChecker>;
checker?: Checker;
readonly diagnostics: readonly Diagnostic[];
evalAdlScript(adlScript: string, filePath?: string): void;
onBuild(cb: (program: Program) => void): Promise<void> | void;

Просмотреть файл

@ -0,0 +1,101 @@
import { assert } from "console";
import { createTestHost, TestHost } from "../test-host.js";
describe("adl: global namespace", () => {
let testHost: TestHost;
beforeEach(async () => {
testHost = await createTestHost();
});
describe("it adds top level entities to the global namespace", () => {
it("adds top-level namespaces", async () => {
testHost.addAdlFile("main.adl", `namespace Foo {}`);
await testHost.compile("./");
const globalNamespaceType = testHost.program.checker?.getGlobalNamespaceType();
assert(
globalNamespaceType?.namespaces.get("Foo"),
"Namespace Foo was added to global namespace type"
);
});
it("adds top-level models", async () => {
testHost.addAdlFile("main.adl", `model MyModel {}`);
await testHost.compile("./");
const globalNamespaceType = testHost.program.checker?.getGlobalNamespaceType();
assert(
globalNamespaceType?.models.get("MyModel"),
"model MyModel was added to global namespace type"
);
});
it("adds top-level oeprations", async () => {
testHost.addAdlFile("main.adl", `op myOperation(): string;`);
await testHost.compile("./");
const globalNamespaceType = testHost.program.checker?.getGlobalNamespaceType();
assert(
globalNamespaceType?.operations.get("myOperation"),
"operation myOperation was added to global namespace type"
);
});
});
describe("it adds top level entities used in other files to the global namespace", () => {
beforeEach(() => {
testHost.addAdlFile(
"main.adl",
`
import "./a.adl";
model Base {}
`
);
});
it("adds top-level namespaces", async () => {
testHost.addAdlFile("a.adl", `namespace Foo {}`);
await testHost.compile("./");
const globalNamespaceType = testHost.program.checker?.getGlobalNamespaceType();
assert(
globalNamespaceType?.namespaces.get("Foo"),
"Namespace Foo was added to global namespace type"
);
assert(
globalNamespaceType?.namespaces.get("Base"),
"Should still reference main file top-level entities"
);
});
it("adds top-level models", async () => {
testHost.addAdlFile("a.adl", `model MyModel {}`);
await testHost.compile("./");
const globalNamespaceType = testHost.program.checker?.getGlobalNamespaceType();
assert(
globalNamespaceType?.models.get("MyModel"),
"model MyModel was added to global namespace type"
);
});
it("adds top-level oeprations", async () => {
testHost.addAdlFile("a.adl", `op myOperation(): string;`);
await testHost.compile("./");
const globalNamespaceType = testHost.program.checker?.getGlobalNamespaceType();
assert(
globalNamespaceType?.operations.get("myOperation"),
"operation myOperation was added to global namespace type"
);
});
});
});

Просмотреть файл

@ -421,3 +421,32 @@ describe("adl: blockless namespaces", () => {
strictEqual(Foo.namespaces.size, 1);
});
});
describe("adl: namespace type name", () => {
let testHost: TestHost;
beforeEach(async () => {
testHost = await createTestHost();
});
it("prefix with the namespace of the entity", async () => {
testHost.addAdlFile(
"a.adl",
`
namespace Foo;
@test()
model Model1 {}
namespace Other.Bar {
@test()
model Model2 {}
}
`
);
const { Model1, Model2 } = await testHost.compile("/a.adl");
strictEqual(testHost.program.checker?.getTypeName(Model1), "Foo.Model1");
strictEqual(testHost.program.checker?.getTypeName(Model2), "Foo.Other.Bar.Model2");
});
});