diff --git a/src/__tests__/compiler.test.ts b/src/__tests__/compiler.test.ts index 4e7f8d5..9dba9d8 100644 --- a/src/__tests__/compiler.test.ts +++ b/src/__tests__/compiler.test.ts @@ -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"); @@ -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); @@ -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) => { diff --git a/src/__tests__/fixtures/e2e-file.ts b/src/__tests__/fixtures/e2e-file.ts index 934dcaa..9d883a1 100644 --- a/src/__tests__/fixtures/e2e-file.ts +++ b/src/__tests__/fixtures/e2e-file.ts @@ -116,6 +116,37 @@ pub fn test9() let vec2 = VecGeneric { x: 7.5, y: 2.5, z: 3.5 } vec2.x + +// Test generic object inheritance strictness +fn generic_get_member(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) + + let point = Point { x: 12, y: 17, z: 4 } + generic_get_member(point) + + let pointy = Pointy { x: 12, y: 17, z: 4 } + generic_get_member(pointy) + +// Test generic object inheritance strictness +obj VecBox { + 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 { box: pf } + pf_box.box.f `; export const tcoText = ` diff --git a/src/semantics/check-types.ts b/src/semantics/check-types.ts index 9e9413f..70e7c56 100644 --- a/src/semantics/check-types.ts +++ b/src/semantics/check-types.ts @@ -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`); } diff --git a/src/semantics/resolution/get-call-fn.ts b/src/semantics/resolution/get-call-fn.ts index 76b4b91..3b39e3b 100644 --- a/src/semantics/resolution/get-call-fn.ts +++ b/src/semantics/resolution/get-call-fn.ts @@ -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; diff --git a/src/semantics/resolution/resolve-object-type.ts b/src/semantics/resolution/resolve-object-type.ts index d70abb1..bd979e4 100644 --- a/src/semantics/resolution/resolve-object-type.ts +++ b/src/semantics/resolution/resolve-object-type.ts @@ -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; diff --git a/src/semantics/resolution/types-are-equivalent.ts b/src/semantics/resolution/types-are-equivalent.ts index 04bd25a..a778fbc 100644 --- a/src/semantics/resolution/types-are-equivalent.ts +++ b/src/semantics/resolution/types-are-equivalent.ts @@ -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; @@ -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);