Fix referencing symbols found in parent ns of top blockless ns

This commit is contained in:
Brian Terlson 2021-03-26 16:50:35 -07:00 коммит произвёл David Wilson
Родитель a2a5a91a1f
Коммит 230c66b357
5 изменённых файлов: 111 добавлений и 28 удалений

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

@ -1,6 +1,6 @@
import { createDiagnostic, Diagnostic, DiagnosticError, formatDiagnostic } from "./diagnostics.js";
import { visitChildren } from "./parser.js";
import { Program } from "./program.js";
import { createProgram, Program } from "./program.js";
import {
NamespaceStatementNode,
ModelStatementNode,
@ -54,8 +54,9 @@ export interface Binder {
export function createBinder(): Binder {
let currentFile: ADLScriptNode;
let parentNode: Node;
let currentNamespace: NamespaceStatementNode | ADLScriptNode;
let globalNamespace: NamespaceStatementNode;
let fileNamespace: NamespaceStatementNode;
let currentNamespace: NamespaceStatementNode;
// Node where locals go.
let scope: ScopeNode;
@ -64,12 +65,12 @@ export function createBinder(): Binder {
};
function bindSourceFile(program: Program, sourceFile: ADLScriptNode) {
globalNamespace = program.globalNamespace;
fileNamespace = globalNamespace;
currentFile = sourceFile;
currentNamespace = scope = currentFile;
currentFile.locals = new SymbolTable();
currentFile.exports = program.globalNamespace.exports!;
currentNamespace = scope = globalNamespace;
bindNode(sourceFile);
currentFile.inScopeNamespaces.push(globalNamespace);
}
function bindNode(node: Node) {
@ -110,9 +111,7 @@ export function createBinder(): Binder {
visitChildren(node, bindNode);
if (node.kind !== SyntaxKind.NamespaceStatement) {
// we've finished binding all the children, so make sure
// there are no duplicates.
reportDuplicateSymbols(node.locals!);
reportDuplicateSymbols(node.locals!)
}
scope = prevScope;
@ -128,8 +127,9 @@ export function createBinder(): Binder {
function getContainingSymbolTable() {
switch (scope.kind) {
case SyntaxKind.NamespaceStatement:
case SyntaxKind.ADLScript:
return scope.exports!;
case SyntaxKind.ADLScript:
return fileNamespace.exports!;
default:
return scope.locals!;
}
@ -147,7 +147,7 @@ export function createBinder(): Binder {
function bindNamespaceStatement(statement: NamespaceStatementNode) {
// check if there's an existing symbol for this namespace
const existingBinding = (scope as NamespaceStatementNode).exports!.get(statement.name.sv);
const existingBinding = currentNamespace.exports!.get(statement.name.sv);
if (existingBinding && existingBinding.kind === "type") {
statement.symbol = existingBinding;
// locals are never shared.
@ -166,10 +166,13 @@ export function createBinder(): Binder {
currentFile.namespaces.push(statement);
if (statement.statements === undefined) {
currentFile.exports = statement.exports!;
scope = currentNamespace = statement;
} else if (!Array.isArray(statement.statements)) {
scope = currentNamespace = statement;
fileNamespace = statement;
let current: ADLScriptNode | NamespaceStatementNode = statement;
while (current.kind !== SyntaxKind.ADLScript) {
currentFile.inScopeNamespaces.push(current);
current = current.parent as ADLScriptNode | NamespaceStatementNode;
}
}
}
@ -192,6 +195,11 @@ export function createBinder(): Binder {
}
node.namespaceSymbol = scope.symbol;
} else if (scope.kind === SyntaxKind.ADLScript) {
if (node.kind === SyntaxKind.TemplateParameterDeclaration) {
throw new Error("Attempted to declare template parameter in global scope");
}
node.namespaceSymbol = fileNamespace.symbol;
}
table.set(name, symbol);
@ -203,7 +211,7 @@ function hasScope(node: Node): node is ScopeNode {
case SyntaxKind.ModelStatement:
return true;
case SyntaxKind.NamespaceStatement:
return Array.isArray(node.statements);
return node.statements !== undefined;
case SyntaxKind.ADLScript:
return true;
default:

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

@ -478,31 +478,36 @@ export function createChecker(program: Program) {
function resolveIdentifier(node: IdentifierNode) {
let scope: Node | undefined = node.parent;
let binding;
let containerSourceFile: ADLScriptNode;
while (scope) {
while (scope && scope.kind !== SyntaxKind.ADLScript) {
if ("exports" in scope) {
binding = resolveIdentifierInTable(node, scope.exports!);
if (binding) break;
if (binding) return binding;
}
if ("locals" in scope) {
binding = resolveIdentifierInTable(node, scope.locals!);
if (binding) break;
if (binding) return binding;
}
scope = scope.parent;
}
if (!binding) {
binding = resolveIdentifierInTable(node, program.globalNamespace.exports!);
if (!binding && scope && scope.kind === SyntaxKind.ADLScript) {
// check any blockless namespace decls and global scope
for (const ns of scope.inScopeNamespaces) {
binding = resolveIdentifierInTable(node, ns.exports!);
if (binding) return binding;
}
// check "global scope" usings
binding = resolveIdentifierInTable(node, scope.locals);
if (binding) return binding;
}
if (!binding) {
throwDiagnostic("Unknown identifier " + node.sv, node);
}
throwDiagnostic("Unknown identifier " + node.sv, node);
return binding;
}
function resolveTypeReference(node: ReferenceExpression): DecoratorSymbol | TypeSymbol {

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

@ -1,3 +1,4 @@
import { SymbolTable } from "./binder.js";
import { throwDiagnostic } from "./diagnostics.js";
import { createScanner, Token } from "./scanner.js";
import * as Types from "./types.js";
@ -19,6 +20,8 @@ export function parse(code: string | Types.SourceFile) {
models: [],
namespaces: [],
usings: [],
locals: new SymbolTable(),
inScopeNamespaces: []
};
let seenBlocklessNs = false;

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

@ -191,9 +191,9 @@ export interface ADLScriptNode extends BaseNode {
models: Array<ModelType>;
file: SourceFile;
interfaces: Array<NamespaceType>;
locals?: SymbolTable;
exports?: SymbolTable;
inScopeNamespaces: NamespaceStatementNode[]; // namespaces that declarations in this file belong to
namespaces: NamespaceStatementNode[]; // list of namespaces in this file (initialized during binding)
locals: SymbolTable;
usings: UsingStatementNode[];
}

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

@ -114,6 +114,42 @@ describe("namespaces with blocks", () => {
};
strictEqual(Z.properties.size, 2, "has two properties");
});
it("can see things in outer scope same file", async () => {
testHost.addAdlFile(
"a.adl",
`
model A { }
namespace N { model B extends A { } }
`
);
await testHost.compile("./");
});
it("can see things in outer scope cross file", async () => {
testHost.addAdlFile(
"a.adl",
`
model A { }
`
);
testHost.addAdlFile(
"b.adl",
`
model B extends A { }
`
);
testHost.addAdlFile(
"c.adl",
`
model C { }
namespace foo {
op foo(a: A, b: B): C;
}
`
);
await testHost.compile("./");
})
});
describe("blockless namespaces", () => {
@ -176,6 +212,36 @@ describe("blockless namespaces", () => {
}
});
it("does lookup correctly with nested namespaces", async () => {
testHost.addAdlFile(
"a.adl",
`
namespace Repro;
model Yo {
}
model Hey {
wat: Yo;
}
`
);
testHost.addAdlFile(
"b.adl",
`
namespace Repro.Uhoh;
model SayYo {
yo: Hey;
wat: Yo;
}
`
);
try {
await testHost.compile("./");
} catch (e) {
console.log(e.diagnostics);
throw e;
}
});
it("binds correctly", async () => {
testHost.addAdlFile(
"a.adl",
@ -251,6 +317,7 @@ describe("blockless namespaces", () => {
};
ok(M.namespace);
ok(O.namespace);
strictEqual(O.namespace, M);
});
});