Add support for parsing/type-checking member expressions
This commit is contained in:
parent
ebf600bdfc
commit
f1a365e29c
3 changed files with 80 additions and 16 deletions
|
@ -687,6 +687,23 @@ export class Checker {
|
|||
return this.instantiate(scheme, node);
|
||||
}
|
||||
|
||||
case SyntaxKind.MemberExpression:
|
||||
{
|
||||
let type = this.inferExpression(node.expression);
|
||||
for (const [_dot, name] of node.path) {
|
||||
const newType = this.createTypeVar();
|
||||
this.addConstraint(
|
||||
new CEqual(
|
||||
type,
|
||||
new TLabeled(name.text, newType),
|
||||
node,
|
||||
)
|
||||
);
|
||||
type = newType;
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
case SyntaxKind.CallExpression:
|
||||
{
|
||||
const opType = this.inferExpression(node.func);
|
||||
|
@ -930,6 +947,12 @@ export class Checker {
|
|||
break;
|
||||
}
|
||||
|
||||
case SyntaxKind.MemberExpression:
|
||||
{
|
||||
this.addReferencesToGraph(graph, node.expression, source);
|
||||
break;
|
||||
}
|
||||
|
||||
case SyntaxKind.NamedTupleExpression:
|
||||
{
|
||||
for (const arg of node.elements) {
|
||||
|
@ -1465,13 +1488,13 @@ export class Checker {
|
|||
if (right.fields === undefined) {
|
||||
right.fields = new Map([ [ right.name, right.type ] ]);
|
||||
}
|
||||
for (const [fieldName, fieldType] of left.fields) {
|
||||
for (const [fieldName, fieldType] of right.fields) {
|
||||
if (left.fields.has(fieldName)) {
|
||||
if (!this.unify(fieldType, left.fields.get(fieldName)!, solution, constraint)) {
|
||||
success = false;
|
||||
}
|
||||
} else {
|
||||
this.diagnostics.add(new FieldMissingDiagnostic(right, fieldName));
|
||||
this.diagnostics.add(new FieldMissingDiagnostic(left, fieldName));
|
||||
}
|
||||
}
|
||||
return success;
|
||||
|
|
27
src/cst.ts
27
src/cst.ts
|
@ -122,6 +122,7 @@ export const enum SyntaxKind {
|
|||
VariadicStructPatternElement,
|
||||
|
||||
// Expressions
|
||||
MemberExpression,
|
||||
CallExpression,
|
||||
ReferenceExpression,
|
||||
NamedTupleExpression,
|
||||
|
@ -1142,7 +1143,7 @@ export class QualifiedName extends SyntaxBase {
|
|||
public readonly kind = SyntaxKind.QualifiedName;
|
||||
|
||||
public constructor(
|
||||
public modulePath: Array<[Identifier, Dot]>,
|
||||
public modulePath: Array<[IdentifierAlt, Dot]>,
|
||||
public name: Identifier,
|
||||
) {
|
||||
super();
|
||||
|
@ -1298,6 +1299,27 @@ export class ReferenceExpression extends SyntaxBase {
|
|||
|
||||
}
|
||||
|
||||
export class MemberExpression extends SyntaxBase {
|
||||
|
||||
public readonly kind = SyntaxKind.MemberExpression;
|
||||
|
||||
public constructor(
|
||||
public expression: Expression,
|
||||
public path: [Dot, Identifier][],
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
public getFirstToken(): Token {
|
||||
return this.expression.getFirstToken();
|
||||
}
|
||||
|
||||
public getLastToken(): Token {
|
||||
return this.path[this.path.length-1][1];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class PrefixExpression extends SyntaxBase {
|
||||
|
||||
public readonly kind = SyntaxKind.PrefixExpression;
|
||||
|
@ -1363,7 +1385,8 @@ export class InfixExpression extends SyntaxBase {
|
|||
}
|
||||
|
||||
export type Expression
|
||||
= CallExpression
|
||||
= MemberExpression
|
||||
| CallExpression
|
||||
| StructExpression
|
||||
| NamedTupleExpression
|
||||
| ReferenceExpression
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
|
||||
import { warn } from "console";
|
||||
import { argv0 } from "process";
|
||||
import {
|
||||
ReferenceTypeExpression,
|
||||
SourceFile,
|
||||
|
@ -46,6 +44,8 @@ import {
|
|||
PunnedStructExpressionField,
|
||||
IfStatementCase,
|
||||
IfStatement,
|
||||
MemberExpression,
|
||||
IdentifierAlt,
|
||||
} from "./cst"
|
||||
import { Stream } from "./util";
|
||||
|
||||
|
@ -180,16 +180,16 @@ export class Parser {
|
|||
}
|
||||
|
||||
public parseQualifiedName(): QualifiedName {
|
||||
const modulePath: Array<[Identifier, Dot]> = [];
|
||||
let name = this.expectToken(SyntaxKind.Identifier)
|
||||
const modulePath: Array<[IdentifierAlt, Dot]> = [];
|
||||
for (;;) {
|
||||
const t1 = this.peekToken()
|
||||
if (t1.kind !== SyntaxKind.Dot) {
|
||||
const t0 = this.peekToken(1);
|
||||
const t1 = this.peekToken(2);
|
||||
if (t0.kind !== SyntaxKind.IdentifierAlt || t1.kind !== SyntaxKind.Dot) {
|
||||
break;
|
||||
}
|
||||
modulePath.push([name, t1]);
|
||||
name = this.expectToken(SyntaxKind.Identifier)
|
||||
modulePath.push([t0, t1]);
|
||||
}
|
||||
const name = this.expectToken(SyntaxKind.Identifier);
|
||||
return new QualifiedName(modulePath, name);
|
||||
}
|
||||
|
||||
|
@ -290,8 +290,26 @@ export class Parser {
|
|||
}
|
||||
}
|
||||
|
||||
private parseExpressionNoOperators(): Expression {
|
||||
const func = this.parsePrimitiveExpression();
|
||||
private tryParseMemberExpression(): Expression {
|
||||
const expression = this.parsePrimitiveExpression();
|
||||
const path: Array<[Dot, Identifier]> = [];
|
||||
for (;;) {
|
||||
const t1 = this.peekToken();
|
||||
if (t1.kind !== SyntaxKind.Dot) {
|
||||
break;
|
||||
}
|
||||
this.getToken();
|
||||
const name = this.expectToken(SyntaxKind.Identifier);
|
||||
path.push([t1, name]);
|
||||
}
|
||||
if (path.length === 0) {
|
||||
return expression;
|
||||
}
|
||||
return new MemberExpression(expression, path);
|
||||
}
|
||||
|
||||
private tryParseCallExpression(): Expression {
|
||||
const func = this.tryParseMemberExpression();
|
||||
const args = [];
|
||||
for (;;) {
|
||||
const t1 = this.peekToken();
|
||||
|
@ -305,7 +323,7 @@ export class Parser {
|
|||
|| isPrefixOperatorLike(t1)) {
|
||||
break;
|
||||
}
|
||||
args.push(this.parsePrimitiveExpression());
|
||||
args.push(this.tryParseMemberExpression());
|
||||
}
|
||||
if (args.length === 0) {
|
||||
return func
|
||||
|
@ -314,7 +332,7 @@ export class Parser {
|
|||
}
|
||||
|
||||
private parseUnaryExpression(): Expression {
|
||||
let result = this.parseExpressionNoOperators()
|
||||
let result = this.tryParseCallExpression()
|
||||
const prefixes = [];
|
||||
for (;;) {
|
||||
const t0 = this.peekToken();
|
||||
|
|
Loading…
Reference in a new issue