Add experimental support for referencing module elements
This commit is contained in:
parent
9dc62d0836
commit
3e169e9ab9
4 changed files with 209 additions and 61 deletions
163
src/checker.ts
163
src/checker.ts
|
@ -1,14 +1,21 @@
|
|||
import {
|
||||
CustomOperator,
|
||||
EnumDeclaration,
|
||||
Expression,
|
||||
ExprOperator,
|
||||
Identifier,
|
||||
IdentifierAlt,
|
||||
LetDeclaration,
|
||||
ModuleDeclaration,
|
||||
Pattern,
|
||||
ReferenceExpression,
|
||||
ReferenceTypeExpression,
|
||||
SourceFile,
|
||||
StructDeclaration,
|
||||
Symkind,
|
||||
Syntax,
|
||||
SyntaxKind,
|
||||
TypeExpression
|
||||
TypeExpression,
|
||||
} from "./cst";
|
||||
import {
|
||||
describeType,
|
||||
|
@ -18,6 +25,7 @@ import {
|
|||
FieldMissingDiagnostic,
|
||||
UnificationFailedDiagnostic,
|
||||
KindMismatchDiagnostic,
|
||||
ModuleNotFoundDiagnostic,
|
||||
} from "./diagnostics";
|
||||
import { assert, isEmpty, MultiMap } from "./util";
|
||||
import { Analyser } from "./analysis";
|
||||
|
@ -682,6 +690,13 @@ class Forall extends SchemeBase {
|
|||
export type Scheme
|
||||
= Forall
|
||||
|
||||
type NodeWithReference
|
||||
= Identifier
|
||||
| IdentifierAlt
|
||||
| ExprOperator
|
||||
| ReferenceExpression
|
||||
| ReferenceTypeExpression
|
||||
|
||||
export class TypeEnv {
|
||||
|
||||
private mapping = new MultiMap<string, [Symkind, Scheme]>();
|
||||
|
@ -694,16 +709,12 @@ export class TypeEnv {
|
|||
this.mapping.add(name, [kind, scheme]);
|
||||
}
|
||||
|
||||
public lookup(name: string, expectedKind: Symkind): Scheme | null {
|
||||
let curr: TypeEnv | null = this;
|
||||
do {
|
||||
for (const [kind, scheme] of curr.mapping.get(name)) {
|
||||
public get(name: string, expectedKind: Symkind): Scheme | null {
|
||||
for (const [kind, scheme] of this.mapping.get(name)) {
|
||||
if (kind & expectedKind) {
|
||||
return scheme;
|
||||
}
|
||||
}
|
||||
curr = curr.parent;
|
||||
} while(curr !== null);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -805,8 +816,64 @@ export class Checker {
|
|||
this.contexts.pop();
|
||||
}
|
||||
|
||||
private lookup(name: string, kind: Symkind): Scheme | null {
|
||||
return this.getContext().env.lookup(name, kind);
|
||||
private lookup(node: NodeWithReference, expectedKind: Symkind): Scheme | null {
|
||||
let modulePath: IdentifierAlt[];
|
||||
let name: Identifier | IdentifierAlt | ExprOperator;
|
||||
if (node.kind === SyntaxKind.ReferenceExpression || node.kind === SyntaxKind.ReferenceTypeExpression) {
|
||||
modulePath = node.modulePath.map(([name, _dot]) => name);
|
||||
name = node.name;
|
||||
} else {
|
||||
modulePath = [];
|
||||
name = node;
|
||||
}
|
||||
if (modulePath.length > 0) {
|
||||
let maxIndex = 0;
|
||||
let currUp = node.getEnclosingModule();
|
||||
outer: for (;;) {
|
||||
let currDown: SourceFile | ModuleDeclaration = currUp;
|
||||
for (let i = 0; i < modulePath.length; i++) {
|
||||
const moduleName = modulePath[i];
|
||||
const nextDown = currDown.resolveModule(moduleName.text);
|
||||
if (nextDown === null) {
|
||||
if (currUp.kind === SyntaxKind.SourceFile) {
|
||||
this.diagnostics.add(
|
||||
new ModuleNotFoundDiagnostic(
|
||||
modulePath.slice(maxIndex).map(id => id.text),
|
||||
modulePath[maxIndex],
|
||||
)
|
||||
);
|
||||
return null;
|
||||
}
|
||||
currUp = currUp.getEnclosingModule();
|
||||
continue outer;
|
||||
}
|
||||
maxIndex = Math.max(maxIndex, i+1);
|
||||
currDown = nextDown;
|
||||
}
|
||||
const found = currDown.typeEnv!.get(name.text, expectedKind);
|
||||
if (found !== null) {
|
||||
return found;
|
||||
}
|
||||
this.diagnostics.add(
|
||||
new BindingNotFoudDiagnostic(
|
||||
modulePath.map(id => id.text),
|
||||
name.text,
|
||||
name,
|
||||
)
|
||||
);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
let curr: TypeEnv | null = this.getContext().env;
|
||||
do {
|
||||
const found = curr.get(name.text, expectedKind);
|
||||
if (found !== null) {
|
||||
return found;
|
||||
}
|
||||
curr = curr.parent;
|
||||
} while(curr !== null);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private getReturnType(): Type {
|
||||
|
@ -834,8 +901,7 @@ export class Checker {
|
|||
}
|
||||
|
||||
private addBinding(name: string, scheme: Scheme, kind: Symkind): void {
|
||||
const context = this.contexts[this.contexts.length-1];
|
||||
context.env.add(name, scheme, kind);
|
||||
this.getContext().env.add(name, scheme, kind);
|
||||
}
|
||||
|
||||
private inferKindFromTypeExpression(node: TypeExpression, env: KindEnv): Kind {
|
||||
|
@ -858,14 +924,27 @@ export class Checker {
|
|||
kind = new KStar();
|
||||
break;
|
||||
}
|
||||
case SyntaxKind.VarTypeExpression:
|
||||
case SyntaxKind.ReferenceTypeExpression:
|
||||
{
|
||||
const matchedKind = env.lookup(node.name.text);
|
||||
if (matchedKind === null) {
|
||||
this.diagnostics.add(new BindingNotFoudDiagnostic(node.name.text, node.name));
|
||||
this.diagnostics.add(new BindingNotFoudDiagnostic([], node.name.text, node.name));
|
||||
// Create a filler kind variable that still will be able to catch other errors.
|
||||
kind = this.createKindVar();
|
||||
kind.flags |= KindFlags.UnificationFailed;
|
||||
} else {
|
||||
kind = matchedKind;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SyntaxKind.VarTypeExpression:
|
||||
{
|
||||
const matchedKind = env.lookup(node.name.text);
|
||||
if (matchedKind === null) {
|
||||
this.diagnostics.add(new BindingNotFoudDiagnostic([], node.name.text, node.name));
|
||||
// Create a filler kind variable that still will be able to catch other errors.
|
||||
kind = this.createKindVar();
|
||||
kind.flags |= KindFlags.UnificationFailed;
|
||||
} else {
|
||||
kind = matchedKind;
|
||||
}
|
||||
|
@ -932,9 +1011,15 @@ export class Checker {
|
|||
}
|
||||
|
||||
private forwardDeclareKind(node: Syntax, env: KindEnv): void {
|
||||
|
||||
switch (node.kind) {
|
||||
|
||||
case SyntaxKind.ModuleDeclaration:
|
||||
{
|
||||
const innerEnv = node.kindEnv = new KindEnv(env);
|
||||
for (const element of node.elements) {
|
||||
this.forwardDeclareKind(element, innerEnv);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SyntaxKind.SourceFile:
|
||||
{
|
||||
for (const element of node.elements) {
|
||||
|
@ -970,13 +1055,19 @@ export class Checker {
|
|||
}
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private inferKind(node: Syntax, env: KindEnv): void {
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.ModuleDeclaration:
|
||||
{
|
||||
const innerEnv = node.kindEnv!;
|
||||
for (const element of node.elements) {
|
||||
this.inferKind(element, innerEnv);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SyntaxKind.SourceFile:
|
||||
{
|
||||
for (const element of node.elements) {
|
||||
|
@ -1264,15 +1355,14 @@ export class Checker {
|
|||
|
||||
case SyntaxKind.ReferenceExpression:
|
||||
{
|
||||
assert(node.modulePath.length === 0);
|
||||
const scope = node.getScope();
|
||||
const target = scope.lookup(node.name.text);
|
||||
if (target !== null && target.kind === SyntaxKind.LetDeclaration && target.active) {
|
||||
if (target !== null && target.kind === SyntaxKind.LetDeclaration && target.activeCycle) {
|
||||
return target.inferredType!;
|
||||
}
|
||||
const scheme = this.lookup(node.name.text, Symkind.Var);
|
||||
const scheme = this.lookup(node, Symkind.Var);
|
||||
if (scheme === null) {
|
||||
this.diagnostics.add(new BindingNotFoudDiagnostic(node.name.text, node.name));
|
||||
// this.diagnostics.add(new BindingNotFoudDiagnostic(node.name.text, node.name));
|
||||
return this.createTypeVar();
|
||||
}
|
||||
const type = this.instantiate(scheme, node);
|
||||
|
@ -1343,10 +1433,10 @@ export class Checker {
|
|||
}
|
||||
case SyntaxKind.PunnedStructExpressionField:
|
||||
{
|
||||
const scheme = this.lookup(member.name.text, Symkind.Var);
|
||||
const scheme = this.lookup(member.name, Symkind.Var);
|
||||
let fieldType;
|
||||
if (scheme === null) {
|
||||
this.diagnostics.add(new BindingNotFoudDiagnostic(member.name.text, member.name));
|
||||
// this.diagnostics.add(new BindingNotFoudDiagnostic(member.name.text, member.name));
|
||||
fieldType = this.createTypeVar();
|
||||
} else {
|
||||
fieldType = this.instantiate(scheme, member);
|
||||
|
@ -1363,9 +1453,9 @@ export class Checker {
|
|||
|
||||
case SyntaxKind.InfixExpression:
|
||||
{
|
||||
const scheme = this.lookup(node.operator.text, Symkind.Var);
|
||||
const scheme = this.lookup(node.operator, Symkind.Var);
|
||||
if (scheme === null) {
|
||||
this.diagnostics.add(new BindingNotFoudDiagnostic(node.operator.text, node.operator));
|
||||
// this.diagnostics.add(new BindingNotFoudDiagnostic(node.operator.text, node.operator));
|
||||
return this.createTypeVar();
|
||||
}
|
||||
const opType = this.instantiate(scheme, node.operator);
|
||||
|
@ -1403,9 +1493,9 @@ export class Checker {
|
|||
|
||||
case SyntaxKind.ReferenceTypeExpression:
|
||||
{
|
||||
const scheme = this.lookup(node.name.text, Symkind.Type);
|
||||
const scheme = this.lookup(node, Symkind.Type);
|
||||
if (scheme === null) {
|
||||
this.diagnostics.add(new BindingNotFoudDiagnostic(node.name.text, node.name));
|
||||
// this.diagnostics.add(new BindingNotFoudDiagnostic(node.name.text, node.name));
|
||||
return this.createTypeVar();
|
||||
}
|
||||
type = this.instantiate(scheme, node.name);
|
||||
|
@ -1430,10 +1520,10 @@ export class Checker {
|
|||
|
||||
case SyntaxKind.VarTypeExpression:
|
||||
{
|
||||
const scheme = this.lookup(node.name.text, Symkind.Type);
|
||||
const scheme = this.lookup(node.name, Symkind.Type);
|
||||
if (scheme === null) {
|
||||
if (!introduceTypeVars) {
|
||||
this.diagnostics.add(new BindingNotFoudDiagnostic(node.name.text, node.name));
|
||||
// this.diagnostics.add(new BindingNotFoudDiagnostic(node.name.text, node.name));
|
||||
}
|
||||
type = this.createTypeVar();
|
||||
this.addBinding(node.name.text, new Forall([], [], type), Symkind.Type);
|
||||
|
@ -1527,10 +1617,10 @@ export class Checker {
|
|||
|
||||
case SyntaxKind.StructPattern:
|
||||
{
|
||||
const scheme = this.lookup(pattern.name.text, Symkind.Type);
|
||||
const scheme = this.lookup(pattern.name, Symkind.Type);
|
||||
let recordType;
|
||||
if (scheme === null) {
|
||||
this.diagnostics.add(new BindingNotFoudDiagnostic(pattern.name.text, pattern.name));
|
||||
// this.diagnostics.add(new BindingNotFoudDiagnostic(pattern.name.text, pattern.name));
|
||||
recordType = this.createTypeVar();
|
||||
} else {
|
||||
recordType = this.instantiate(scheme, pattern.name);
|
||||
|
@ -1734,8 +1824,9 @@ export class Checker {
|
|||
kenv.setNamed('Int', new KStar());
|
||||
kenv.setNamed('String', new KStar());
|
||||
kenv.setNamed('Bool', new KStar());
|
||||
this.forwardDeclareKind(node, kenv);
|
||||
this.inferKind(node, kenv);
|
||||
const skenv = new KindEnv(kenv);
|
||||
this.forwardDeclareKind(node, skenv);
|
||||
this.inferKind(node, skenv);
|
||||
|
||||
const typeVars = new TVSet();
|
||||
const constraints = new ConstraintSet();
|
||||
|
@ -1852,7 +1943,7 @@ export class Checker {
|
|||
&& isFunctionDeclarationLike(element)) {
|
||||
if (!this.analyser.isReferencedInParentScope(element)) {
|
||||
assert(element.pattern.kind === SyntaxKind.BindPattern);
|
||||
const scheme = this.lookup(element.pattern.name.text, Symkind.Var);
|
||||
const scheme = this.lookup(element.pattern.name, Symkind.Var);
|
||||
assert(scheme !== null);
|
||||
this.instantiate(scheme, null);
|
||||
}
|
||||
|
@ -1871,7 +1962,7 @@ export class Checker {
|
|||
|
||||
for (const node of nodes) {
|
||||
assert(node.kind === SyntaxKind.LetDeclaration);
|
||||
node.active = true;
|
||||
node.activeCycle = true;
|
||||
}
|
||||
|
||||
for (const node of nodes) {
|
||||
|
@ -1912,7 +2003,7 @@ export class Checker {
|
|||
|
||||
for (const node of nodes) {
|
||||
assert(node.kind === SyntaxKind.LetDeclaration);
|
||||
node.active = false;
|
||||
node.activeCycle = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
53
src/cst.ts
53
src/cst.ts
|
@ -1,4 +1,4 @@
|
|||
import { JSONObject, JSONValue, MultiMap } from "./util";
|
||||
import { assert, JSONObject, JSONValue, MultiMap } from "./util";
|
||||
import type { InferContext, Kind, Scheme, Type, TypeEnv } from "./checker"
|
||||
|
||||
export type TextSpan = [number, number];
|
||||
|
@ -413,6 +413,17 @@ abstract class SyntaxBase {
|
|||
throw new Error(`Could not find a scope for ${this}. Maybe the parent links are not set?`);
|
||||
}
|
||||
|
||||
public getEnclosingModule(): ModuleDeclaration | SourceFile {
|
||||
let curr = this.parent!;
|
||||
while (curr !== null) {
|
||||
if (curr.kind === SyntaxKind.SourceFile || curr.kind === SyntaxKind.ModuleDeclaration) {
|
||||
return curr;
|
||||
}
|
||||
curr = curr.parent!;
|
||||
}
|
||||
throw new Error(`Unable to find an enclosing module for ${this.constructor.name}. Perhaps the parent-links are not set?`);
|
||||
}
|
||||
|
||||
public setParents(): void {
|
||||
|
||||
const visit = (value: any) => {
|
||||
|
@ -468,6 +479,29 @@ abstract class SyntaxBase {
|
|||
|
||||
}
|
||||
|
||||
public resolveModule(name: string): ModuleDeclaration | null {
|
||||
const node = this as unknown as Syntax;
|
||||
assert(node.kind === SyntaxKind.ModuleDeclaration || node.kind === SyntaxKind.SourceFile);
|
||||
for (const element of node.elements) {
|
||||
if (element.kind === SyntaxKind.ModuleDeclaration && element.name.text === name) {
|
||||
return element;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public getModulePath(): string[] {
|
||||
let curr = this.parent;
|
||||
const modulePath = [];
|
||||
while (curr !== null) {
|
||||
if (curr.kind === SyntaxKind.ModuleDeclaration) {
|
||||
modulePath.unshift(curr.name.text);
|
||||
}
|
||||
curr = curr.parent;
|
||||
}
|
||||
return modulePath;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export function forEachChild(node: Syntax, callback: (node: Syntax) => void): void {
|
||||
|
@ -681,6 +715,15 @@ export class CustomOperator extends TokenBase {
|
|||
|
||||
}
|
||||
|
||||
export type ExprOperator
|
||||
= CustomOperator
|
||||
| VBar
|
||||
|
||||
export function isExprOperator(node: Syntax): node is ExprOperator {
|
||||
return node.kind === SyntaxKind.CustomOperator
|
||||
|| node.kind === SyntaxKind.VBar
|
||||
}
|
||||
|
||||
export class Assignment extends TokenBase {
|
||||
|
||||
public readonly kind = SyntaxKind.Assignment;
|
||||
|
@ -1655,7 +1698,7 @@ export class PrefixExpression extends SyntaxBase {
|
|||
public readonly kind = SyntaxKind.PrefixExpression;
|
||||
|
||||
public constructor(
|
||||
public operator: Token,
|
||||
public operator: ExprOperator,
|
||||
public expression: Expression,
|
||||
) {
|
||||
super();
|
||||
|
@ -1677,7 +1720,7 @@ export class PostfixExpression extends SyntaxBase {
|
|||
|
||||
public constructor(
|
||||
public expression: Expression,
|
||||
public operator: Token,
|
||||
public operator: ExprOperator,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
@ -1698,7 +1741,7 @@ export class InfixExpression extends SyntaxBase {
|
|||
|
||||
public constructor(
|
||||
public left: Expression,
|
||||
public operator: Token,
|
||||
public operator: ExprOperator,
|
||||
public right: Expression,
|
||||
) {
|
||||
super();
|
||||
|
@ -2116,7 +2159,7 @@ export class LetDeclaration extends SyntaxBase {
|
|||
public scope?: Scope;
|
||||
public typeEnv?: TypeEnv;
|
||||
|
||||
public active?: boolean;
|
||||
public activeCycle?: boolean;
|
||||
public context?: InferContext;
|
||||
|
||||
public constructor(
|
||||
|
|
|
@ -151,6 +151,7 @@ export class BindingNotFoudDiagnostic {
|
|||
public readonly level = Level.Error;
|
||||
|
||||
public constructor(
|
||||
public modulePath: string[],
|
||||
public name: string,
|
||||
public node: Syntax,
|
||||
) {
|
||||
|
@ -159,7 +160,11 @@ export class BindingNotFoudDiagnostic {
|
|||
|
||||
public format(out: IndentWriter): void {
|
||||
out.write(ANSI_FG_RED + ANSI_BOLD + 'error: ' + ANSI_RESET);
|
||||
out.write(`binding '${this.name}' was not found.\n\n`);
|
||||
out.write(`binding '${this.name}' was not found`);
|
||||
if (this.modulePath.length > 0) {
|
||||
out.write(` in module ${ANSI_FG_BLUE + this.modulePath.join('.') + ANSI_RESET}`);
|
||||
}
|
||||
out.write(`.\n\n`);
|
||||
out.write(printNode(this.node) + '\n');
|
||||
}
|
||||
|
||||
|
@ -349,6 +354,25 @@ export class KindMismatchDiagnostic {
|
|||
|
||||
}
|
||||
|
||||
export class ModuleNotFoundDiagnostic {
|
||||
|
||||
public readonly level = Level.Error;
|
||||
|
||||
public constructor(
|
||||
public modulePath: string[],
|
||||
public node: Syntax,
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
public format(out: IndentWriter): void {
|
||||
out.write(ANSI_FG_RED + ANSI_BOLD + 'error: ' + ANSI_RESET);
|
||||
out.write(`a module named ${ANSI_FG_BLUE + this.modulePath.join('.') + ANSI_RESET} was not found.\n\n`);
|
||||
out.write(printNode(this.node) + '\n');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export type Diagnostic
|
||||
= UnexpectedCharDiagnostic
|
||||
| BindingNotFoudDiagnostic
|
||||
|
@ -357,6 +381,7 @@ export type Diagnostic
|
|||
| FieldMissingDiagnostic
|
||||
| FieldDoesNotExistDiagnostic
|
||||
| KindMismatchDiagnostic
|
||||
| ModuleNotFoundDiagnostic
|
||||
|
||||
export interface Diagnostics {
|
||||
readonly hasError: boolean;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
|
||||
import { warn } from "console";
|
||||
import {
|
||||
ReferenceTypeExpression,
|
||||
SourceFile,
|
||||
|
@ -61,6 +60,7 @@ import {
|
|||
DisjunctivePattern,
|
||||
TupleTypeExpression,
|
||||
ModuleDeclaration,
|
||||
isExprOperator,
|
||||
} from "./cst"
|
||||
import { Stream } from "./util";
|
||||
|
||||
|
@ -76,16 +76,6 @@ export class ParseError extends Error {
|
|||
|
||||
}
|
||||
|
||||
function isBinaryOperatorLike(token: Token): boolean {
|
||||
return token.kind === SyntaxKind.CustomOperator
|
||||
|| token.kind === SyntaxKind.VBar;
|
||||
}
|
||||
|
||||
function isPrefixOperatorLike(token: Token): boolean {
|
||||
return token.kind === SyntaxKind.CustomOperator
|
||||
|| token.kind === SyntaxKind.VBar;
|
||||
}
|
||||
|
||||
const enum OperatorMode {
|
||||
None = 0,
|
||||
Prefix = 1,
|
||||
|
@ -442,8 +432,7 @@ export class Parser {
|
|||
|| t1.kind === SyntaxKind.RParen
|
||||
|| t1.kind === SyntaxKind.BlockStart
|
||||
|| t1.kind === SyntaxKind.Comma
|
||||
|| isBinaryOperatorLike(t1)
|
||||
|| isPrefixOperatorLike(t1)) {
|
||||
|| isExprOperator(t1)) {
|
||||
break;
|
||||
}
|
||||
args.push(this.tryParseMemberExpression());
|
||||
|
@ -459,7 +448,7 @@ export class Parser {
|
|||
const prefixes = [];
|
||||
for (;;) {
|
||||
const t0 = this.peekToken();
|
||||
if (!isPrefixOperatorLike(t0)) {
|
||||
if (!isExprOperator(t0)) {
|
||||
break;
|
||||
}
|
||||
if (!this.prefixExprOperators.has(t0.text)) {
|
||||
|
@ -478,7 +467,7 @@ export class Parser {
|
|||
private parseBinaryOperatorAfterExpr(lhs: Expression, minPrecedence: number) {
|
||||
for (;;) {
|
||||
const t0 = this.peekToken();
|
||||
if (!isBinaryOperatorLike(t0)) {
|
||||
if (!isExprOperator(t0)) {
|
||||
break;
|
||||
}
|
||||
const info0 = this.binaryExprOperators.get(t0.text);
|
||||
|
@ -489,7 +478,7 @@ export class Parser {
|
|||
let rhs = this.parseUnaryExpression();
|
||||
for (;;) {
|
||||
const t1 = this.peekToken();
|
||||
if (!isBinaryOperatorLike(t1)) {
|
||||
if (!isExprOperator(t1)) {
|
||||
break;
|
||||
}
|
||||
const info1 = this.binaryExprOperators.get(t1.text);
|
||||
|
|
Loading…
Reference in a new issue