Adjust the way let-declarations are visited in the type-checker
Let-declarations are now roughly visited in the order they are referenced, resulting in constraints being propagated in the same way.
This commit is contained in:
parent
6df807440a
commit
2f9b6db5af
3 changed files with 107 additions and 142 deletions
|
@ -3,24 +3,21 @@ import { assert } from "./util";
|
||||||
import { Syntax, LetDeclaration, SourceFile, SyntaxKind } from "./cst";
|
import { Syntax, LetDeclaration, SourceFile, SyntaxKind } from "./cst";
|
||||||
import type { Scope } from "./scope"
|
import type { Scope } from "./scope"
|
||||||
|
|
||||||
type NodeWithBlock
|
|
||||||
= LetDeclaration
|
|
||||||
| SourceFile
|
|
||||||
|
|
||||||
export class Analyser {
|
export class Analyser {
|
||||||
|
|
||||||
private referenceGraph = new DirectedHashGraph<NodeWithBlock>();
|
private referenceGraph = new DirectedHashGraph<LetDeclaration>();
|
||||||
|
|
||||||
public addSourceFile(node: SourceFile): void {
|
public addSourceFile(node: SourceFile): void {
|
||||||
|
|
||||||
const visit = (node: Syntax, source: NodeWithBlock) => {
|
const visit = (node: Syntax, source: Syntax | null) => {
|
||||||
|
|
||||||
const addReference = (scope: Scope, name: string) => {
|
const addReference = (scope: Scope, name: string) => {
|
||||||
const target = scope.lookup(name);
|
const target = scope.lookup(name);
|
||||||
if (target === null || target.kind === SyntaxKind.Param) {
|
if (source === null || target === null || target.kind === SyntaxKind.Param) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
assert(target.kind === SyntaxKind.LetDeclaration || target.kind === SyntaxKind.SourceFile);
|
assert(source.kind === SyntaxKind.LetDeclaration);
|
||||||
|
assert(target.kind === SyntaxKind.LetDeclaration);
|
||||||
this.referenceGraph.addEdge(source, target);
|
this.referenceGraph.addEdge(source, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,7 +170,7 @@ export class Analyser {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
visit(node, node);
|
visit(node, null);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,7 +193,7 @@ export class Analyser {
|
||||||
* a let-declaration is not recusive, it will simply show up as a collection
|
* a let-declaration is not recusive, it will simply show up as a collection
|
||||||
* with only one element.
|
* with only one element.
|
||||||
*/
|
*/
|
||||||
public getSortedDeclarations(): Iterable<NodeWithBlock[]> {
|
public getSortedDeclarations(): Iterable<LetDeclaration[]> {
|
||||||
return strongconnect(this.referenceGraph);
|
return strongconnect(this.referenceGraph);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -179,6 +179,7 @@ class KArrow extends KindBase {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO actually use these
|
||||||
const kindOfTypes = new KType();
|
const kindOfTypes = new KType();
|
||||||
const kindOfRows = new KRow();
|
const kindOfRows = new KRow();
|
||||||
|
|
||||||
|
@ -348,9 +349,18 @@ export interface InferContext {
|
||||||
returnType: Type | null;
|
returnType: Type | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isSignatureDeclarationLike(node: LetDeclaration): boolean {
|
||||||
|
return false; // May be foreignKeyword !== null later
|
||||||
|
}
|
||||||
|
|
||||||
|
function isVariableDeclarationLike(node: LetDeclaration): boolean {
|
||||||
|
return node.pattern.kind !== SyntaxKind.NamedPattern || !node.body;
|
||||||
|
}
|
||||||
|
|
||||||
function isFunctionDeclarationLike(node: LetDeclaration): boolean {
|
function isFunctionDeclarationLike(node: LetDeclaration): boolean {
|
||||||
return (node.pattern.kind === SyntaxKind.NamedPattern || node.pattern.kind === SyntaxKind.NestedPattern && node.pattern.pattern.kind === SyntaxKind.NamedPattern)
|
return !isSignatureDeclarationLike(node) && !isVariableDeclarationLike(node);
|
||||||
&& (node.params.length > 0 || (node.body !== null && node.body.kind === SyntaxKind.BlockBody));
|
// return (node.pattern.kind === SyntaxKind.NamedPattern || node.pattern.kind === SyntaxKind.NestedPattern && node.pattern.pattern.kind === SyntaxKind.NamedPattern)
|
||||||
|
// && (node.params.length > 0 || (node.body !== null && node.body.kind === SyntaxKind.BlockBody));
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Checker {
|
export class Checker {
|
||||||
|
@ -1047,50 +1057,85 @@ export class Checker {
|
||||||
case SyntaxKind.LetDeclaration:
|
case SyntaxKind.LetDeclaration:
|
||||||
{
|
{
|
||||||
if (isFunctionDeclarationLike(node)) {
|
if (isFunctionDeclarationLike(node)) {
|
||||||
break;
|
|
||||||
}
|
node.activeCycle = true;
|
||||||
const ctx = this.getContext();
|
node.visited = true;
|
||||||
const constraints = new ConstraintSet;
|
|
||||||
const innerCtx: InferContext = {
|
const context = node.context!;
|
||||||
...ctx,
|
const returnType = context.returnType!;
|
||||||
constraints,
|
this.contexts.push(context);
|
||||||
};
|
|
||||||
this.pushContext(innerCtx);
|
if (node.body !== null) {
|
||||||
let type;
|
switch (node.body.kind) {
|
||||||
if (node.typeAssert !== null) {
|
case SyntaxKind.ExprBody:
|
||||||
type = this.inferTypeExpression(node.typeAssert.typeExpression);
|
{
|
||||||
}
|
this.addConstraint(
|
||||||
if (node.body !== null) {
|
new CEqual(
|
||||||
let bodyType;
|
this.inferExpression(node.body.expression),
|
||||||
switch (node.body.kind) {
|
returnType,
|
||||||
case SyntaxKind.ExprBody:
|
node.body.expression
|
||||||
{
|
)
|
||||||
bodyType = this.inferExpression(node.body.expression);
|
);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
case SyntaxKind.BlockBody:
|
||||||
|
{
|
||||||
|
for (const element of node.body.elements) {
|
||||||
|
this.infer(element);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case SyntaxKind.BlockBody:
|
}
|
||||||
{
|
|
||||||
// TODO
|
this.contexts.pop();
|
||||||
assert(false);
|
node.activeCycle = false;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
const ctx = this.getContext();
|
||||||
|
const constraints = new ConstraintSet;
|
||||||
|
const innerCtx: InferContext = {
|
||||||
|
...ctx,
|
||||||
|
constraints,
|
||||||
|
};
|
||||||
|
this.pushContext(innerCtx);
|
||||||
|
let type;
|
||||||
|
if (node.typeAssert !== null) {
|
||||||
|
type = this.inferTypeExpression(node.typeAssert.typeExpression);
|
||||||
|
}
|
||||||
|
if (node.body !== null) {
|
||||||
|
let bodyType;
|
||||||
|
switch (node.body.kind) {
|
||||||
|
case SyntaxKind.ExprBody:
|
||||||
|
{
|
||||||
|
bodyType = this.inferExpression(node.body.expression);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SyntaxKind.BlockBody:
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (type === undefined) {
|
||||||
|
type = bodyType;
|
||||||
|
} else {
|
||||||
|
constraints.push(
|
||||||
|
new CEqual(
|
||||||
|
type,
|
||||||
|
bodyType,
|
||||||
|
node.body
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (type === undefined) {
|
if (type === undefined) {
|
||||||
type = bodyType;
|
type = this.createTypeVar();
|
||||||
} else {
|
|
||||||
constraints.push(
|
|
||||||
new CEqual(
|
|
||||||
type,
|
|
||||||
bodyType,
|
|
||||||
node.body
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
this.popContext(innerCtx);
|
||||||
|
this.inferBindings(node.pattern, type, undefined, constraints, true);
|
||||||
}
|
}
|
||||||
if (type === undefined) {
|
|
||||||
type = this.createTypeVar();
|
|
||||||
}
|
|
||||||
this.popContext(innerCtx);
|
|
||||||
this.inferBindings(node.pattern, type, undefined, constraints, true);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1163,8 +1208,13 @@ export class Checker {
|
||||||
{
|
{
|
||||||
const scope = node.getScope();
|
const scope = node.getScope();
|
||||||
const target = scope.lookup(node.name.text);
|
const target = scope.lookup(node.name.text);
|
||||||
if (target !== null && target.kind === SyntaxKind.LetDeclaration && target.activeCycle) {
|
if (target !== null && target.kind === SyntaxKind.LetDeclaration) {
|
||||||
return target.inferredType!;
|
if (target.activeCycle) {
|
||||||
|
return target.inferredType!;
|
||||||
|
}
|
||||||
|
if (!target.visited) {
|
||||||
|
this.infer(target);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const scheme = this.lookup(node, Symkind.Var);
|
const scheme = this.lookup(node, Symkind.Var);
|
||||||
if (scheme === null) {
|
if (scheme === null) {
|
||||||
|
@ -1701,11 +1751,11 @@ export class Checker {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public check(node: SourceFile): void {
|
public check(sourceFile: SourceFile): void {
|
||||||
|
|
||||||
const kenv = new KindEnv(this.globalKindEnv);
|
const kenv = new KindEnv(this.globalKindEnv);
|
||||||
this.forwardDeclareKind(node, kenv);
|
this.forwardDeclareKind(sourceFile, kenv);
|
||||||
this.inferKind(node, kenv);
|
this.inferKind(sourceFile, kenv);
|
||||||
|
|
||||||
const typeVars = new TVSet();
|
const typeVars = new TVSet();
|
||||||
const constraints = new ConstraintSet();
|
const constraints = new ConstraintSet();
|
||||||
|
@ -1714,12 +1764,12 @@ export class Checker {
|
||||||
|
|
||||||
this.pushContext(context);
|
this.pushContext(context);
|
||||||
|
|
||||||
this.initialize(node, env);
|
this.initialize(sourceFile, env);
|
||||||
|
|
||||||
this.pushContext({
|
this.pushContext({
|
||||||
typeVars,
|
typeVars,
|
||||||
constraints,
|
constraints,
|
||||||
env: node.typeEnv!,
|
env: sourceFile.typeEnv!,
|
||||||
returnType: null
|
returnType: null
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1727,18 +1777,11 @@ export class Checker {
|
||||||
|
|
||||||
for (const nodes of sccs) {
|
for (const nodes of sccs) {
|
||||||
|
|
||||||
if (nodes.some(n => n.kind === SyntaxKind.SourceFile)) {
|
|
||||||
assert(nodes.length === 1);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const typeVars = new TVSet();
|
const typeVars = new TVSet();
|
||||||
const constraints = new ConstraintSet();
|
const constraints = new ConstraintSet();
|
||||||
|
|
||||||
for (const node of nodes) {
|
for (const node of nodes) {
|
||||||
|
|
||||||
assert(node.kind === SyntaxKind.LetDeclaration);
|
|
||||||
|
|
||||||
if (!isFunctionDeclarationLike(node)) {
|
if (!isFunctionDeclarationLike(node)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -1806,84 +1849,7 @@ export class Checker {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const visitElements = (elements: Syntax[]) => {
|
this.infer(sourceFile);
|
||||||
for (const element of elements) {
|
|
||||||
if (element.kind === SyntaxKind.LetDeclaration
|
|
||||||
&& isFunctionDeclarationLike(element)) {
|
|
||||||
if (!this.analyser.isReferencedInParentScope(element)) {
|
|
||||||
const scheme = this.lookup(element.name, Symkind.Var);
|
|
||||||
assert(scheme !== null);
|
|
||||||
this.instantiate(scheme, null);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const shouldChangeTypeEnv = shouldChangeTypeEnvDuringVisit(element);
|
|
||||||
if (shouldChangeTypeEnv) {
|
|
||||||
this.pushContext({ ...this.getContext(), env: element.typeEnv! });
|
|
||||||
}
|
|
||||||
this.infer(element);
|
|
||||||
if(shouldChangeTypeEnv) {
|
|
||||||
this.contexts.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const nodes of sccs) {
|
|
||||||
|
|
||||||
if (nodes[0].kind === SyntaxKind.SourceFile) {
|
|
||||||
assert(nodes.length === 1);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const node of nodes) {
|
|
||||||
assert(node.kind === SyntaxKind.LetDeclaration);
|
|
||||||
node.activeCycle = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const node of nodes) {
|
|
||||||
|
|
||||||
assert(node.kind === SyntaxKind.LetDeclaration);
|
|
||||||
|
|
||||||
if (!isFunctionDeclarationLike(node)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const context = node.context!;
|
|
||||||
const returnType = context.returnType!;
|
|
||||||
this.contexts.push(context);
|
|
||||||
|
|
||||||
if (node.body !== null) {
|
|
||||||
switch (node.body.kind) {
|
|
||||||
case SyntaxKind.ExprBody:
|
|
||||||
{
|
|
||||||
this.addConstraint(
|
|
||||||
new CEqual(
|
|
||||||
this.inferExpression(node.body.expression),
|
|
||||||
returnType,
|
|
||||||
node.body.expression
|
|
||||||
)
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case SyntaxKind.BlockBody:
|
|
||||||
{
|
|
||||||
visitElements(node.body.elements);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.contexts.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const node of nodes) {
|
|
||||||
assert(node.kind === SyntaxKind.LetDeclaration);
|
|
||||||
node.activeCycle = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
visitElements(node.elements);
|
|
||||||
|
|
||||||
this.contexts.pop();
|
this.contexts.pop();
|
||||||
this.popContext(context);
|
this.popContext(context);
|
||||||
|
|
|
@ -2874,6 +2874,8 @@ export class LetDeclaration extends SyntaxBase {
|
||||||
|
|
||||||
@nonenumerable
|
@nonenumerable
|
||||||
public activeCycle?: boolean;
|
public activeCycle?: boolean;
|
||||||
|
@nonenumerable
|
||||||
|
public visited?: boolean;
|
||||||
|
|
||||||
@nonenumerable
|
@nonenumerable
|
||||||
public context?: InferContext;
|
public context?: InferContext;
|
||||||
|
|
Loading…
Reference in a new issue