Merge pull request #324 from bterlson/new-checker

Refactor checker, implemented nested namespaces, fix bugs
This commit is contained in:
Brian Terlson 2021-03-04 15:56:00 -08:00 коммит произвёл GitHub
Родитель 8c6c5cb97b f9a2d3ce7b
Коммит 338ff4a187
10 изменённых файлов: 481 добавлений и 248 удалений

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

@ -1,7 +1,6 @@
import { DiagnosticError, formatDiagnostic } from "./diagnostics.js";
import { visitChildren } from "./parser.js";
import { ADLSourceFile, Program } from "./program.js";
import { createSourceFile } from "./scanner.js";
import {
NamespaceStatementNode,
ModelStatementNode,
@ -10,6 +9,9 @@ import {
TemplateParameterDeclarationNode,
SourceLocation,
Sym,
Declaration,
OperationStatementNode,
ScopeNode,
} from "./types.js";
export class SymbolTable extends Map<string, Sym> {
@ -49,9 +51,10 @@ export function createBinder(): Binder {
let currentFile: ADLSourceFile;
let parentNode: Node;
// Node where locals go.
let scope: Node;
let currentNamespace: NamespaceStatementNode | undefined;
// Node where locals go.
let scope: ScopeNode;
return {
bindSourceFile,
};
@ -63,6 +66,7 @@ export function createBinder(): Binder {
) {
currentFile = sourceFile;
bindNode(sourceFile.ast);
reportDuplicateSymbols(currentFile.symbols);
// everything is global
if (globalScope) {
@ -80,13 +84,16 @@ export function createBinder(): Binder {
switch (node.kind) {
case SyntaxKind.ModelStatement:
bindModelStatement(<any>node);
bindModelStatement(node);
break;
case SyntaxKind.NamespaceStatement:
bindInterfaceStatement(<any>node);
bindNamespaceStatement(node);
break;
case SyntaxKind.OperationStatement:
bindOperationStatement(node);
break;
case SyntaxKind.TemplateParameterDeclaration:
bindTemplateParameterDeclaration(<any>node);
bindTemplateParameterDeclaration(node);
}
const prevParent = parentNode;
@ -95,9 +102,16 @@ export function createBinder(): Binder {
if (hasScope(node)) {
const prevScope = scope;
const prevNamespace = currentNamespace;
scope = node;
if (node.kind === SyntaxKind.NamespaceStatement) {
currentNamespace = node;
}
visitChildren(node, bindNode);
scope = prevScope;
currentNamespace = prevNamespace;
} else {
visitChildren(node, bindNode);
}
@ -106,34 +120,42 @@ export function createBinder(): Binder {
parentNode = prevParent;
}
function getContainingSymbolTable() {
return scope ? scope.locals! : currentFile.symbols;
}
function bindTemplateParameterDeclaration(node: TemplateParameterDeclarationNode) {
(<ModelStatementNode>scope).locals!.set(node.sv, {
kind: "type",
node: node,
name: node.sv,
});
declareSymbol(getContainingSymbolTable(), node);
}
function bindModelStatement(node: ModelStatementNode) {
currentFile.symbols.set(node.id.sv, {
kind: "type",
node: node,
name: node.id.sv,
});
declareSymbol(getContainingSymbolTable(), node);
// initialize locals for type parameters.
// Initialize locals for type parameters
node.locals = new SymbolTable();
}
function bindInterfaceStatement(statement: NamespaceStatementNode) {
currentFile.symbols.set(statement.id.sv, {
kind: "type",
node: statement,
name: statement.id.sv,
});
function bindNamespaceStatement(statement: NamespaceStatementNode) {
declareSymbol(getContainingSymbolTable(), statement);
// Initialize locals for namespace members
statement.locals = new SymbolTable();
}
function reportDuplicateSymbols(globalSymbols: SymbolTable) {
function bindOperationStatement(statement: OperationStatementNode) {
declareSymbol(getContainingSymbolTable(), statement);
}
function declareSymbol(table: SymbolTable, node: Declaration) {
const symbol = createTypeSymbol(node, node.id.sv);
node.symbol = symbol;
if (currentNamespace && node.kind !== SyntaxKind.TemplateParameterDeclaration) {
node.namespaceSymbol = currentNamespace.symbol;
}
table.set(node.id.sv, symbol);
}
function reportDuplicateSymbols(symbols: SymbolTable) {
let reported = new Set<Sym>();
let messages = new Array<string>();
@ -141,10 +163,17 @@ export function createBinder(): Binder {
report(symbol);
}
for (const symbol of globalSymbols.duplicates) {
for (const symbol of symbols.duplicates) {
report(symbol);
}
// Check symbols that have their own scopes
for (const [_, symbol] of symbols) {
if (symbol.kind === "type" && hasScope(symbol.node) && symbol.node.locals) {
reportDuplicateSymbols(symbol.node.locals);
}
}
if (messages.length > 0) {
// TODO: We're now reporting all duplicates up to the binding of the first file
// that introduced one, but still bailing the compilation rather than
@ -154,6 +183,7 @@ export function createBinder(): Binder {
// That said, decorators are entered into the global symbol table before
// any source file is bound and therefore this will include all duplicate
// decorator implementations.
throw new DiagnosticError(messages.join("\n"));
}
@ -167,11 +197,21 @@ export function createBinder(): Binder {
}
}
function hasScope(node: Node) {
function hasScope(node: Node): node is ScopeNode {
switch (node.kind) {
case SyntaxKind.ModelStatement:
return true;
case SyntaxKind.NamespaceStatement:
return true;
default:
return false;
}
}
function createTypeSymbol(node: Node, name: string): TypeSymbol {
return {
kind: "type",
node,
name,
};
}

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

@ -1,3 +1,4 @@
import { SymbolTable } from "./binder.js";
import { throwDiagnostic } from "./diagnostics.js";
import { ADLSourceFile, Program } from "./program.js";
import {
@ -5,10 +6,10 @@ import {
BooleanLiteralNode,
BooleanLiteralType,
IdentifierNode,
NamespacePropertyNode,
OperationStatementNode,
NamespaceStatementNode,
Namespace,
NamespaceProperty,
NamespaceType,
OperationType,
IntersectionExpressionNode,
LiteralNode,
LiteralType,
@ -23,7 +24,7 @@ import {
StringLiteralNode,
StringLiteralType,
SyntaxKind,
TemplateApplicationNode,
TypeReferenceNode,
TemplateParameterDeclarationNode,
TupleExpressionNode,
TupleType,
@ -31,6 +32,11 @@ import {
UnionExpressionNode,
UnionType,
ReferenceExpression,
DecoratorSymbol,
TypeSymbol,
SymbolLinks,
MemberExpressionNode,
Sym,
} from "./types.js";
/**
@ -72,6 +78,8 @@ export class MultiKeyMap<T> {
export function createChecker(program: Program) {
let templateInstantiation: Array<Type> = [];
let instantiatingTemplate: Node | undefined;
let currentSymbolId = 0;
const symbolLinks = new Map<number, SymbolLinks>();
const seq = 0;
@ -80,13 +88,10 @@ export function createChecker(program: Program) {
checkProgram,
getLiteralType,
getTypeName,
checkNamespaceProperty,
checkOperation,
};
function getTypeForNode(node: Node): Type {
const cached = program.typeCache.get([node, ...templateInstantiation.values()]);
if (cached) return cached;
switch (node.kind) {
case SyntaxKind.ModelExpression:
return checkModel(node);
@ -96,11 +101,8 @@ export function createChecker(program: Program) {
return checkModelProperty(node);
case SyntaxKind.NamespaceStatement:
return checkNamespace(node);
case SyntaxKind.NamespaceProperty:
return checkNamespaceProperty(node);
case SyntaxKind.Identifier:
// decorator bindings presently return an empty binding
return <any>checkIdentifier(node);
case SyntaxKind.OperationStatement:
return checkOperation(node);
case SyntaxKind.NumericLiteral:
return checkNumericLiteral(node);
case SyntaxKind.BooleanLiteral:
@ -115,8 +117,8 @@ export function createChecker(program: Program) {
return checkUnionExpression(node);
case SyntaxKind.IntersectionExpression:
return checkIntersectionExpression(node);
case SyntaxKind.TemplateApplication:
return checkTemplateApplication(node);
case SyntaxKind.TypeReference:
return checkTypeReference(node);
case SyntaxKind.TemplateParameterDeclaration:
return checkTemplateParameterDeclaration(node);
}
@ -141,20 +143,27 @@ export function createChecker(program: Program) {
return "(unnamed type)";
}
function getNamespaceString(type: NamespaceType | undefined): string {
if (!type) return "";
const parent = type.namespace;
return parent ? `${getNamespaceString(parent)}.${type.name}` : type.name;
}
function getModelName(model: ModelType) {
if ((<ModelStatementNode>model.node).assignment) {
return model.name;
} else if (model.templateArguments && model.templateArguments.length > 0) {
const nsName = getNamespaceString(model.namespace);
const modelName = (nsName ? nsName + "." : "") + (model.name || "(anonymous model)");
if (model.templateArguments && model.templateArguments.length > 0) {
// template instantiation
const args = model.templateArguments.map(getTypeName);
return `${model.name}<${args.join(", ")}>`;
return `${modelName}<${args.join(", ")}>`;
} else if ((<ModelStatementNode>model.node).templateParameters?.length > 0) {
// template
const params = (<ModelStatementNode>model.node).templateParameters.map((t) => t.sv);
const params = (<ModelStatementNode>model.node).templateParameters.map((t) => t.id.sv);
return `${model.name}<${params.join(", ")}>`;
} else {
// regular old model.
return model.name || "(anonymous model)";
return modelName;
}
}
@ -172,11 +181,65 @@ export function createChecker(program: Program) {
});
}
function checkTemplateApplication(node: TemplateApplicationNode): Type {
function checkTypeReference(node: TypeReferenceNode): Type {
const sym = resolveTypeReference(node);
if (sym.kind === "decorator") {
throwDiagnostic("Can't put a decorator in a type", node);
}
const symbolLinks = getSymbolLinks(sym);
const args = node.arguments.map(getTypeForNode);
const targetType = getTypeForNode(node.target);
// todo: check proper target
return instantiateTemplate(<ModelStatementNode>targetType.node, args);
if (sym.node.kind === SyntaxKind.ModelStatement && !sym.node.assignment) {
// model statement, possibly templated
if (sym.node.templateParameters.length === 0) {
if (args.length > 0) {
throwDiagnostic("Can't pass template arguments to model that is not templated", node);
}
if (symbolLinks.declaredType) {
return symbolLinks.declaredType;
}
return checkModelStatement(sym.node);
} else {
// model is templated, lets instantiate.
if (!symbolLinks.declaredType) {
// we haven't checked the declared type yet, so do so.
checkModelStatement(sym.node);
}
if (sym.node.templateParameters!.length > node.arguments.length) {
throwDiagnostic("Too few template arguments provided.", node);
}
if (sym.node.templateParameters!.length < node.arguments.length) {
throwDiagnostic("Too many template arguments provided.", node);
}
return instantiateTemplate(sym.node, args);
}
}
// some other kind of reference
if (args.length > 0) {
throwDiagnostic("Can't pass template arguments to non-templated type", node);
}
if (sym.node.kind === SyntaxKind.TemplateParameterDeclaration) {
const type = checkTemplateParameterDeclaration(sym.node);
// TODO: could cache this probably.
return type;
}
// types for non-templated types
if (symbolLinks.type) {
return symbolLinks.type;
}
const type = getTypeForNode(sym.node);
symbolLinks.type = type;
return type;
}
/**
@ -185,15 +248,14 @@ export function createChecker(program: Program) {
* parameters to access type type arguments.
*
* This will fall over if the same template is ever being instantiated
* twice at the same time.
* twice at the same time, or if template parameters from more than one template
* are ever in scope at once.
*/
function instantiateTemplate(templateNode: ModelStatementNode, args: Array<Type>): ModelType {
if (templateNode.templateParameters!.length < args.length) {
throwDiagnostic("Too few template arguments provided.", templateNode);
}
if (templateNode.templateParameters!.length > args.length) {
throwDiagnostic("Too many template arguments provided.", templateNode);
const symbolLinks = getSymbolLinks(templateNode.symbol!);
const cached = symbolLinks.instantiations!.get(args) as ModelType;
if (cached) {
return cached;
}
const oldTis = templateInstantiation;
@ -202,6 +264,9 @@ export function createChecker(program: Program) {
instantiatingTemplate = templateNode;
// this cast is invalid once we support templatized `model =`.
const type = <ModelType>getTypeForNode(templateNode);
symbolLinks.instantiations!.set(args, type);
type.templateNode = templateNode;
templateInstantiation = oldTis;
instantiatingTemplate = oldTemplate;
@ -242,13 +307,10 @@ export function createChecker(program: Program) {
);
}
const newPropType = createType(
{
...prop,
sourceProperty: prop,
},
true
);
const newPropType = createType({
...prop,
sourceProperty: prop,
});
properties.set(prop.name, newPropType);
}
@ -274,28 +336,56 @@ export function createChecker(program: Program) {
}
function checkNamespace(node: NamespaceStatementNode) {
const type: Namespace = createType({
const type: NamespaceType = createType({
kind: "Namespace",
name: node.id.sv,
namespace: getParentNamespaceType(node),
node: node,
properties: new Map(),
parameters: node.parameters ? <ModelType>getTypeForNode(node.parameters) : undefined,
models: new Map(),
operations: new Map(),
namespaces: new Map(),
});
for (const prop of node.properties) {
type.properties.set(prop.id.sv, checkNamespaceProperty(prop));
const links = getSymbolLinks(node.symbol!);
links.type = type;
for (const statement of node.statements.map(getTypeForNode)) {
switch (statement.kind) {
case "Model":
type.models.set(statement.name, statement as ModelType);
break;
case "Operation":
type.operations.set(statement.name, statement as OperationType);
break;
case "Namespace":
type.namespaces.set(statement.name, statement as NamespaceType);
break;
}
}
return type;
}
function checkNamespaceProperty(prop: NamespacePropertyNode): NamespaceProperty {
function getParentNamespaceType(
node: ModelStatementNode | NamespaceStatementNode | OperationStatementNode
): NamespaceType | undefined {
if (!node.namespaceSymbol) return undefined;
const symbolLinks = getSymbolLinks(node.namespaceSymbol);
if (!symbolLinks.type) {
throw new Error("Parent namespace isn't typed yet, please file a bug.");
}
return symbolLinks.type as NamespaceType;
}
function checkOperation(node: OperationStatementNode): OperationType {
return createType({
kind: "NamespaceProperty",
name: prop.id.sv,
node: prop,
parameters: <ModelType>getTypeForNode(prop.parameters),
returnType: getTypeForNode(prop.returnType),
kind: "Operation",
name: node.id.sv,
namespace: getParentNamespaceType(node),
node: node,
parameters: <ModelType>getTypeForNode(node.parameters),
returnType: getTypeForNode(node.returnType),
});
}
@ -307,13 +397,29 @@ export function createChecker(program: Program) {
});
}
function checkIdentifier(node: IdentifierNode) {
const binding = resolveIdentifier(node);
if (binding.kind === "decorator") {
return {};
} else {
return getTypeForNode(binding.node);
function getSymbolLinks(s: TypeSymbol): SymbolLinks {
const id = getSymbolId(s);
if (symbolLinks.has(id)) {
return symbolLinks.get(id)!;
}
const links = {};
symbolLinks.set(id, links);
return links;
}
function getSymbolId(s: TypeSymbol) {
if (s.id === undefined) {
s.id = currentSymbolId++;
}
return s.id;
}
function resolveIdentifierInScope(node: IdentifierNode, scope: { locals?: SymbolTable }) {
return (<any>scope).locals.get(node.sv);
}
function resolveIdentifier(node: IdentifierNode) {
@ -322,7 +428,7 @@ export function createChecker(program: Program) {
while (scope) {
if ("locals" in scope) {
binding = (<any>scope).locals.get(node.sv);
binding = resolveIdentifierInScope(node, scope);
if (binding) break;
}
scope = scope.parent;
@ -339,6 +445,36 @@ export function createChecker(program: Program) {
return binding;
}
function resolveTypeReference(node: ReferenceExpression): DecoratorSymbol | TypeSymbol {
if (node.kind === SyntaxKind.TypeReference) {
return resolveTypeReference(node.target);
}
if (node.kind === SyntaxKind.MemberExpression) {
const base = resolveTypeReference(node.base);
if (base.kind === "type" && base.node.kind === SyntaxKind.NamespaceStatement) {
const symbol = resolveIdentifierInScope(node.id, base.node);
if (!symbol) {
throwDiagnostic(`Namespace doesn't have member ${node.id.sv}`, node);
}
return symbol;
} else if (base.kind === "decorator") {
throwDiagnostic(`Cannot resolve '${node.id.sv}' in decorator`, node);
} else {
throwDiagnostic(
`Cannot resolve '${node.id.sv}' in non-namespace node ${base.node.kind}`,
node
);
}
}
if (node.kind === SyntaxKind.Identifier) {
return resolveIdentifier(node);
}
throw new Error("Unknown type reference kind");
}
function checkStringLiteral(str: StringLiteralNode): StringLiteralType {
return getLiteralType(str);
}
@ -364,65 +500,113 @@ export function createChecker(program: Program) {
}
function checkModel(node: ModelExpressionNode | ModelStatementNode) {
if (node.properties) {
const properties = new Map();
const baseModels =
node.kind === SyntaxKind.ModelExpression ? [] : checkClassHeritage(node.heritage);
for (const prop of node.properties) {
if ("id" in prop) {
const propType = <ModelTypeProperty>getTypeForNode(prop);
properties.set(propType.name, propType);
} else {
// spread property
const newProperties = checkSpreadProperty(prop.target);
for (const newProp of newProperties) {
if (properties.has(newProp.name)) {
throwDiagnostic(`Model already has a property named ${newProp.name}`, node);
}
properties.set(newProp.name, newProp);
}
}
if (node.kind === SyntaxKind.ModelStatement) {
if (node.properties) {
return checkModelStatement(node);
} else {
return checkModelEquals(node);
}
return createType({
kind: "Model",
name: node.kind === SyntaxKind.ModelStatement ? node.id.sv : "",
node: node,
properties,
baseModels: baseModels,
});
} else {
// model =
// this will likely have to change, as right now `model =` is really just
// alias and so disappears. That means you can't easily rename symbols.
const assignmentType = getTypeForNode((<ModelStatementNode>node).assignment!);
if (assignmentType.kind === "Model") {
const type: ModelType = createType({
...(<ModelType>assignmentType),
node: node,
name: (<ModelStatementNode>node).id.sv,
assignmentType,
});
return type;
}
return assignmentType;
return checkModelExpression(node);
}
}
function checkClassHeritage(heritage: ReferenceExpression[]): ModelType[] {
return heritage.flatMap((heritageRef) => {
const heritageType = getTypeForNode(heritageRef);
function checkModelStatement(node: ModelStatementNode) {
const links = getSymbolLinks(node.symbol!);
const instantiatingThisTemplate = instantiatingTemplate === node;
if (heritageType.kind === "TemplateParameter") {
// don't need to track heritage for template parameters.
return [];
if (links.declaredType && !instantiatingThisTemplate) {
// we're not instantiating this model and we've already checked it
return links.declaredType;
}
const baseModels = checkClassHeritage(node.heritage);
const properties = checkModelProperties(node);
const type: ModelType = {
kind: "Model",
name: node.id.sv,
node: node,
properties,
baseModels: baseModels,
namespace: getParentNamespaceType(node),
};
if (
(instantiatingThisTemplate &&
templateInstantiation.every((t) => t.kind !== "TemplateParameter")) ||
node.templateParameters.length === 0
) {
createType(type);
}
if (!instantiatingThisTemplate) {
links.declaredType = type;
links.instantiations = new MultiKeyMap();
}
return type;
}
function checkModelExpression(node: ModelExpressionNode) {
const properties = checkModelProperties(node);
const type: ModelType = createType({
kind: "Model",
name: "",
node: node,
properties,
baseModels: [],
});
return type;
}
function checkModelProperties(node: ModelExpressionNode | ModelStatementNode) {
const properties = new Map();
for (const prop of node.properties!) {
if ("id" in prop) {
const propType = <ModelTypeProperty>getTypeForNode(prop);
properties.set(propType.name, propType);
} else {
// spread property
const newProperties = checkSpreadProperty(prop.target);
for (const newProp of newProperties) {
if (properties.has(newProp.name)) {
throwDiagnostic(`Model already has a property named ${newProp.name}`, node);
}
properties.set(newProp.name, newProp);
}
}
}
return properties;
}
function checkModelEquals(node: ModelStatementNode) {
// model =
// this will likely have to change, as right now `model =` is really just
// alias and so disappears. That means you can't easily rename symbols.
const assignmentType = getTypeForNode((<ModelStatementNode>node).assignment!);
if (assignmentType.kind === "Model") {
const type: ModelType = createType({
...(<ModelType>assignmentType),
node: node,
name: (<ModelStatementNode>node).id.sv,
assignmentType,
namespace: getParentNamespaceType(node),
});
return type;
}
return assignmentType;
}
function checkClassHeritage(heritage: ReferenceExpression[]): ModelType[] {
return heritage.map((heritageRef) => {
const heritageType = getTypeForNode(heritageRef);
if (heritageType.kind !== "Model") {
throwDiagnostic("Models must extend other models.", heritageRef);
@ -443,13 +627,10 @@ export function createChecker(program: Program) {
// copy each property
for (const prop of walkPropertiesInherited(targetType)) {
const newProp = createType(
{
...prop,
sourceProperty: prop,
},
true
);
const newProp = createType({
...prop,
sourceProperty: prop,
});
props.push(newProp);
}
}
@ -490,18 +671,12 @@ export function createChecker(program: Program) {
});
}
}
// the types here aren't ideal and could probably be refactored.
function createType<T extends Type>(typeDef: T, skipCache = false): T {
if (!skipCache) {
(<any>typeDef).seq = program.typeCache.set([typeDef.node, ...templateInstantiation], typeDef);
}
function createType<T extends Type>(typeDef: T): T {
(<any>typeDef).templateArguments = templateInstantiation;
// only run decorators on fully instantiated types.
if (templateInstantiation.every((i) => i.kind !== "TemplateParameter")) {
program.executeDecorators(typeDef);
}
program.executeDecorators(typeDef);
return typeDef;
}

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

@ -38,7 +38,9 @@ export function parse(code: string | Types.SourceFile) {
case Token.ModelKeyword:
return parseModelStatement(decorators);
case Token.NamespaceKeyword:
return parseInterfaceStatement(decorators);
return parseNamespaceStatement(decorators);
case Token.OpKeyword:
return parseOperationStatement(decorators);
case Token.Semicolon:
if (decorators.length > 0) {
error("Cannot decorate an empty statement");
@ -63,7 +65,7 @@ export function parse(code: string | Types.SourceFile) {
return decorators;
}
function parseInterfaceStatement(
function parseNamespaceStatement(
decorators: Array<Types.DecoratorExpressionNode>
): Types.NamespaceStatementNode {
const pos = tokenPos();
@ -87,15 +89,11 @@ export function parse(code: string | Types.SourceFile) {
}
parseExpected(Token.OpenBrace);
const properties: Array<Types.NamespacePropertyNode> = [];
do {
if (token() == Token.CloseBrace) {
break;
}
const memberDecorators = parseDecoratorList();
properties.push(parseNamespaceProperty(memberDecorators));
} while (parseOptional(Token.Comma) || parseOptional(Token.Semicolon));
const statements: Array<Types.Statement> = [];
while (token() !== Token.CloseBrace) {
statements.push(parseStatement());
}
parseExpected(Token.CloseBrace);
@ -105,15 +103,15 @@ export function parse(code: string | Types.SourceFile) {
decorators,
id,
parameters,
properties,
statements,
},
pos
);
}
function parseNamespaceProperty(
function parseOperationStatement(
decorators: Array<Types.DecoratorExpressionNode>
): Types.NamespacePropertyNode {
): Types.OperationStatementNode {
const pos = tokenPos();
parseExpected(Token.OpKeyword);
const id = parseIdentifier();
@ -137,9 +135,10 @@ export function parse(code: string | Types.SourceFile) {
parseExpected(Token.Colon);
const returnType = parseExpression();
parseExpected(Token.Semicolon);
return finishNode(
{
kind: Types.SyntaxKind.NamespaceProperty,
kind: Types.SyntaxKind.OperationStatement,
id,
parameters,
returnType,
@ -218,7 +217,7 @@ export function parse(code: string | Types.SourceFile) {
const param = finishNode(
{
kind: Types.SyntaxKind.TemplateParameterDeclaration,
sv: id.sv,
id: id,
} as const,
pos
);
@ -379,20 +378,18 @@ export function parse(code: string | Types.SourceFile) {
function parseReferenceExpression(): Types.ReferenceExpression {
const pos = tokenPos();
const expr = parseIdentifierOrMemberExpression();
const target = parseIdentifierOrMemberExpression();
if (token() !== Token.LessThan) {
return expr;
let args: Types.Expression[] = [];
if (parseOptional(Token.LessThan)) {
args = parseExpressionList();
parseExpected(Token.GreaterThan);
}
parseExpected(Token.LessThan);
const args = parseExpressionList();
parseExpected(Token.GreaterThan);
return finishNode(
{
kind: Types.SyntaxKind.TemplateApplication,
target: expr,
kind: Types.SyntaxKind.TypeReference,
target,
arguments: args,
},
pos
@ -738,7 +735,7 @@ export function visitChildren<T>(node: Types.Node, cb: NodeCb<T>): T | undefined
return visitNode(cb, node.target) || visitEach(cb, node.arguments);
case Types.SyntaxKind.ImportStatement:
return visitNode(cb, node.id) || visitEach(cb, node.as);
case Types.SyntaxKind.NamespaceProperty:
case Types.SyntaxKind.OperationStatement:
return (
visitEach(cb, node.decorators) ||
visitNode(cb, node.id) ||
@ -747,10 +744,7 @@ export function visitChildren<T>(node: Types.Node, cb: NodeCb<T>): T | undefined
);
case Types.SyntaxKind.NamespaceStatement:
return (
visitEach(cb, node.decorators) ||
visitNode(cb, node.id) ||
visitNode(cb, node.parameters) ||
visitEach(cb, node.properties)
visitEach(cb, node.decorators) || visitNode(cb, node.id) || visitEach(cb, node.statements)
);
case Types.SyntaxKind.IntersectionExpression:
return visitEach(cb, node.options);
@ -773,7 +767,7 @@ export function visitChildren<T>(node: Types.Node, cb: NodeCb<T>): T | undefined
);
case Types.SyntaxKind.NamedImport:
return visitNode(cb, node.id);
case Types.SyntaxKind.TemplateApplication:
case Types.SyntaxKind.TypeReference:
return visitNode(cb, node.target) || visitEach(cb, node.arguments);
case Types.SyntaxKind.TupleExpression:
return visitEach(cb, node.values);

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

@ -11,7 +11,7 @@ import {
ADLScriptNode,
DecoratorExpressionNode,
IdentifierNode,
Namespace,
NamespaceType,
LiteralType,
ModelStatementNode,
ModelType,
@ -32,7 +32,7 @@ export interface Program {
checker?: ReturnType<typeof createChecker>;
evalAdlScript(adlScript: string, filePath?: string): void;
onBuild(cb: (program: Program) => void): void;
executeInterfaceDecorators(type: Namespace): void;
executeNamespaceDecorators(type: NamespaceType): void;
executeModelDecorators(type: ModelType): void;
executeDecorators(type: Type): void;
}
@ -41,7 +41,7 @@ export interface ADLSourceFile extends SourceFile {
ast: ADLScriptNode;
symbols: SymbolTable;
models: Array<ModelType>;
interfaces: Array<Namespace>;
interfaces: Array<NamespaceType>;
}
export async function compile(rootDir: string, options?: CompilerOptions) {
@ -54,7 +54,7 @@ export async function compile(rootDir: string, options?: CompilerOptions) {
typeCache: new MultiKeyMap(),
literalTypes: new Map(),
evalAdlScript,
executeInterfaceDecorators,
executeNamespaceDecorators,
executeModelDecorators,
executeDecorators,
onBuild(cb) {
@ -79,14 +79,22 @@ export async function compile(rootDir: string, options?: CompilerOptions) {
* does type checking.
*/
function executeInterfaceDecorators(type: Namespace) {
function executeNamespaceDecorators(type: NamespaceType) {
const stmt = type.node;
for (const dec of stmt.decorators) {
executeDecorator(dec, program, type);
}
for (const [name, propType] of type.properties) {
for (const [_, modelType] of type.models) {
executeModelDecorators(modelType);
}
for (const [_, namespaceType] of type.namespaces) {
executeNamespaceDecorators(namespaceType);
}
for (const [_, propType] of type.operations) {
for (const dec of propType.node.decorators) {
executeDecorator(dec, program, propType);
}

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

@ -1,4 +1,5 @@
import { SymbolTable } from "./binder";
import { MultiKeyMap } from "./checker";
/**
* Type System types
@ -13,8 +14,8 @@ export type Type =
| ModelType
| ModelTypeProperty
| TemplateParameterType
| Namespace
| NamespaceProperty
| NamespaceType
| OperationType
| StringLiteralType
| NumericLiteralType
| BooleanLiteralType
@ -25,6 +26,7 @@ export type Type =
export interface ModelType extends BaseType {
kind: "Model";
name: string;
namespace?: NamespaceType;
properties: Map<string, ModelTypeProperty>;
baseModels: Array<ModelType>;
templateArguments?: Array<Type>;
@ -43,20 +45,23 @@ export interface ModelTypeProperty {
optional: boolean;
}
export interface NamespaceProperty {
kind: "NamespaceProperty";
node: NamespacePropertyNode;
export interface OperationType {
kind: "Operation";
node: OperationStatementNode;
name: string;
namespace?: NamespaceType;
parameters?: ModelType;
returnType: Type;
}
export interface Namespace extends BaseType {
export interface NamespaceType extends BaseType {
kind: "Namespace";
name: string;
namespace?: NamespaceType;
node: NamespaceStatementNode;
properties: Map<string, NamespaceProperty>;
parameters?: ModelType;
models: Map<string, ModelType>;
operations: Map<string, OperationType>;
namespaces: Map<string, NamespaceType>;
}
export type LiteralType = StringLiteralType | NumericLiteralType | BooleanLiteralType;
@ -114,6 +119,16 @@ export interface TypeSymbol {
kind: "type";
node: Node;
name: string;
id?: number;
}
export interface SymbolLinks {
type?: Type;
// for types which can be instantiated, we split `type` into declaredType and
// a map of instantiations.
declaredType?: Type;
instantiations?: MultiKeyMap<Type>;
}
/**
@ -127,7 +142,7 @@ export enum SyntaxKind {
DecoratorExpression,
MemberExpression,
NamespaceStatement,
NamespaceProperty,
OperationStatement,
ModelStatement,
ModelExpression,
ModelProperty,
@ -139,7 +154,7 @@ export enum SyntaxKind {
StringLiteral,
NumericLiteral,
BooleanLiteral,
TemplateApplication,
TypeReference,
TemplateParameterDeclaration,
}
@ -152,7 +167,7 @@ export type Node =
| ADLScriptNode
| TemplateParameterDeclarationNode
| ModelPropertyNode
| NamespacePropertyNode
| OperationStatementNode
| NamedImportNode
| ModelPropertyNode
| ModelSpreadPropertyNode
@ -166,7 +181,24 @@ export interface ADLScriptNode extends BaseNode {
file: SourceFile;
}
export type Statement = ImportStatementNode | ModelStatementNode | NamespaceStatementNode;
export type Statement =
| ImportStatementNode
| ModelStatementNode
| NamespaceStatementNode
| OperationStatementNode;
export interface DeclarationNode {
symbol?: TypeSymbol; // tracks the symbol assigned to this declaration
namespaceSymbol?: TypeSymbol; // tracks the namespace this declaration is in
}
export type Declaration =
| ModelStatementNode
| NamespaceStatementNode
| OperationStatementNode
| TemplateParameterDeclarationNode;
export type ScopeNode = NamespaceStatementNode | ModelStatementNode;
export interface ImportStatementNode extends BaseNode {
kind: SyntaxKind.ImportStatement;
@ -197,13 +229,13 @@ export type Expression =
| TupleExpressionNode
| UnionExpressionNode
| IntersectionExpressionNode
| TemplateApplicationNode
| TypeReferenceNode
| IdentifierNode
| StringLiteralNode
| NumericLiteralNode
| BooleanLiteralNode;
export type ReferenceExpression = TemplateApplicationNode | MemberExpressionNode | IdentifierNode;
export type ReferenceExpression = TypeReferenceNode | MemberExpressionNode | IdentifierNode;
export interface MemberExpressionNode extends BaseNode {
kind: SyntaxKind.MemberExpression;
@ -211,23 +243,23 @@ export interface MemberExpressionNode extends BaseNode {
base: MemberExpressionNode | IdentifierNode;
}
export interface NamespaceStatementNode extends BaseNode {
export interface NamespaceStatementNode extends BaseNode, DeclarationNode {
kind: SyntaxKind.NamespaceStatement;
id: IdentifierNode;
parameters?: ModelExpressionNode;
properties: Array<NamespacePropertyNode>;
statements: Array<Statement>;
locals?: SymbolTable;
decorators: Array<DecoratorExpressionNode>;
}
export interface NamespacePropertyNode extends BaseNode {
kind: SyntaxKind.NamespaceProperty;
export interface OperationStatementNode extends BaseNode, DeclarationNode {
kind: SyntaxKind.OperationStatement;
id: IdentifierNode;
parameters: ModelExpressionNode;
returnType: Expression;
decorators: Array<DecoratorExpressionNode>;
}
export interface ModelStatementNode extends BaseNode {
export interface ModelStatementNode extends BaseNode, DeclarationNode {
kind: SyntaxKind.ModelStatement;
id: IdentifierNode;
properties?: Array<ModelPropertyNode | ModelSpreadPropertyNode>;
@ -293,15 +325,16 @@ export interface IntersectionExpressionNode extends BaseNode {
options: Array<Expression>;
}
export interface TemplateApplicationNode extends BaseNode {
kind: SyntaxKind.TemplateApplication;
target: Expression;
export interface TypeReferenceNode extends BaseNode {
kind: SyntaxKind.TypeReference;
target: ReferenceExpression;
arguments: Array<Expression>;
}
export interface TemplateParameterDeclarationNode extends BaseNode {
kind: SyntaxKind.TemplateParameterDeclaration;
sv: string;
id: IdentifierNode;
symbol?: TypeSymbol;
}
/**

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

@ -174,6 +174,7 @@ Statement :
ImportStatement
ModelStatement
NamespaceStatement
OperationStatement
`;`
ImportStatement :
@ -220,19 +221,10 @@ ModelSpreadProperty :
`...` ReferenceExpression
NamespaceStatement:
DecoratorList? `namespace` Identifier `{` NamespaceBody? `}`
DecoratorList? `namespace` IdentifierOrMemberExpression `{` StatementList? `}`
NamespaceBody :
NamespacePropertyList `,`?
NamespacePropertyList `;`?
NamespacePropertyList :
NamespaceProperty
NamespacePropertyList `,` NamespaceProperty
NamespacePropertyList `;` NamespaceProperty
NamespaceProperty :
DecoratorList? `op` Identifier `(` ModelPropertyList? `)` `:` Expression
OperationStatement :
DecoratorList? `op` Identifier `(` ModelPropertyList? `)` `:` Expression `;`
Expression :
UnionExpressionOrHigher

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

@ -160,6 +160,7 @@
&emsp;&emsp;&emsp;<a name="Statement-zi_5hwi0"></a>*[ImportStatement](#ImportStatement)*
&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-sg2sawim"></a>`` ; ``
&emsp;&emsp;<a name="ImportStatement"></a>*ImportStatement* **:**
@ -206,19 +207,10 @@
&emsp;&emsp;&emsp;<a name="ModelSpreadProperty-r9kkoml-"></a>`` ... ``&emsp;*[ReferenceExpression](#ReferenceExpression)*
&emsp;&emsp;<a name="NamespaceStatement"></a>*NamespaceStatement* **:**
&emsp;&emsp;&emsp;<a name="NamespaceStatement-lllfloam"></a>*[DecoratorList](#DecoratorList)*<sub>opt</sub>&emsp;`` namespace ``&emsp;*[Identifier](#Identifier)*&emsp;`` { ``&emsp;*[NamespaceBody](#NamespaceBody)*<sub>opt</sub>&emsp;`` } ``
&emsp;&emsp;&emsp;<a name="NamespaceStatement-lrsdvje0"></a>*[DecoratorList](#DecoratorList)*<sub>opt</sub>&emsp;`` namespace ``&emsp;*[IdentifierOrMemberExpression](#IdentifierOrMemberExpression)*&emsp;`` { ``&emsp;*[StatementList](#StatementList)*<sub>opt</sub>&emsp;`` } ``
&emsp;&emsp;<a name="NamespaceBody"></a>*NamespaceBody* **:**
&emsp;&emsp;&emsp;<a name="NamespaceBody-ihomezph"></a>*[NamespacePropertyList](#NamespacePropertyList)*&emsp;`` , ``<sub>opt</sub>
&emsp;&emsp;&emsp;<a name="NamespaceBody-srhr2gdj"></a>*[NamespacePropertyList](#NamespacePropertyList)*&emsp;`` ; ``<sub>opt</sub>
&emsp;&emsp;<a name="NamespacePropertyList"></a>*NamespacePropertyList* **:**
&emsp;&emsp;&emsp;<a name="NamespacePropertyList-feggpj5t"></a>*[NamespaceProperty](#NamespaceProperty)*
&emsp;&emsp;&emsp;<a name="NamespacePropertyList-8g_wmadx"></a>*[NamespacePropertyList](#NamespacePropertyList)*&emsp;`` , ``&emsp;*[NamespaceProperty](#NamespaceProperty)*
&emsp;&emsp;&emsp;<a name="NamespacePropertyList-isnpt3gn"></a>*[NamespacePropertyList](#NamespacePropertyList)*&emsp;`` ; ``&emsp;*[NamespaceProperty](#NamespaceProperty)*
&emsp;&emsp;<a name="NamespaceProperty"></a>*NamespaceProperty* **:**
&emsp;&emsp;&emsp;<a name="NamespaceProperty-vbdf9viv"></a>*[DecoratorList](#DecoratorList)*<sub>opt</sub>&emsp;`` op ``&emsp;*[Identifier](#Identifier)*&emsp;`` ( ``&emsp;*[ModelPropertyList](#ModelPropertyList)*<sub>opt</sub>&emsp;`` ) ``&emsp;`` : ``&emsp;*[Expression](#Expression)*
&emsp;&emsp;<a name="OperationStatement"></a>*OperationStatement* **:**
&emsp;&emsp;&emsp;<a name="OperationStatement-fic4qgph"></a>*[DecoratorList](#DecoratorList)*<sub>opt</sub>&emsp;`` op ``&emsp;*[Identifier](#Identifier)*&emsp;`` ( ``&emsp;*[ModelPropertyList](#ModelPropertyList)*<sub>opt</sub>&emsp;`` ) ``&emsp;`` : ``&emsp;*[Expression](#Expression)*&emsp;`` ; ``
&emsp;&emsp;<a name="Expression"></a>*Expression* **:**
&emsp;&emsp;&emsp;<a name="Expression-otzlm2nv"></a>*[UnionExpressionOrHigher](#UnionExpressionOrHigher)*

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

@ -1,5 +1,5 @@
import { Program } from "../compiler/program";
import { ModelTypeProperty, Namespace, Type } from "../compiler/types";
import { ModelTypeProperty, NamespaceType, Type } from "../compiler/types";
const docs = new Map<Type, string>();
@ -178,7 +178,7 @@ function mapFilterOut(
const listProperties = new Set<Type>();
export function list(program: Program, target: Type) {
if (target.kind === "NamespaceProperty" || target.kind === "ModelProperty") {
if (target.kind === "Operation" || target.kind === "ModelProperty") {
listProperties.add(target);
} else {
throw new Error("The @list decorator can only be applied to interface or model properties.");
@ -195,7 +195,7 @@ const tagProperties = new Map<Type, string[]>();
// Set a tag on an operation or namespace. There can be multiple tags on either an
// operation or namespace.
export function tag(program: Program, target: Type, tag: string) {
if (target.kind === "NamespaceProperty" || target.kind === "Namespace") {
if (target.kind === "Operation" || target.kind === "Namespace") {
const tags = tagProperties.get(target);
if (tags) {
tags.push(tag);
@ -215,7 +215,7 @@ export function getTags(target: Type): string[] {
// Merge the tags for a operation with the tags that are on the namespace it resides within.
//
// TODO: (JC) We'll need to update this for nested namespaces
export function getAllTags(namespace: Namespace, target: Type): string[] | undefined {
export function getAllTags(namespace: NamespaceType, target: Type): string[] | undefined {
const tags = new Set<string>();
for (const t of getTags(namespace)) {

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

@ -75,7 +75,7 @@ interface OperationRoute {
const operationRoutes = new Map<Type, OperationRoute>();
function setOperationRoute(entity: Type, verb: OperationRoute) {
if (entity.kind === "NamespaceProperty") {
if (entity.kind === "Operation") {
if (!operationRoutes.has(entity)) {
operationRoutes.set(entity, verb);
} else {

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

@ -110,7 +110,7 @@ describe("syntax", () => {
});
describe("tuple model expressions", () => {
parseEach(['namespace A { op b(param: [number, string]): [1, "hi"] }']);
parseEach(['namespace A { op b(param: [number, string]): [1, "hi"]; }']);
});
describe("array expressions", () => {
@ -136,13 +136,12 @@ describe("syntax", () => {
describe("namespace statements", () => {
parseEach([
"namespace Store {}",
"namespace Store { op read(): int32 }",
"namespace Store { op read(): int32, op write(v: int32): {} }",
"namespace Store { op read(): int32; op write(v: int32): {} }",
"@foo namespace Store { @dec op read():number, @dec op write(n: number): {} }",
"namespace Store { op read(): int32; }",
"namespace Store { op read(): int32; op write(v: int32): {}; }",
"namespace Store { op read(): int32; op write(v: int32): {}; }",
"@foo namespace Store { @dec op read(): number; @dec op write(n: number): {}; }",
"@foo @bar namespace Store { @foo @bar op read(): number; }",
"namespace Store(apiKey: string, otherArg: number) { }",
"namespace Store(... apiKeys, x: string) { op foo(... A, b: string, ...C, d: number): void }",
"namespace Store { namespace Read { op read(): int32; } namespace Write { op write(v: int32): {}; } }",
]);
});
@ -203,7 +202,7 @@ function dumpAST(astNode: any) {
const replacer = function (this: any, key: string, value: any) {
return key == "kind" ? SyntaxKind[value] : value;
};
console.log(JSON.stringify(astNode, replacer, 4));
//console.log(JSON.stringify(astNode, replacer, 4));
}
function shorten(code: string) {