This commit is contained in:
Brian Terlson 2021-03-26 12:40:14 -07:00 коммит произвёл GitHub
Родитель 00fe2f5c4e
Коммит 72afcd17ba
11 изменённых файлов: 323 добавлений и 34 удалений

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

@ -14,6 +14,7 @@ import {
ScopeNode,
IdentifierNode,
ADLScriptNode,
UsingStatementNode,
} from "./types.js";
import { reportDuplicateSymbols } from "./util.js";
@ -54,7 +55,7 @@ export function createBinder(): Binder {
let currentFile: ADLScriptNode;
let parentNode: Node;
let currentNamespace: NamespaceStatementNode;
let currentNamespace: NamespaceStatementNode | ADLScriptNode;
// Node where locals go.
let scope: ScopeNode;
@ -64,7 +65,7 @@ export function createBinder(): Binder {
function bindSourceFile(program: Program, sourceFile: ADLScriptNode) {
currentFile = sourceFile;
currentNamespace = scope = program.globalNamespace;
currentNamespace = scope = currentFile;
currentFile.locals = new SymbolTable();
currentFile.exports = program.globalNamespace.exports!;
@ -88,6 +89,10 @@ export function createBinder(): Binder {
break;
case SyntaxKind.TemplateParameterDeclaration:
bindTemplateParameterDeclaration(node);
break;
case SyntaxKind.UsingStatement:
bindUsingStatement(node);
break;
}
const prevParent = parentNode;
@ -123,6 +128,7 @@ export function createBinder(): Binder {
function getContainingSymbolTable() {
switch (scope.kind) {
case SyntaxKind.NamespaceStatement:
case SyntaxKind.ADLScript:
return scope.exports!;
default:
return scope.locals!;
@ -144,7 +150,8 @@ export function createBinder(): Binder {
const existingBinding = (scope as NamespaceStatementNode).exports!.get(statement.name.sv);
if (existingBinding && existingBinding.kind === "type") {
statement.symbol = existingBinding;
statement.locals = (existingBinding.node as NamespaceStatementNode).locals;
// locals are never shared.
statement.locals = new SymbolTable();
statement.exports = (existingBinding.node as NamespaceStatementNode).exports;
} else {
declareSymbol(getContainingSymbolTable(), statement, statement.name.sv);
@ -159,12 +166,14 @@ export function createBinder(): Binder {
currentFile.namespaces.push(statement);
if (!statement.statements) {
scope = currentNamespace = statement;
currentFile.locals = statement.locals!;
currentFile.exports = statement.exports!;
}
}
function bindUsingStatement(statement: UsingStatementNode) {
currentFile.usings.push(statement);
}
function bindOperationStatement(statement: OperationStatementNode) {
declareSymbol(getContainingSymbolTable(), statement, statement.id.sv);
}
@ -192,6 +201,8 @@ function hasScope(node: Node): node is ScopeNode {
return true;
case SyntaxKind.NamespaceStatement:
return true;
case SyntaxKind.ADLScript:
return true;
default:
return false;
}

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

@ -38,6 +38,7 @@ import {
MemberExpressionNode,
Sym,
ADLScriptNode,
IntrinsicType,
} from "./types.js";
import { reportDuplicateSymbols } from "./util.js";
@ -87,16 +88,33 @@ export function createChecker(program: Program) {
let instantiatingTemplate: Node | undefined;
let currentSymbolId = 0;
const symbolLinks = new Map<number, SymbolLinks>();
const errorType: IntrinsicType = { kind: "Intrinsic", name: "ErrorType" };
// This variable holds on to the model type that is currently
// being instantiated in checkModelStatement so that it is
// possible to have recursive type references in properties.
let pendingModelType: PendingModelInfo | undefined = undefined;
// for our first act, issue errors for duplicate symbols
for (const file of program.sourceFiles) {
for (const using of file.usings) {
const parentNs = using.parent! as NamespaceStatementNode | ADLScriptNode;
const sym = resolveTypeReference(using.name);
if (sym.kind === "decorator") throwDiagnostic("Can't use a decorator", using);
if (sym.node.kind !== SyntaxKind.NamespaceStatement) {
throwDiagnostic("Using must refer to a namespace", using);
}
for (const [name, binding] of sym.node.exports!) {
parentNs.locals!.set(name, binding);
}
}
}
reportDuplicateSymbols(program.globalNamespace.exports!);
for (const file of program.sourceFiles) {
reportDuplicateSymbols(file.locals!);
for (const ns of file.namespaces) {
reportDuplicateSymbols(ns.locals!);
reportDuplicateSymbols(ns.exports!);
}
}
@ -141,7 +159,7 @@ export function createChecker(program: Program) {
return checkTemplateParameterDeclaration(node);
}
throwDiagnostic("Cannot evaluate " + SyntaxKind[node.kind], node);
return errorType;
}
function getTypeName(type: Type): string {
@ -463,13 +481,13 @@ export function createChecker(program: Program) {
let containerSourceFile: ADLScriptNode;
while (scope) {
if ("locals" in scope) {
binding = resolveIdentifierInTable(node, scope.locals!);
if ("exports" in scope) {
binding = resolveIdentifierInTable(node, scope.exports!);
if (binding) break;
}
if ("exports" in scope) {
binding = resolveIdentifierInTable(node, scope.exports!);
if ("locals" in scope) {
binding = resolveIdentifierInTable(node, scope.locals!);
if (binding) break;
}

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

@ -61,11 +61,7 @@ export function createDiagnostic(
location = getSourceLocation(target);
} catch (err) {
locationError = err;
location = {
file: createSourceFile("", "<unknown location>"),
pos: 0,
end: 0,
};
location = createDummySourceLocation();
}
if (typeof message === "string") {
@ -197,15 +193,22 @@ export function getSourceLocation(target: DiagnosticTarget): SourceLocation {
}
if (target.kind === "decorator") {
return {
// We currently report all decorators at line 1 of defining .js path.
file: createSourceFile("", target.path),
pos: 0,
end: 0,
};
return createDummySourceLocation(target.path);
}
return getSourceLocationOfNode("node" in target ? target.node : target);
const node = "node" in target ? target.node! : target;
if (node.kind === "Intrinsic") {
return createDummySourceLocation();
}
return getSourceLocationOfNode(node);
}
function createDummySourceLocation(loc = "<unknown location>") {
return {
file: createSourceFile("", loc),
pos: 0,
end: 0,
};
}
function getSourceLocationOfNode(node: Node): SourceLocation {

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

@ -1,5 +1,4 @@
import { checkServerIdentity } from "tls";
import { DiagnosticError, throwDiagnostic } from "./diagnostics.js";
import { throwDiagnostic } from "./diagnostics.js";
import { createScanner, Token } from "./scanner.js";
import * as Types from "./types.js";
@ -19,6 +18,7 @@ export function parse(code: string | Types.SourceFile) {
interfaces: [],
models: [],
namespaces: [],
usings: [],
};
let seenBlocklessNs = false;
@ -61,6 +61,11 @@ export function parse(code: string | Types.SourceFile) {
return parseNamespaceStatement(decorators);
case Token.OpKeyword:
return parseOperationStatement(decorators);
case Token.UsingKeyword:
if (decorators.length > 0) {
error("Cannot decorate using statements");
}
return parseUsingStatement();
case Token.Semicolon:
if (decorators.length > 0) {
error("Cannot decorate an empty statement");
@ -92,6 +97,11 @@ export function parse(code: string | Types.SourceFile) {
return ns;
case Token.OpKeyword:
return parseOperationStatement(decorators);
case Token.UsingKeyword:
if (decorators.length > 0) {
error("Cannot decorate using statements");
}
return parseUsingStatement();
case Token.Semicolon:
if (decorators.length > 0) {
error("Cannot decorate an empty statement");
@ -171,6 +181,21 @@ export function parse(code: string | Types.SourceFile) {
return outerNs;
}
function parseUsingStatement(): Types.UsingStatementNode {
const pos = tokenPos();
parseExpected(Token.UsingKeyword);
const name = parseIdentifierOrMemberExpression();
parseExpected(Token.Semicolon);
return finishNode(
{
kind: Types.SyntaxKind.UsingStatement,
name,
},
pos
);
}
function parseOperationStatement(
decorators: Array<Types.DecoratorExpressionNode>
): Types.OperationStatementNode {
@ -810,6 +835,8 @@ export function visitChildren<T>(node: Types.Node, cb: NodeCb<T>): T | undefined
Array.isArray(node.statements)
? visitEach(cb, node.statements as Types.Statement[])
: visitNode(cb, node.statements);
case Types.SyntaxKind.UsingStatement:
return visitNode(cb, node.name);
case Types.SyntaxKind.IntersectionExpression:
return visitEach(cb, node.options);
case Types.SyntaxKind.MemberExpression:
@ -842,7 +869,13 @@ export function visitChildren<T>(node: Types.Node, cb: NodeCb<T>): T | undefined
case Types.SyntaxKind.NumericLiteral:
case Types.SyntaxKind.BooleanLiteral:
case Types.SyntaxKind.Identifier:
case Types.SyntaxKind.TemplateParameterDeclaration:
return;
default:
// Dummy const to ensure we handle all node types.
// If you get an error here, add a case for the new node type
// you added..
const assertNever: never = node;
return;
}
}

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

@ -60,6 +60,7 @@ export enum Token {
ImportKeyword,
ModelKeyword,
NamespaceKeyword,
UsingKeyword,
OpKeyword,
ExtendsKeyword,
TrueKeyword,
@ -70,6 +71,7 @@ const keywords = new Map([
["import", Token.ImportKeyword],
["model", Token.ModelKeyword],
["namespace", Token.NamespaceKeyword],
["using", Token.UsingKeyword],
["op", Token.OpKeyword],
["extends", Token.ExtendsKeyword],
["true", Token.TrueKeyword],

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

@ -6,7 +6,7 @@ import { MultiKeyMap } from "./checker";
*/
export interface BaseType {
kind: string;
node: Node;
node?: Node;
instantiationParameters?: Array<Type>;
}
@ -21,11 +21,18 @@ export type Type =
| BooleanLiteralType
| ArrayType
| TupleType
| UnionType;
| UnionType
| IntrinsicType;
export interface IntrinsicType extends BaseType {
kind: "Intrinsic";
name: string;
}
export interface ModelType extends BaseType {
kind: "Model";
name: string;
node: ModelStatementNode | ModelExpressionNode | IntersectionExpressionNode;
namespace?: NamespaceType;
properties: Map<string, ModelTypeProperty>;
baseModels: Array<ModelType>;
@ -98,11 +105,13 @@ export interface TupleType extends BaseType {
export interface UnionType extends BaseType {
kind: "Union";
node: UnionExpressionNode;
options: Array<Type>;
}
export interface TemplateParameterType extends BaseType {
kind: "TemplateParameter";
node: TemplateParameterDeclarationNode;
}
// trying to avoid masking built-in Symbol
@ -142,6 +151,7 @@ export enum SyntaxKind {
DecoratorExpression,
MemberExpression,
NamespaceStatement,
UsingStatement,
OperationStatement,
ModelStatement,
ModelExpression,
@ -184,12 +194,14 @@ export interface ADLScriptNode extends BaseNode {
locals?: SymbolTable;
exports?: SymbolTable;
namespaces: NamespaceStatementNode[]; // list of namespaces in this file (initialized during binding)
usings: UsingStatementNode[];
}
export type Statement =
| ImportStatementNode
| ModelStatementNode
| NamespaceStatementNode
| UsingStatementNode
| OperationStatementNode;
export interface DeclarationNode {
@ -203,7 +215,7 @@ export type Declaration =
| OperationStatementNode
| TemplateParameterDeclarationNode;
export type ScopeNode = NamespaceStatementNode | ModelStatementNode;
export type ScopeNode = NamespaceStatementNode | ModelStatementNode | ADLScriptNode;
export interface ImportStatementNode extends BaseNode {
kind: SyntaxKind.ImportStatement;
@ -257,6 +269,11 @@ export interface NamespaceStatementNode extends BaseNode, DeclarationNode {
decorators: Array<DecoratorExpressionNode>;
}
export interface UsingStatementNode extends BaseNode {
kind: SyntaxKind.UsingStatement;
name: IdentifierNode | MemberExpressionNode;
}
export interface OperationStatementNode extends BaseNode, DeclarationNode {
kind: SyntaxKind.OperationStatement;
id: IdentifierNode;

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

@ -27,6 +27,7 @@ Keyword :
`namespace`
`op`
`extends`
`using`
Identifier :
IdentifierName but not Keyword
@ -188,11 +189,11 @@ Statement :
ModelStatement
NamespaceStatement
OperationStatement
UsingStatement
`;`
NamedImports :
Identifier
NamedImports `,` Identifier
UsingStatement :
`using` IdentifierOrMemberExpression `;`
ModelStatement :
DecoratorList? `model` Identifier TemplateParameters? ModelHeritage? `{` ModelBody? `}`

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

@ -24,6 +24,7 @@
&emsp;&emsp;&emsp;<a name="Keyword-lpev-y-g"></a>`` namespace ``
&emsp;&emsp;&emsp;<a name="Keyword-ytodrzn0"></a>`` op ``
&emsp;&emsp;&emsp;<a name="Keyword-rmwjtwx9"></a>`` extends ``
&emsp;&emsp;&emsp;<a name="Keyword-8d2nu2lj"></a>`` using ``
&emsp;&emsp;<a name="Identifier"></a>*Identifier* **:**
&emsp;&emsp;&emsp;<a name="Identifier-exwdmcdi"></a>*[IdentifierName](#IdentifierName)* **but not** *[Keyword](#Keyword)*
@ -174,11 +175,11 @@
&emsp;&emsp;&emsp;<a name="Statement-ngbc4m7o"></a>*[ModelStatement](#ModelStatement)*
&emsp;&emsp;&emsp;<a name="Statement-_ljtj5og"></a>*[NamespaceStatement](#NamespaceStatement)*
&emsp;&emsp;&emsp;<a name="Statement-qlyu8ssa"></a>*[OperationStatement](#OperationStatement)*
&emsp;&emsp;&emsp;<a name="Statement-rmuscggd"></a>*[UsingStatement](#UsingStatement)*
&emsp;&emsp;&emsp;<a name="Statement-sg2sawim"></a>`` ; ``
&emsp;&emsp;<a name="NamedImports"></a>*NamedImports* **:**
&emsp;&emsp;&emsp;<a name="NamedImports-bras6mo_"></a>*[Identifier](#Identifier)*
&emsp;&emsp;&emsp;<a name="NamedImports-arox3l8x"></a>*[NamedImports](#NamedImports)*&emsp;`` , ``&emsp;*[Identifier](#Identifier)*
&emsp;&emsp;<a name="UsingStatement"></a>*UsingStatement* **:**
&emsp;&emsp;&emsp;<a name="UsingStatement-nfxtjnnc"></a>`` using ``&emsp;*[IdentifierOrMemberExpression](#IdentifierOrMemberExpression)*&emsp;`` ; ``
&emsp;&emsp;<a name="ModelStatement"></a>*ModelStatement* **:**
&emsp;&emsp;&emsp;<a name="ModelStatement-w3a1y-ib"></a>*[DecoratorList](#DecoratorList)*<sub>opt</sub>&emsp;`` model ``&emsp;*[Identifier](#Identifier)*&emsp;*[TemplateParameters](#TemplateParameters)*<sub>opt</sub>&emsp;*[ModelHeritage](#ModelHeritage)*<sub>opt</sub>&emsp;`` { ``&emsp;*[ModelBody](#ModelBody)*<sub>opt</sub>&emsp;`` } ``

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

@ -157,4 +157,26 @@ describe("blockless namespaces", () => {
);
await testHost.compile("./");
});
it("binds correctly", async () => {
testHost.addAdlFile(
"a.adl",
`
namespace N.M;
model A { }
`
);
testHost.addAdlFile(
"b.adl",
`
model X { a: N.M.A }
`
);
try {
await testHost.compile("/");
} catch (e) {
console.log(e.diagnostics);
throw e;
}
});
});

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

@ -0,0 +1,177 @@
import { ok, rejects, strictEqual } from "assert";
import { ModelType } from "../../compiler/types";
import { createTestHost, TestHost } from "../test-host.js";
describe("using statements", () => {
let testHost: TestHost;
beforeEach(async () => {
testHost = await createTestHost();
});
it("works in global scope", async () => {
testHost.addAdlFile(
"a.adl",
`
namespace N;
model X { x: int32 }
`
);
testHost.addAdlFile(
"b.adl",
`
using N;
@test model Y { ... X }
`
);
const { Y } = (await testHost.compile("./")) as {
Y: ModelType;
};
strictEqual(Y.properties.size, 1);
});
it("works in namespaces", async () => {
testHost.addAdlFile(
"a.adl",
`
namespace N;
model X { x: int32 }
`
);
testHost.addAdlFile(
"b.adl",
`
namespace Z;
using N;
@test model Y { ... X }
`
);
const { Y } = (await testHost.compile("./")) as {
Y: ModelType;
};
strictEqual(Y.properties.size, 1);
});
it("works with dotted namespaces", async () => {
testHost.addAdlFile(
"a.adl",
`
namespace N.M;
model X { x: int32 }
`
);
testHost.addAdlFile(
"b.adl",
`
using N.M;
@test model Y { ... X }
`
);
const { Y } = (await testHost.compile("./")) as {
Y: ModelType;
};
strictEqual(Y.properties.size, 1);
});
it("throws errors for duplicate imported usings", async () => {
testHost.addAdlFile(
"a.adl",
`
namespace N.M;
model X { x: int32 }
`
);
testHost.addAdlFile(
"b.adl",
`
using N.M;
using N.M;
@test model Y { ... X }
`
);
await rejects(testHost.compile("./"));
});
it("throws errors for different usings with the same bindings", async () => {
testHost.addAdlFile(
"a.adl",
`
namespace N {
model A { }
}
namespace M {
model A { }
}
`
);
testHost.addAdlFile(
"b.adl",
`
using N;
using M;
`
);
await rejects(testHost.compile("./"));
});
it("resolves 'local' decls over usings", async () => {
testHost.addAdlFile(
"a.adl",
`
namespace N;
model A { a: string }
`
);
testHost.addAdlFile(
"b.adl",
`
using N;
model A { a: int32 | string }
@test model B { ... A }
`
);
const { B } = (await testHost.compile("./")) as {
B: ModelType;
};
strictEqual(B.properties.size, 1);
strictEqual(B.properties.get("a")!.type.kind, "Union");
});
it("usings are local to a file", async () => {
testHost.addAdlFile(
"a.adl",
`
namespace N;
model A { a: string }
`
);
testHost.addAdlFile(
"b.adl",
`
namespace M {
using N;
}
namespace M {
model X = A;
}
`
);
await rejects(testHost.compile("./"));
});
});

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

@ -157,6 +157,10 @@ describe("syntax", () => {
]);
});
describe("using statements", () => {
parseEach(["using A;", "using A.B;", "namespace Foo { using A; }"]);
});
describe("multiple statements", () => {
parseEach([
`