From d617fe300066d200aa8b54f1e466b23fd91351e5 Mon Sep 17 00:00:00 2001 From: Brian Terlson Date: Wed, 3 Mar 2021 12:35:51 -0800 Subject: [PATCH 1/7] Remove ast-node type caching, fix bugs, refactor. --- packages/adl/compiler/binder.ts | 33 ++-- packages/adl/compiler/checker.ts | 316 +++++++++++++++++++++---------- packages/adl/compiler/parser.ts | 29 ++- packages/adl/compiler/types.ts | 41 +++- 4 files changed, 279 insertions(+), 140 deletions(-) diff --git a/packages/adl/compiler/binder.ts b/packages/adl/compiler/binder.ts index 10be14a60..e8a7d5862 100644 --- a/packages/adl/compiler/binder.ts +++ b/packages/adl/compiler/binder.ts @@ -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,7 @@ import { TemplateParameterDeclarationNode, SourceLocation, Sym, + Declaration, } from "./types.js"; export class SymbolTable extends Map { @@ -83,7 +83,7 @@ export function createBinder(): Binder { bindModelStatement(node); break; case SyntaxKind.NamespaceStatement: - bindInterfaceStatement(node); + bindNamespaceStatement(node); break; case SyntaxKind.TemplateParameterDeclaration: bindTemplateParameterDeclaration(node); @@ -115,22 +115,13 @@ export function createBinder(): Binder { } function bindModelStatement(node: ModelStatementNode) { - currentFile.symbols.set(node.id.sv, { - kind: "type", - node: node, - name: node.id.sv, - }); - + declareSymbol(currentFile.symbols, node); // 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(currentFile.symbols, statement); } function reportDuplicateSymbols(globalSymbols: SymbolTable) { @@ -175,3 +166,17 @@ function hasScope(node: Node) { return false; } } + +function createTypeSymbol(node: Node, name: string): TypeSymbol { + return { + kind: "type", + node, + name, + }; +} + +function declareSymbol(table: SymbolTable, node: Declaration) { + const symbol = createTypeSymbol(node, node.id.sv); + node.symbol = symbol; + table.set(node.id.sv, symbol); +} diff --git a/packages/adl/compiler/checker.ts b/packages/adl/compiler/checker.ts index 14da48f46..ef6bd9f15 100644 --- a/packages/adl/compiler/checker.ts +++ b/packages/adl/compiler/checker.ts @@ -23,7 +23,7 @@ import { StringLiteralNode, StringLiteralType, SyntaxKind, - TemplateApplicationNode, + TypeReferenceNode, TemplateParameterDeclarationNode, TupleExpressionNode, TupleType, @@ -31,6 +31,9 @@ import { UnionExpressionNode, UnionType, ReferenceExpression, + DecoratorSymbol, + TypeSymbol, + SymbolLinks, } from "./types.js"; /** @@ -72,6 +75,8 @@ export class MultiKeyMap { export function createChecker(program: Program) { let templateInstantiation: Array = []; let instantiatingTemplate: Node | undefined; + let currentSymbolId = 0; + const symbolLinks = new Map(); const seq = 0; @@ -84,9 +89,6 @@ export function createChecker(program: Program) { }; 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); @@ -98,9 +100,6 @@ export function createChecker(program: Program) { return checkNamespace(node); case SyntaxKind.NamespaceProperty: return checkNamespaceProperty(node); - case SyntaxKind.Identifier: - // decorator bindings presently return an empty binding - return checkIdentifier(node); case SyntaxKind.NumericLiteral: return checkNumericLiteral(node); case SyntaxKind.BooleanLiteral: @@ -115,8 +114,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); } @@ -172,11 +171,66 @@ export function createChecker(program: Program) { }); } - function checkTemplateApplication(node: TemplateApplicationNode): Type { + function checkTypeReference(node: TypeReferenceNode): Type { + // todo: support member expressions + const sym = resolveTypeReference(node.target as IdentifierNode); + 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(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 +239,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): 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 +255,9 @@ export function createChecker(program: Program) { instantiatingTemplate = templateNode; // this cast is invalid once we support templatized `model =`. const type = getTypeForNode(templateNode); + + symbolLinks.instantiations!.set(args, type); + type.templateNode = templateNode; templateInstantiation = oldTis; instantiatingTemplate = oldTemplate; @@ -242,13 +298,10 @@ export function createChecker(program: Program) { ); } - const newPropType = createType( - { - ...prop, - sourceProperty: prop, - }, - true - ); + const newPropType = createType({ + ...prop, + sourceProperty: prop, + }); properties.set(prop.name, newPropType); } @@ -286,6 +339,9 @@ export function createChecker(program: Program) { type.properties.set(prop.id.sv, checkNamespaceProperty(prop)); } + const links = getSymbolLinks(node.symbol!); + links.type = type; + return type; } @@ -307,16 +363,28 @@ 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 resolveIdentifier(node: IdentifierNode) { + function getSymbolId(s: TypeSymbol) { + if (s.id === undefined) { + s.id = currentSymbolId++; + } + + return s.id; + } + + function resolveIdentifier(node: IdentifierNode): DecoratorSymbol | TypeSymbol { let scope: Node | undefined = node.parent; let binding; @@ -339,6 +407,11 @@ export function createChecker(program: Program) { return binding; } + function resolveTypeReference(node: IdentifierNode): DecoratorSymbol | TypeSymbol { + // TODO: Support for member expressions + return resolveIdentifier(node); + } + function checkStringLiteral(str: StringLiteralNode): StringLiteralType { return getLiteralType(str); } @@ -364,65 +437,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 = 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((node).assignment!); - - if (assignmentType.kind === "Model") { - const type: ModelType = createType({ - ...(assignmentType), - node: node, - name: (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, + }; + + if ( + instantiatingThisTemplate && + templateInstantiation.every((t) => t.kind !== "TemplateParameter") + ) { + 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 = 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((node).assignment!); + + if (assignmentType.kind === "Model") { + const type: ModelType = createType({ + ...(assignmentType), + node: node, + name: (node).id.sv, + assignmentType, + }); + + return type; + } + + const links = getSymbolLinks(node.symbol!); + links.type = assignmentType; + + 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 +564,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 +608,12 @@ export function createChecker(program: Program) { }); } } + // the types here aren't ideal and could probably be refactored. - function createType(typeDef: T, skipCache = false): T { - if (!skipCache) { - (typeDef).seq = program.typeCache.set([typeDef.node, ...templateInstantiation], typeDef); - } + function createType(typeDef: T): T { (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; } diff --git a/packages/adl/compiler/parser.ts b/packages/adl/compiler/parser.ts index e0bd0013c..6be79f9b9 100644 --- a/packages/adl/compiler/parser.ts +++ b/packages/adl/compiler/parser.ts @@ -379,24 +379,20 @@ 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, - arguments: args, - }, - pos - ); + return finishNode({ + kind: Types.SyntaxKind.TypeReference, + target, + arguments: args, + }, pos); } function parseReferenceExpressionList(): Types.ReferenceExpression[] { @@ -773,8 +769,9 @@ export function visitChildren(node: Types.Node, cb: NodeCb): T | undefined ); case Types.SyntaxKind.NamedImport: return visitNode(cb, node.id); - case Types.SyntaxKind.TemplateApplication: - return visitNode(cb, node.target) || visitEach(cb, node.arguments); + case Types.SyntaxKind.TypeReference: + return visitNode(cb, node.target) || + visitEach(cb, node.arguments); case Types.SyntaxKind.TupleExpression: return visitEach(cb, node.values); case Types.SyntaxKind.UnionExpression: diff --git a/packages/adl/compiler/types.ts b/packages/adl/compiler/types.ts index eaa0f8c96..305f1ab66 100644 --- a/packages/adl/compiler/types.ts +++ b/packages/adl/compiler/types.ts @@ -1,4 +1,5 @@ import { SymbolTable } from "./binder"; +import { MultiKeyMap } from "./checker"; /** * Type System types @@ -114,8 +115,19 @@ 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; +} + + /** * AST types */ @@ -139,8 +151,8 @@ export enum SyntaxKind { StringLiteral, NumericLiteral, BooleanLiteral, - TemplateApplication, - TemplateParameterDeclaration, + TypeReference, + TemplateParameterDeclaration } export interface BaseNode extends TextRange { @@ -160,6 +172,7 @@ export type Node = | Statement | Expression; + export interface ADLScriptNode extends BaseNode { kind: SyntaxKind.ADLScript; statements: Array; @@ -168,6 +181,14 @@ export interface ADLScriptNode extends BaseNode { export type Statement = ImportStatementNode | ModelStatementNode | NamespaceStatementNode; +export interface DeclarationNode { + symbol?: TypeSymbol; // tracks the symbol assigned to this declaration +} + +export type Declaration = + | ModelStatementNode + | NamespaceStatementNode; + export interface ImportStatementNode extends BaseNode { kind: SyntaxKind.ImportStatement; id: IdentifierNode; @@ -197,13 +218,16 @@ 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,7 +235,7 @@ 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; @@ -227,7 +251,8 @@ export interface NamespacePropertyNode extends BaseNode { decorators: Array; } -export interface ModelStatementNode extends BaseNode { + +export interface ModelStatementNode extends BaseNode, DeclarationNode { kind: SyntaxKind.ModelStatement; id: IdentifierNode; properties?: Array; @@ -293,8 +318,8 @@ export interface IntersectionExpressionNode extends BaseNode { options: Array; } -export interface TemplateApplicationNode extends BaseNode { - kind: SyntaxKind.TemplateApplication; +export interface TypeReferenceNode extends BaseNode { + kind: SyntaxKind.TypeReference; target: Expression; arguments: Array; } From 55ac2208f5b157a4f65aa6605215cbc956a4a03c Mon Sep 17 00:00:00 2001 From: David Wilson Date: Wed, 10 Feb 2021 16:14:19 -0800 Subject: [PATCH 2/7] Implement nested namespace parsing, rename some core types --- packages/adl/compiler/binder.ts | 55 ++++++++---- packages/adl/compiler/checker.ts | 142 ++++++++++++++++++++++++++----- packages/adl/compiler/parser.ts | 36 ++++---- packages/adl/compiler/program.ts | 20 +++-- packages/adl/compiler/types.ts | 51 +++++++---- packages/adl/language.grammar | 16 +--- packages/adl/language.md | 16 +--- packages/adl/lib/decorators.ts | 8 +- packages/adl/lib/rest.ts | 2 +- packages/adl/test/test-parser.ts | 13 ++- 10 files changed, 244 insertions(+), 115 deletions(-) diff --git a/packages/adl/compiler/binder.ts b/packages/adl/compiler/binder.ts index e8a7d5862..fb14ce4d6 100644 --- a/packages/adl/compiler/binder.ts +++ b/packages/adl/compiler/binder.ts @@ -10,6 +10,8 @@ import { SourceLocation, Sym, Declaration, + OperationStatementNode, + ScopeNode, } from "./types.js"; export class SymbolTable extends Map { @@ -50,8 +52,7 @@ export function createBinder(): Binder { let parentNode: Node; // Node where locals go. - let scope: Node; - + let scope: ScopeNode; return { bindSourceFile, }; @@ -63,6 +64,7 @@ export function createBinder(): Binder { ) { currentFile = sourceFile; bindNode(sourceFile.ast); + reportDuplicateSymbols(currentFile.symbols); // everything is global if (globalScope) { @@ -80,13 +82,16 @@ export function createBinder(): Binder { switch (node.kind) { case SyntaxKind.ModelStatement: - bindModelStatement(node); + bindModelStatement(node); break; case SyntaxKind.NamespaceStatement: - bindNamespaceStatement(node); + bindNamespaceStatement(node); + break; + case SyntaxKind.OperationStatement: + bindOperationStatement(node); break; case SyntaxKind.TemplateParameterDeclaration: - bindTemplateParameterDeclaration(node); + bindTemplateParameterDeclaration(node); } const prevParent = parentNode; @@ -106,25 +111,33 @@ export function createBinder(): Binder { parentNode = prevParent; } + function getContainingSymbolTable() { + return scope ? scope.locals! : currentFile.symbols; + } + function bindTemplateParameterDeclaration(node: TemplateParameterDeclarationNode) { - (scope).locals!.set(node.sv, { - kind: "type", - node: node, - name: node.sv, - }); + declareSymbol(getContainingSymbolTable(), node); } function bindModelStatement(node: ModelStatementNode) { - declareSymbol(currentFile.symbols, node); - // initialize locals for type parameters. + declareSymbol(getContainingSymbolTable(), node); + + // Initialize locals for type parameters node.locals = new SymbolTable(); } function bindNamespaceStatement(statement: NamespaceStatementNode) { - declareSymbol(currentFile.symbols, statement); + declareSymbol(getContainingSymbolTable(), statement); + + // Initialize locals for namespace members + statement.locals = new SymbolTable(); } - function reportDuplicateSymbols(globalSymbols: SymbolTable) { + function bindOperationStatement(statement: OperationStatementNode) { + declareSymbol(getContainingSymbolTable(), statement); + } + + function reportDuplicateSymbols(symbols: SymbolTable) { let reported = new Set(); let messages = new Array(); @@ -132,10 +145,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 @@ -145,6 +165,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")); } @@ -158,10 +179,12 @@ 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; } diff --git a/packages/adl/compiler/checker.ts b/packages/adl/compiler/checker.ts index ef6bd9f15..7108c66f9 100644 --- a/packages/adl/compiler/checker.ts +++ b/packages/adl/compiler/checker.ts @@ -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, @@ -34,6 +35,8 @@ import { DecoratorSymbol, TypeSymbol, SymbolLinks, + MemberExpressionNode, + Sym, } from "./types.js"; /** @@ -85,7 +88,7 @@ export function createChecker(program: Program) { checkProgram, getLiteralType, getTypeName, - checkNamespaceProperty, + checkOperation, }; function getTypeForNode(node: Node): Type { @@ -98,8 +101,8 @@ export function createChecker(program: Program) { return checkModelProperty(node); case SyntaxKind.NamespaceStatement: return checkNamespace(node); - case SyntaxKind.NamespaceProperty: - return checkNamespaceProperty(node); + case SyntaxKind.OperationStatement: + return checkOperation(node); case SyntaxKind.NumericLiteral: return checkNumericLiteral(node); case SyntaxKind.BooleanLiteral: @@ -140,9 +143,16 @@ export function createChecker(program: Program) { return "(unnamed type)"; } + function getNamespaceString(type: ModelType | NamespaceType | OperationType): string | undefined { + return type.namespace + ? `${getNamespaceString(type.namespace) || ""}${type.namespace.name}.` + : undefined; + } + function getModelName(model: ModelType) { + let modelName: string | undefined = model.name; if ((model.node).assignment) { - return model.name; + // Just use the existing modelName } else if (model.templateArguments && model.templateArguments.length > 0) { // template instantiation const args = model.templateArguments.map(getTypeName); @@ -155,6 +165,8 @@ export function createChecker(program: Program) { // regular old model. return model.name || "(anonymous model)"; } + + return (getNamespaceString(model) || "") + modelName; } function checkTemplateParameterDeclaration(node: TemplateParameterDeclarationNode): Type { @@ -327,16 +339,28 @@ 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 ? 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)); + 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; + } } const links = getSymbolLinks(node.symbol!); @@ -345,13 +369,29 @@ export function createChecker(program: Program) { return type; } - function checkNamespaceProperty(prop: NamespacePropertyNode): NamespaceProperty { + function getParentNamespaceType( + node: ModelStatementNode | NamespaceStatementNode | OperationStatementNode + ) { + switch (node.kind) { + case SyntaxKind.ModelStatement: + case SyntaxKind.NamespaceStatement: + case SyntaxKind.OperationStatement: + return node.parent && node.parent.kind === SyntaxKind.NamespaceStatement + ? (getTypeForNode(node.parent) as NamespaceType) + : undefined; + default: + return undefined; + } + } + + function checkOperation(node: OperationStatementNode): OperationType { return createType({ - kind: "NamespaceProperty", - name: prop.id.sv, - node: prop, - parameters: getTypeForNode(prop.parameters), - returnType: getTypeForNode(prop.returnType), + kind: "Operation", + name: node.id.sv, + namespace: getParentNamespaceType(node), + node: node, + parameters: getTypeForNode(node.parameters), + returnType: getTypeForNode(node.returnType), }); } @@ -384,13 +424,75 @@ export function createChecker(program: Program) { return s.id; } - function resolveIdentifier(node: IdentifierNode): DecoratorSymbol | TypeSymbol { + function getMemberExpressionPath(node: MemberExpressionNode): string { + // Recursively build the rest of the path, back to front + const pathBefore = + node.base.kind === SyntaxKind.MemberExpression + ? getMemberExpressionPath(node.base) + : node.base.sv; + + return pathBefore + "." + node.id.sv; + } + + function checkMemberExpression(node: MemberExpressionNode) { + const binding = resolveMember(node); + if (binding) { + if (binding.kind === "decorator") { + return {}; + } else { + return getTypeForNode(binding.node); + } + } else { + throwDiagnostic(`Cannot resolve identifier '${getMemberExpressionPath(node)}'`, node); + } + } + + function resolveMember(node: MemberExpressionNode): any { + let result: Sym | undefined = undefined; + + // Navigate down the member expression and then resolve on the way + // back up because the 'base' pointers are stored in reverse + if (node.base.kind === SyntaxKind.MemberExpression) { + result = resolveMember(node.base); + } else { + // The last 'base' in the chain will be an identifier, so + // resolve it first and then resolve all remaining member + // expressions with respect to its scope + result = resolveIdentifier(node.base); + } + + if (result) { + // Previous segment was resolved, was it a namespace? + if (result.kind === "type") { + if (result.node.kind === SyntaxKind.NamespaceStatement) { + return resolveIdentifierInScope(node.id, result.node); + } else { + throwDiagnostic( + `Cannot resolve '${node.id.sv}' in non-namespace node ${result.node.kind}`, + node + ); + } + } else { + throwDiagnostic(`Unexpectedly resolved '${node.id.sv}' to a decorator symbol`, node); + } + } else { + // Let checkMemberExpression report on the inability to + // resolve the member expression + return undefined; + } + } + + function resolveIdentifierInScope(node: IdentifierNode, scope: { locals?: SymbolTable }) { + return (scope).locals.get(node.sv); + } + + function resolveIdentifier(node: IdentifierNode) { let scope: Node | undefined = node.parent; let binding; while (scope) { if ("locals" in scope) { - binding = (scope).locals.get(node.sv); + binding = resolveIdentifierInScope(node, scope); if (binding) break; } scope = scope.parent; diff --git a/packages/adl/compiler/parser.ts b/packages/adl/compiler/parser.ts index 6be79f9b9..3b8da5f3d 100644 --- a/packages/adl/compiler/parser.ts +++ b/packages/adl/compiler/parser.ts @@ -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.NamespaceStatementNode { const pos = tokenPos(); @@ -87,15 +89,11 @@ export function parse(code: string | Types.SourceFile) { } parseExpected(Token.OpenBrace); - const properties: Array = []; - do { - if (token() == Token.CloseBrace) { - break; - } - const memberDecorators = parseDecoratorList(); - properties.push(parseNamespaceProperty(memberDecorators)); - } while (parseOptional(Token.Comma) || parseOptional(Token.Semicolon)); + const statements: Array = []; + 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.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 ); @@ -734,7 +733,7 @@ export function visitChildren(node: Types.Node, cb: NodeCb): 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) || @@ -743,10 +742,7 @@ export function visitChildren(node: Types.Node, cb: NodeCb): 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); diff --git a/packages/adl/compiler/program.ts b/packages/adl/compiler/program.ts index a095389a2..4676b302c 100644 --- a/packages/adl/compiler/program.ts +++ b/packages/adl/compiler/program.ts @@ -11,7 +11,7 @@ import { ADLScriptNode, DecoratorExpressionNode, IdentifierNode, - Namespace, + NamespaceType, LiteralType, ModelStatementNode, ModelType, @@ -32,7 +32,7 @@ export interface Program { checker?: ReturnType; 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; - interfaces: Array; + interfaces: Array; } 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); } diff --git a/packages/adl/compiler/types.ts b/packages/adl/compiler/types.ts index 305f1ab66..5667ef661 100644 --- a/packages/adl/compiler/types.ts +++ b/packages/adl/compiler/types.ts @@ -14,8 +14,8 @@ export type Type = | ModelType | ModelTypeProperty | TemplateParameterType - | Namespace - | NamespaceProperty + | NamespaceType + | OperationType | StringLiteralType | NumericLiteralType | BooleanLiteralType @@ -26,6 +26,7 @@ export type Type = export interface ModelType extends BaseType { kind: "Model"; name: string; + namespace?: NamespaceType; properties: Map; baseModels: Array; templateArguments?: Array; @@ -44,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; - parameters?: ModelType; + models: Map; + operations: Map; + namespaces: Map; } export type LiteralType = StringLiteralType | NumericLiteralType | BooleanLiteralType; @@ -139,7 +143,7 @@ export enum SyntaxKind { DecoratorExpression, MemberExpression, NamespaceStatement, - NamespaceProperty, + OperationStatement, ModelStatement, ModelExpression, ModelProperty, @@ -164,7 +168,7 @@ export type Node = | ADLScriptNode | TemplateParameterDeclarationNode | ModelPropertyNode - | NamespacePropertyNode + | OperationStatementNode | NamedImportNode | ModelPropertyNode | ModelSpreadPropertyNode @@ -179,7 +183,11 @@ 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 @@ -187,7 +195,13 @@ export interface DeclarationNode { export type Declaration = | ModelStatementNode - | NamespaceStatementNode; + | NamespaceStatementNode + | OperationStatementNode + | TemplateParameterDeclarationNode; + +export type ScopeNode = + | NamespaceStatementNode + | ModelStatementNode export interface ImportStatementNode extends BaseNode { kind: SyntaxKind.ImportStatement; @@ -238,17 +252,19 @@ export interface MemberExpressionNode extends BaseNode { export interface NamespaceStatementNode extends BaseNode, DeclarationNode { kind: SyntaxKind.NamespaceStatement; id: IdentifierNode; - parameters?: ModelExpressionNode; - properties: Array; + statements: Array; + locals?: SymbolTable; decorators: Array; } -export interface NamespacePropertyNode extends BaseNode { - kind: SyntaxKind.NamespaceProperty; +export interface OperationStatementNode extends BaseNode { + kind: SyntaxKind.OperationStatement; id: IdentifierNode; parameters: ModelExpressionNode; returnType: Expression; decorators: Array; + + symbol: TypeSymbol; } @@ -326,7 +342,8 @@ export interface TypeReferenceNode extends BaseNode { export interface TemplateParameterDeclarationNode extends BaseNode { kind: SyntaxKind.TemplateParameterDeclaration; - sv: string; + id: IdentifierNode; + symbol: TypeSymbol; } /** diff --git a/packages/adl/language.grammar b/packages/adl/language.grammar index 83f462b4f..32d8f8662 100644 --- a/packages/adl/language.grammar +++ b/packages/adl/language.grammar @@ -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 diff --git a/packages/adl/language.md b/packages/adl/language.md index 8ff774fa9..b730a9a8b 100644 --- a/packages/adl/language.md +++ b/packages/adl/language.md @@ -160,6 +160,7 @@    *[ImportStatement](#ImportStatement)*    *[ModelStatement](#ModelStatement)*    *[NamespaceStatement](#NamespaceStatement)* +   *[OperationStatement](#OperationStatement)*    `` ; ``   *ImportStatement* **:** @@ -206,19 +207,10 @@    `` ... `` *[ReferenceExpression](#ReferenceExpression)*   *NamespaceStatement* **:** -   *[DecoratorList](#DecoratorList)*opt `` namespace `` *[Identifier](#Identifier)* `` { `` *[NamespaceBody](#NamespaceBody)*opt `` } `` +   *[DecoratorList](#DecoratorList)*opt `` namespace `` *[IdentifierOrMemberExpression](#IdentifierOrMemberExpression)* `` { `` *[StatementList](#StatementList)*opt `` } `` -  *NamespaceBody* **:** -   *[NamespacePropertyList](#NamespacePropertyList)* `` , ``opt -   *[NamespacePropertyList](#NamespacePropertyList)* `` ; ``opt - -  *NamespacePropertyList* **:** -   *[NamespaceProperty](#NamespaceProperty)* -   *[NamespacePropertyList](#NamespacePropertyList)* `` , `` *[NamespaceProperty](#NamespaceProperty)* -   *[NamespacePropertyList](#NamespacePropertyList)* `` ; `` *[NamespaceProperty](#NamespaceProperty)* - -  *NamespaceProperty* **:** -   *[DecoratorList](#DecoratorList)*opt `` op `` *[Identifier](#Identifier)* `` ( `` *[ModelPropertyList](#ModelPropertyList)*opt `` ) `` `` : `` *[Expression](#Expression)* +  *OperationStatement* **:** +   *[DecoratorList](#DecoratorList)*opt `` op `` *[Identifier](#Identifier)* `` ( `` *[ModelPropertyList](#ModelPropertyList)*opt `` ) `` `` : `` *[Expression](#Expression)* `` ; ``   *Expression* **:**    *[UnionExpressionOrHigher](#UnionExpressionOrHigher)* diff --git a/packages/adl/lib/decorators.ts b/packages/adl/lib/decorators.ts index 1aa6cf92c..eb8f56a4a 100644 --- a/packages/adl/lib/decorators.ts +++ b/packages/adl/lib/decorators.ts @@ -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(); @@ -178,7 +178,7 @@ function mapFilterOut( const listProperties = new Set(); 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(); // 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(); for (const t of getTags(namespace)) { diff --git a/packages/adl/lib/rest.ts b/packages/adl/lib/rest.ts index 230a1c11a..defc3c3a4 100644 --- a/packages/adl/lib/rest.ts +++ b/packages/adl/lib/rest.ts @@ -75,7 +75,7 @@ interface OperationRoute { const operationRoutes = new Map(); function setOperationRoute(entity: Type, verb: OperationRoute) { - if (entity.kind === "NamespaceProperty") { + if (entity.kind === "Operation") { if (!operationRoutes.has(entity)) { operationRoutes.set(entity, verb); } else { diff --git a/packages/adl/test/test-parser.ts b/packages/adl/test/test-parser.ts index 0c6bc37b7..7dcd68422 100644 --- a/packages/adl/test/test-parser.ts +++ b/packages/adl/test/test-parser.ts @@ -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): {}; } }", ]); }); From 47866d53d87b6e87ac760388e0e08a1f312b883e Mon Sep 17 00:00:00 2001 From: Brian Terlson Date: Thu, 4 Mar 2021 11:57:20 -0800 Subject: [PATCH 3/7] Finish namespace implementation, fix circularity --- packages/adl/compiler/binder.ts | 24 ++++-- packages/adl/compiler/checker.ts | 122 ++++++++++++------------------- packages/adl/compiler/types.ts | 9 +-- packages/adl/test/test-parser.ts | 2 +- 4 files changed, 69 insertions(+), 88 deletions(-) diff --git a/packages/adl/compiler/binder.ts b/packages/adl/compiler/binder.ts index fb14ce4d6..26a70217d 100644 --- a/packages/adl/compiler/binder.ts +++ b/packages/adl/compiler/binder.ts @@ -51,6 +51,8 @@ export function createBinder(): Binder { let currentFile: ADLSourceFile; let parentNode: Node; + let currentNamespace: NamespaceStatementNode | undefined; + // Node where locals go. let scope: ScopeNode; return { @@ -100,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); } @@ -137,6 +146,16 @@ export function createBinder(): Binder { 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(); let messages = new Array(); @@ -198,8 +217,3 @@ function createTypeSymbol(node: Node, name: string): TypeSymbol { }; } -function declareSymbol(table: SymbolTable, node: Declaration) { - const symbol = createTypeSymbol(node, node.id.sv); - node.symbol = symbol; - table.set(node.id.sv, symbol); -} diff --git a/packages/adl/compiler/checker.ts b/packages/adl/compiler/checker.ts index 7108c66f9..c229762ea 100644 --- a/packages/adl/compiler/checker.ts +++ b/packages/adl/compiler/checker.ts @@ -159,7 +159,7 @@ export function createChecker(program: Program) { return `${model.name}<${args.join(", ")}>`; } else if ((model.node).templateParameters?.length > 0) { // template - const params = (model.node).templateParameters.map((t) => t.sv); + const params = (model.node).templateParameters.map((t) => t.id.sv); return `${model.name}<${params.join(", ")}>`; } else { // regular old model. @@ -184,8 +184,7 @@ export function createChecker(program: Program) { } function checkTypeReference(node: TypeReferenceNode): Type { - // todo: support member expressions - const sym = resolveTypeReference(node.target as IdentifierNode); + const sym = resolveTypeReference(node); if (sym.kind === "decorator") { throwDiagnostic("Can't put a decorator in a type", node); } @@ -349,6 +348,9 @@ export function createChecker(program: Program) { namespaces: new Map(), }); + const links = getSymbolLinks(node.symbol!); + links.type = type; + for (const statement of node.statements.map(getTypeForNode)) { switch (statement.kind) { case "Model": @@ -363,25 +365,19 @@ export function createChecker(program: Program) { } } - const links = getSymbolLinks(node.symbol!); - links.type = type; - return type; } function getParentNamespaceType( node: ModelStatementNode | NamespaceStatementNode | OperationStatementNode - ) { - switch (node.kind) { - case SyntaxKind.ModelStatement: - case SyntaxKind.NamespaceStatement: - case SyntaxKind.OperationStatement: - return node.parent && node.parent.kind === SyntaxKind.NamespaceStatement - ? (getTypeForNode(node.parent) as NamespaceType) - : undefined; - default: - return undefined; + ): NamespaceType | undefined { + if (!node.namespaceSymbol) return undefined; + + const symbolLinks = getSymbolLinks(node.namespaceSymbol); + if (!symbolLinks.type) { + throwDiagnostic("Parent namespace isn't typed yet, please file a bug.", node); } + return symbolLinks.type as NamespaceType; } function checkOperation(node: OperationStatementNode): OperationType { @@ -424,64 +420,6 @@ export function createChecker(program: Program) { return s.id; } - function getMemberExpressionPath(node: MemberExpressionNode): string { - // Recursively build the rest of the path, back to front - const pathBefore = - node.base.kind === SyntaxKind.MemberExpression - ? getMemberExpressionPath(node.base) - : node.base.sv; - - return pathBefore + "." + node.id.sv; - } - - function checkMemberExpression(node: MemberExpressionNode) { - const binding = resolveMember(node); - if (binding) { - if (binding.kind === "decorator") { - return {}; - } else { - return getTypeForNode(binding.node); - } - } else { - throwDiagnostic(`Cannot resolve identifier '${getMemberExpressionPath(node)}'`, node); - } - } - - function resolveMember(node: MemberExpressionNode): any { - let result: Sym | undefined = undefined; - - // Navigate down the member expression and then resolve on the way - // back up because the 'base' pointers are stored in reverse - if (node.base.kind === SyntaxKind.MemberExpression) { - result = resolveMember(node.base); - } else { - // The last 'base' in the chain will be an identifier, so - // resolve it first and then resolve all remaining member - // expressions with respect to its scope - result = resolveIdentifier(node.base); - } - - if (result) { - // Previous segment was resolved, was it a namespace? - if (result.kind === "type") { - if (result.node.kind === SyntaxKind.NamespaceStatement) { - return resolveIdentifierInScope(node.id, result.node); - } else { - throwDiagnostic( - `Cannot resolve '${node.id.sv}' in non-namespace node ${result.node.kind}`, - node - ); - } - } else { - throwDiagnostic(`Unexpectedly resolved '${node.id.sv}' to a decorator symbol`, node); - } - } else { - // Let checkMemberExpression report on the inability to - // resolve the member expression - return undefined; - } - } - function resolveIdentifierInScope(node: IdentifierNode, scope: { locals?: SymbolTable }) { return (scope).locals.get(node.sv); } @@ -509,9 +447,39 @@ export function createChecker(program: Program) { return binding; } - function resolveTypeReference(node: IdentifierNode): DecoratorSymbol | TypeSymbol { - // TODO: Support for member expressions - return resolveIdentifier(node); + 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); + } + + throwDiagnostic("Unknown reference node type", node); } function checkStringLiteral(str: StringLiteralNode): StringLiteralType { diff --git a/packages/adl/compiler/types.ts b/packages/adl/compiler/types.ts index 5667ef661..f73028a61 100644 --- a/packages/adl/compiler/types.ts +++ b/packages/adl/compiler/types.ts @@ -191,6 +191,7 @@ export type Statement = export interface DeclarationNode { symbol?: TypeSymbol; // tracks the symbol assigned to this declaration + namespaceSymbol?: TypeSymbol; // tracks the namespace this declaration is in } export type Declaration = @@ -257,14 +258,12 @@ export interface NamespaceStatementNode extends BaseNode, DeclarationNode { decorators: Array; } -export interface OperationStatementNode extends BaseNode { +export interface OperationStatementNode extends BaseNode, DeclarationNode { kind: SyntaxKind.OperationStatement; id: IdentifierNode; parameters: ModelExpressionNode; returnType: Expression; decorators: Array; - - symbol: TypeSymbol; } @@ -336,14 +335,14 @@ export interface IntersectionExpressionNode extends BaseNode { export interface TypeReferenceNode extends BaseNode { kind: SyntaxKind.TypeReference; - target: Expression; + target: ReferenceExpression; arguments: Array; } export interface TemplateParameterDeclarationNode extends BaseNode { kind: SyntaxKind.TemplateParameterDeclaration; id: IdentifierNode; - symbol: TypeSymbol; + symbol?: TypeSymbol; } /** diff --git a/packages/adl/test/test-parser.ts b/packages/adl/test/test-parser.ts index 7dcd68422..d90968b2c 100644 --- a/packages/adl/test/test-parser.ts +++ b/packages/adl/test/test-parser.ts @@ -202,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) { From ca96f9cf3b53ef77631173abe9d79bc2cf28e0a6 Mon Sep 17 00:00:00 2001 From: Brian Terlson Date: Thu, 4 Mar 2021 12:29:24 -0800 Subject: [PATCH 4/7] It's ok to run decorators on non-templated models --- packages/adl/compiler/checker.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/adl/compiler/checker.ts b/packages/adl/compiler/checker.ts index c229762ea..12a36ba19 100644 --- a/packages/adl/compiler/checker.ts +++ b/packages/adl/compiler/checker.ts @@ -538,8 +538,9 @@ export function createChecker(program: Program) { }; if ( - instantiatingThisTemplate && - templateInstantiation.every((t) => t.kind !== "TemplateParameter") + (instantiatingThisTemplate && + templateInstantiation.every((t) => t.kind !== "TemplateParameter")) || + node.templateParameters.length === 0 ) { createType(type); } @@ -605,9 +606,6 @@ export function createChecker(program: Program) { return type; } - const links = getSymbolLinks(node.symbol!); - links.type = assignmentType; - return assignmentType; } From 3773cb1c046741a023c0f1f27a69f59ad888f782 Mon Sep 17 00:00:00 2001 From: Brian Terlson Date: Thu, 4 Mar 2021 13:44:04 -0800 Subject: [PATCH 5/7] Fix type name generation --- packages/adl/compiler/checker.ts | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/adl/compiler/checker.ts b/packages/adl/compiler/checker.ts index 12a36ba19..442022a23 100644 --- a/packages/adl/compiler/checker.ts +++ b/packages/adl/compiler/checker.ts @@ -143,30 +143,28 @@ export function createChecker(program: Program) { return "(unnamed type)"; } - function getNamespaceString(type: ModelType | NamespaceType | OperationType): string | undefined { - return type.namespace - ? `${getNamespaceString(type.namespace) || ""}${type.namespace.name}.` - : undefined; + 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) { - let modelName: string | undefined = model.name; - if ((model.node).assignment) { - // Just use the existing modelName - } 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 ((model.node).templateParameters?.length > 0) { // template const params = (model.node).templateParameters.map((t) => t.id.sv); return `${model.name}<${params.join(", ")}>`; } else { // regular old model. - return model.name || "(anonymous model)"; + return modelName; } - - return (getNamespaceString(model) || "") + modelName; } function checkTemplateParameterDeclaration(node: TemplateParameterDeclarationNode): Type { @@ -535,6 +533,7 @@ export function createChecker(program: Program) { node: node, properties, baseModels: baseModels, + namespace: getParentNamespaceType(node) }; if ( @@ -601,6 +600,7 @@ export function createChecker(program: Program) { node: node, name: (node).id.sv, assignmentType, + namespace: getParentNamespaceType(node) }); return type; From e993c2355cba527c94767aa89c2bfb44f60c4126 Mon Sep 17 00:00:00 2001 From: Brian Terlson Date: Thu, 4 Mar 2021 13:50:24 -0800 Subject: [PATCH 6/7] format --- packages/adl/compiler/binder.ts | 2 -- packages/adl/compiler/checker.ts | 21 ++++++++------------- packages/adl/compiler/parser.ts | 17 +++++++++-------- packages/adl/compiler/types.ts | 16 ++++------------ 4 files changed, 21 insertions(+), 35 deletions(-) diff --git a/packages/adl/compiler/binder.ts b/packages/adl/compiler/binder.ts index 26a70217d..aa8b57159 100644 --- a/packages/adl/compiler/binder.ts +++ b/packages/adl/compiler/binder.ts @@ -155,7 +155,6 @@ export function createBinder(): Binder { table.set(node.id.sv, symbol); } - function reportDuplicateSymbols(symbols: SymbolTable) { let reported = new Set(); let messages = new Array(); @@ -216,4 +215,3 @@ function createTypeSymbol(node: Node, name: string): TypeSymbol { name, }; } - diff --git a/packages/adl/compiler/checker.ts b/packages/adl/compiler/checker.ts index 442022a23..85728c5d6 100644 --- a/packages/adl/compiler/checker.ts +++ b/packages/adl/compiler/checker.ts @@ -144,15 +144,15 @@ export function createChecker(program: Program) { } function getNamespaceString(type: NamespaceType | undefined): string { - if (!type) return ''; + if (!type) return ""; const parent = type.namespace; return parent ? `${getNamespaceString(parent)}.${type.name}` : type.name; } function getModelName(model: ModelType) { - const nsName = getNamespaceString(model.namespace) - const modelName = (nsName ? nsName + '.' : '') + (model.name || "(anonymous model)"); + 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); @@ -455,16 +455,11 @@ export function createChecker(program: Program) { 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 - ) + 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 - ); + throwDiagnostic(`Cannot resolve '${node.id.sv}' in decorator`, node); } else { throwDiagnostic( `Cannot resolve '${node.id.sv}' in non-namespace node ${base.node.kind}`, @@ -533,12 +528,12 @@ export function createChecker(program: Program) { node: node, properties, baseModels: baseModels, - namespace: getParentNamespaceType(node) + namespace: getParentNamespaceType(node), }; if ( (instantiatingThisTemplate && - templateInstantiation.every((t) => t.kind !== "TemplateParameter")) || + templateInstantiation.every((t) => t.kind !== "TemplateParameter")) || node.templateParameters.length === 0 ) { createType(type); @@ -600,7 +595,7 @@ export function createChecker(program: Program) { node: node, name: (node).id.sv, assignmentType, - namespace: getParentNamespaceType(node) + namespace: getParentNamespaceType(node), }); return type; diff --git a/packages/adl/compiler/parser.ts b/packages/adl/compiler/parser.ts index 3b8da5f3d..00bbf2ac2 100644 --- a/packages/adl/compiler/parser.ts +++ b/packages/adl/compiler/parser.ts @@ -386,12 +386,14 @@ export function parse(code: string | Types.SourceFile) { parseExpected(Token.GreaterThan); } - - return finishNode({ - kind: Types.SyntaxKind.TypeReference, - target, - arguments: args, - }, pos); + return finishNode( + { + kind: Types.SyntaxKind.TypeReference, + target, + arguments: args, + }, + pos + ); } function parseReferenceExpressionList(): Types.ReferenceExpression[] { @@ -766,8 +768,7 @@ export function visitChildren(node: Types.Node, cb: NodeCb): T | undefined case Types.SyntaxKind.NamedImport: return visitNode(cb, node.id); case Types.SyntaxKind.TypeReference: - return visitNode(cb, node.target) || - visitEach(cb, node.arguments); + return visitNode(cb, node.target) || visitEach(cb, node.arguments); case Types.SyntaxKind.TupleExpression: return visitEach(cb, node.values); case Types.SyntaxKind.UnionExpression: diff --git a/packages/adl/compiler/types.ts b/packages/adl/compiler/types.ts index f73028a61..c21bfd121 100644 --- a/packages/adl/compiler/types.ts +++ b/packages/adl/compiler/types.ts @@ -128,10 +128,9 @@ export interface SymbolLinks { // for types which can be instantiated, we split `type` into declaredType and // a map of instantiations. declaredType?: Type; - instantiations?: MultiKeyMap; + instantiations?: MultiKeyMap; } - /** * AST types */ @@ -156,7 +155,7 @@ export enum SyntaxKind { NumericLiteral, BooleanLiteral, TypeReference, - TemplateParameterDeclaration + TemplateParameterDeclaration, } export interface BaseNode extends TextRange { @@ -176,7 +175,6 @@ export type Node = | Statement | Expression; - export interface ADLScriptNode extends BaseNode { kind: SyntaxKind.ADLScript; statements: Array; @@ -200,9 +198,7 @@ export type Declaration = | OperationStatementNode | TemplateParameterDeclarationNode; -export type ScopeNode = - | NamespaceStatementNode - | ModelStatementNode +export type ScopeNode = NamespaceStatementNode | ModelStatementNode; export interface ImportStatementNode extends BaseNode { kind: SyntaxKind.ImportStatement; @@ -239,10 +235,7 @@ export type Expression = | NumericLiteralNode | BooleanLiteralNode; -export type ReferenceExpression = - | TypeReferenceNode - | MemberExpressionNode - | IdentifierNode; +export type ReferenceExpression = TypeReferenceNode | MemberExpressionNode | IdentifierNode; export interface MemberExpressionNode extends BaseNode { kind: SyntaxKind.MemberExpression; @@ -266,7 +259,6 @@ export interface OperationStatementNode extends BaseNode, DeclarationNode { decorators: Array; } - export interface ModelStatementNode extends BaseNode, DeclarationNode { kind: SyntaxKind.ModelStatement; id: IdentifierNode; From f9a2d3ce7bbd96b2863f4f606fd808f5e3956658 Mon Sep 17 00:00:00 2001 From: Brian Terlson Date: Thu, 4 Mar 2021 15:36:51 -0800 Subject: [PATCH 7/7] move some throwDiag to regular exceptions --- packages/adl/compiler/checker.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/adl/compiler/checker.ts b/packages/adl/compiler/checker.ts index 85728c5d6..0c15a51aa 100644 --- a/packages/adl/compiler/checker.ts +++ b/packages/adl/compiler/checker.ts @@ -373,7 +373,7 @@ export function createChecker(program: Program) { const symbolLinks = getSymbolLinks(node.namespaceSymbol); if (!symbolLinks.type) { - throwDiagnostic("Parent namespace isn't typed yet, please file a bug.", node); + throw new Error("Parent namespace isn't typed yet, please file a bug."); } return symbolLinks.type as NamespaceType; } @@ -472,7 +472,7 @@ export function createChecker(program: Program) { return resolveIdentifier(node); } - throwDiagnostic("Unknown reference node type", node); + throw new Error("Unknown type reference kind"); } function checkStringLiteral(str: StringLiteralNode): StringLiteralType {