Skip to content

Commit

Permalink
Fix bug that caused generics to resolve an ancestral version (#40)
Browse files Browse the repository at this point in the history
In terms of inheritance
  • Loading branch information
drew-y authored Sep 10, 2024
1 parent b7b6a94 commit 68715fa
Show file tree
Hide file tree
Showing 6 changed files with 54 additions and 5 deletions.
6 changes: 6 additions & 0 deletions src/__tests__/compiler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ describe("E2E Compiler Pipeline", () => {
const test7 = getWasmFn("test7", instance);
const test8 = getWasmFn("test8", instance);
const test9 = getWasmFn("test9", instance);
const test10 = getWasmFn("test10", instance);
const test11 = getWasmFn("test11", instance);
assert(test1, "Test1 exists");
assert(test2, "Test2 exists");
assert(test3, "Test3 exists");
Expand All @@ -49,6 +51,8 @@ describe("E2E Compiler Pipeline", () => {
assert(test7, "Test7 exists");
assert(test8, "Test8 exists");
assert(test9, "Test9 exists");
assert(test10, "Test10 exists");
assert(test11, "Test11 exists");

// Static method resolution tests
t.expect(test1(), "test 1 returns correct value").toEqual(13);
Expand All @@ -66,6 +70,8 @@ describe("E2E Compiler Pipeline", () => {

// Generic object type test
t.expect(test9(), "test 9 returns correct value").toEqual(7.5);
t.expect(test10(), "test 10 returns correct value").toEqual(12);
t.expect(test11(), "test 11 returns correct value").toEqual(4);
});

test("Compiler can do tco", async (t) => {
Expand Down
31 changes: 31 additions & 0 deletions src/__tests__/fixtures/e2e-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,37 @@ pub fn test9()
let vec2 = VecGeneric<f64> { x: 7.5, y: 2.5, z: 3.5 }
vec2.x
// Test generic object inheritance strictness
fn generic_get_member<T>(vec: T)
vec.get_member()
// Ensure generic instances of an ancestor aren't used in place of a descendant, should return 12
pub fn test10()
let vec = Vec { x: 7, y: 2}
generic_get_member<Vec>(vec)
let point = Point { x: 12, y: 17, z: 4 }
generic_get_member<Point>(point)
let pointy = Pointy { x: 12, y: 17, z: 4 }
generic_get_member<Pointy>(pointy)
// Test generic object inheritance strictness
obj VecBox<T> {
box: T
}
obj PointF extends Vec {
x: i32,
y: i32,
f: i32
}
pub fn test11()
let pf = PointF { x: 12, y: 17, f: 4 }
let pf_box = VecBox<PointF> { box: pf }
pf_box.box.f
`;

export const tcoText = `
Expand Down
2 changes: 1 addition & 1 deletion src/semantics/check-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ const checkObjectInit = (call: Call): Call => {
checkTypes(literal);

// Check to ensure literal structure is compatible with nominal structure
if (!typesAreEquivalent(literal.type, call.type, true)) {
if (!typesAreEquivalent(literal.type, call.type, { structuralOnly: true })) {
throw new Error(`Object literal type does not match expected type`);
}

Expand Down
4 changes: 3 additions & 1 deletion src/semantics/resolution/get-call-fn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@ const typeArgsMatch = (call: Call, candidate: Fn): boolean =>
? candidate.appliedTypeArgs.every((t, i) => {
const argType = getExprType(call.typeArgs?.at(i));
const appliedType = getExprType(t);
return typesAreEquivalent(argType, appliedType);
return typesAreEquivalent(argType, appliedType, {
exactNominalMatch: true,
});
})
: true;

Expand Down
4 changes: 3 additions & 1 deletion src/semantics/resolution/resolve-object-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ const typeArgsMatch = (call: Call, candidate: ObjectType): boolean =>
? candidate.appliedTypeArgs.every((t, i) => {
const argType = getExprType(call.typeArgs?.at(i));
const appliedType = getExprType(t);
return typesAreEquivalent(argType, appliedType);
return typesAreEquivalent(argType, appliedType, {
exactNominalMatch: true,
});
})
: true;
12 changes: 10 additions & 2 deletions src/semantics/resolution/types-are-equivalent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@ import { Type } from "../../syntax-objects/index.js";
export const typesAreEquivalent = (
a?: Type,
b?: Type,
ignoreExtension?: boolean // Hacky
opts: {
/** Will not check that a is an extension of b if true */
structuralOnly?: boolean;

/** Will ancestors, the type must be the same regardless of inheritance */
exactNominalMatch?: boolean;
} = {}
): boolean => {
if (!a || !b) return false;

Expand All @@ -12,8 +18,10 @@ export const typesAreEquivalent = (
}

if (a.isObjectType() && b.isObjectType()) {
if (opts.exactNominalMatch) return a.id === b.id;

return (
(ignoreExtension || a.extends(b)) &&
(opts.structuralOnly || a.extends(b)) &&
b.fields.every((field) => {
const match = a.fields.find((f) => f.name === field.name);
return match && typesAreEquivalent(field.type, match.type);
Expand Down

0 comments on commit 68715fa

Please sign in to comment.