Skip to content

Commit 3286c92

Browse files
committed
Potential type checker support for nominal objects
1 parent 088b1d3 commit 3286c92

File tree

2 files changed

+100
-10
lines changed

2 files changed

+100
-10
lines changed

src/semantics/check-types.ts

+81-7
Original file line numberDiff line numberDiff line change
@@ -382,9 +382,34 @@ const checkObjectType = (obj: ObjectType): ObjectType => {
382382
field.type = type;
383383
});
384384

385+
if (obj.parentObjExpr) {
386+
obj.parentObjExpr = checkTypes(obj.parentObjExpr);
387+
const parentType = resolveExprType(obj.parentObjExpr);
388+
assertValidExtension(obj, parentType);
389+
obj.parentObj = parentType;
390+
}
391+
385392
return obj;
386393
};
387394

395+
function assertValidExtension(
396+
child: ObjectType,
397+
parent?: Type
398+
): asserts parent is ObjectType {
399+
if (!parent || !parent?.isObjectType()) {
400+
throw new Error(`Cannot resolve parent for obj ${child.name}`);
401+
}
402+
403+
const validExtension = parent.fields.every((field) => {
404+
const match = child.fields.find((f) => f.name === field.name);
405+
return match && typesAreEquivalent(field.type, match.type);
406+
});
407+
408+
if (!validExtension) {
409+
throw new Error(`${child.name} does not properly extend ${parent.name}`);
410+
}
411+
}
412+
388413
const checkTypeAlias = (alias: TypeAlias): TypeAlias => {
389414
alias.typeExpr = checkTypes(alias.typeExpr);
390415
alias.type = resolveExprType(alias.typeExpr);
@@ -452,9 +477,7 @@ const getIdentifierType = (id: Identifier): Type | undefined => {
452477
};
453478

454479
const resolveCallFn = (call: Call): Fn | undefined => {
455-
const candidates = call.resolveFns(call.fnName);
456-
if (!candidates) return undefined;
457-
return candidates.find((candidate) => {
480+
const candidates = call.resolveFns(call.fnName).filter((candidate) => {
458481
const params = candidate.parameters;
459482
return params.every((p, index) => {
460483
const arg = call.argAt(index);
@@ -468,6 +491,54 @@ const resolveCallFn = (call: Call): Fn | undefined => {
468491
return typesAreEquivalent(p.type!, argType) && labelsMatch;
469492
});
470493
});
494+
495+
if (!candidates) return undefined;
496+
if (candidates.length === 1) return candidates[0];
497+
return findBestFnMatch(candidates, call);
498+
};
499+
500+
const findBestFnMatch = (candidates: Fn[], call: Call): Fn => {
501+
let winner: Fn | undefined = undefined;
502+
let tied = false;
503+
let lowestScore: number | undefined;
504+
for (const candidate of candidates) {
505+
const score = candidate.parameters.reduce((score, param, index) => {
506+
if (!param.type?.isObjectType()) {
507+
return score;
508+
}
509+
510+
const argType = resolveExprType(call.argAt(index));
511+
if (!argType || !argType.isObjectType()) {
512+
throw new Error(`Could not determine type. I'm helpful >.<`);
513+
}
514+
515+
return (score += argType.extensionDistance(param.type));
516+
}, 0);
517+
518+
if (lowestScore === undefined) {
519+
lowestScore = score;
520+
winner = candidate;
521+
}
522+
523+
if (score > lowestScore) {
524+
continue;
525+
}
526+
527+
if (score < lowestScore) {
528+
lowestScore = score;
529+
winner = candidate;
530+
tied = false;
531+
continue;
532+
}
533+
534+
tied = true;
535+
}
536+
537+
if (!winner || tied) {
538+
throw new Error(`Ambiguous call ${JSON.stringify(call, null, 2)}`);
539+
}
540+
541+
return winner;
471542
};
472543

473544
const getExprLabel = (expr?: Expr): string | undefined => {
@@ -486,10 +557,13 @@ const typesAreEquivalent = (a?: Type, b?: Type): boolean => {
486557
}
487558

488559
if (a.isObjectType() && b.isObjectType()) {
489-
return a.fields.every((field) => {
490-
const match = b.fields.find((f) => f.name === field.name);
491-
return match && typesAreEquivalent(field.type, match.type);
492-
});
560+
return (
561+
a.extends(b) &&
562+
a.fields.every((field) => {
563+
const match = b.fields.find((f) => f.name === field.name);
564+
return match && typesAreEquivalent(field.type, match.type);
565+
})
566+
);
493567
}
494568

495569
return false;

src/syntax-objects/types.ts

+19-3
Original file line numberDiff line numberDiff line change
@@ -199,18 +199,34 @@ export class ObjectType extends BaseType {
199199
});
200200
}
201201

202-
extends(type: ObjectType): boolean {
203-
if (this.constructor === type.constructor) {
202+
extends(ancestor: ObjectType): boolean {
203+
if (this.constructor === ancestor.constructor) {
204204
return true;
205205
}
206206

207207
if (this.parentObj) {
208-
return this.parentObj.extends(type);
208+
return this.parentObj.extends(ancestor);
209209
}
210210

211211
return false;
212212
}
213213

214+
/**
215+
* How closely related this object is to ancestor.
216+
* 0 = same type, 1 = ancestor is parent, 2 = ancestor is grandparent, etc
217+
*/
218+
extensionDistance(ancestor: ObjectType, start = 0): number {
219+
if (this.constructor === ancestor.constructor) {
220+
return start;
221+
}
222+
223+
if (this.parentObj) {
224+
return this.parentObj.extensionDistance(ancestor, start + 1);
225+
}
226+
227+
throw new Error(`${this.name} does not extend ${ancestor.name}`);
228+
}
229+
214230
hasField(name: Id) {
215231
return this.fields.some((field) => field.name === getIdStr(name));
216232
}

0 commit comments

Comments
 (0)