From 365fbd387b4c685241a06255a871445b755829be Mon Sep 17 00:00:00 2001 From: Hisham Muhammad Date: Wed, 8 Jan 2025 03:54:49 -0300 Subject: [PATCH] fix: resolve self on records This fixes #877, at least for cases without generics. There is a pending test showcasing the cases which are not covered by this commit. Fixes #877. --- spec/lang/subtyping/record_spec.lua | 63 +++++++++++++++++++++++++++++ tl.lua | 51 ++++++++++++++++++----- tl.tl | 51 ++++++++++++++++++----- 3 files changed, 145 insertions(+), 20 deletions(-) diff --git a/spec/lang/subtyping/record_spec.lua b/spec/lang/subtyping/record_spec.lua index 7c2e89f5c..5d6e68875 100644 --- a/spec/lang/subtyping/record_spec.lua +++ b/spec/lang/subtyping/record_spec.lua @@ -25,5 +25,68 @@ describe("records", function() { y = 15, "record field doesn't match: m: got integer, expected string" }, { y = 17, "record field doesn't match: m: got integer, expected string" }, })) + + it("refines self on fields inherited from interfaces (regression test for #877)", util.check([[ + -- Define an interface that uses self. + -- Using self should automatically refine implementations to map self to the impl. + local interface Container + new: function(): self + end + + -- Calling Foo.new should return a Foo (that is, replace `self` with `Foo`) + local record Foo is Container end + + -- Use another collaborating record to demonstrate the bug. + local record SomeRecord + update: function(self: SomeRecord) + end + + local function foo(c: C) + end + + -- Calling foo, which expects a Container, from this outer scope works. + local outerValue = Foo.new() + foo(outerValue) -- works! + + -- Calling foo from within SomeRecord.update does not work. + function SomeRecord:update() + local value = Foo.new() + foo(value) -- works! + end + ]])) + + pending("refines generic self on fields inherited from interfaces (regression test for #877)", util.check([[ + -- Define an interface that uses self. + -- Using self should automatically refine implementations to map self to the impl. + local interface Container + new: function(val: T): self + myfield: T + end + + -- Calling Foo.new should return a Foo (that is, replace `self` with `Foo`) + local record Foo is Container + end + + -- Use another collaborating record to demonstrate the bug. + local record SomeRecord + update: function(self: SomeRecord) + end + + -- FIXME resolution of generic constraints doesn't work + local function foo>(c: C) + print(tostring(c.myfield)) + end + + -- Calling foo, which expects a Container, from this outer scope works. + local outerValue = Foo.new(1) + foo(outerValue) -- works! + + -- Calling foo from within SomeRecord.update does not work. + function SomeRecord:update() + local value = Foo.new("hello") + foo(value) -- works! + end + ]])) + end) diff --git a/tl.lua b/tl.lua index de02916bf..3589507e0 100644 --- a/tl.lua +++ b/tl.lua @@ -2254,9 +2254,9 @@ end -local function a_nominal(n, names) - return a_type(n, "nominal", { names = names }) -end + + + @@ -2449,6 +2449,7 @@ do local function new_generic(ps, i, typeargs, typ) local gt = new_type(ps, i, "generic") gt.typeargs = typeargs + assert(not (typ.typename == "typedecl")) gt.t = typ return gt end @@ -7469,6 +7470,8 @@ do return t end + assert(not (t.typename == "typedecl")) + local gt = a_type(t, "generic", { t = t }) gt.typeargs = typeargs return gt @@ -7838,6 +7841,13 @@ do copy.elements, same = resolve(t.elements, same) end + if t.interface_list then + copy.interface_list = {} + for i, v in ipairs(t.interface_list) do + copy.interface_list[i], same = resolve(v, same) + end + end + copy.is_userdata = t.is_userdata copy.fields = {} @@ -8755,10 +8765,10 @@ do function TypeChecker:type_of_self(w) local t = self:find_var_type("@self") if not t then - return a_type(w, "invalid", {}) + return a_type(w, "invalid", {}), nil end assert(t.typename == "typedecl") - return t.def + return t.def, t end @@ -8932,7 +8942,7 @@ do ["*"] = { ["boolean_context"] = compare_true, ["self"] = function(self, a, b) - return self:same_type(a, self:type_of_self(b)) + return self:same_type(a, (self:type_of_self(b))) end, ["typevar"] = function(self, a, b) return self:compare_or_infer_typevar(b.typevar, a, nil, self.same_type) @@ -9287,7 +9297,7 @@ a.types[i], b.types[i]), } return ok, errs end, ["self"] = function(self, a, b) - return self:is_a(a, self:type_of_self(b)) + return self:is_a(a, (self:type_of_self(b))) end, ["tuple"] = function(self, a, b) return self:is_a(a_type(a, "tuple", { tuple = { a } }), b) @@ -10515,7 +10525,7 @@ a.types[i], b.types[i]), } end end - local function typedecl_to_nominal(node, name, t, resolved) + local function typedecl_to_nominal(w, name, t, resolved) local typevals local def = t.def if def.typename == "generic" then @@ -10527,7 +10537,7 @@ a.types[i], b.types[i]), } })) end end - local nom = a_nominal(node, { name }) + local nom = a_type(w, "nominal", { names = { name } }) nom.typevals = typevals nom.found = t nom.resolved = resolved @@ -13166,6 +13176,27 @@ self:expand_type(node, values, elements) }) return t end + local resolve_self + do + local resolve_self_fns = { + ["self"] = function(tc, t) + local selftype, selfdecl = tc:type_of_self(t) + local checktype = selftype + if selftype.typename == "generic" then + checktype = selftype.t + end + if checktype.typename == "record" then + return typedecl_to_nominal(t, checktype.declname, selfdecl) + end + return t + end, + } + + resolve_self = function(self, t) + return map_type(self, t, resolve_self_fns) + end + end + do local function add_interface_fields(self, fields, field_order, resolved, named, list) for fname, ftype in fields_of(resolved, list) do @@ -13176,7 +13207,7 @@ self:expand_type(node, values, elements) }) end else table.insert(field_order, fname) - fields[fname] = ftype + fields[fname] = resolve_self(self, ftype) end end end diff --git a/tl.tl b/tl.tl index 1a6de4541..1fd9d23c0 100644 --- a/tl.tl +++ b/tl.tl @@ -2254,8 +2254,8 @@ local macroexp a_map(w: Where, k: Type, v: Type): MapType return a_type(w, "map", { keys = k, values = v } as MapType) end -local function a_nominal(n: Node, names: {string}): NominalType - return a_type(n, "nominal", { names = names } as NominalType) +local macroexp a_nominal(w: Where, names: {string}): NominalType + return a_type(w, "nominal", { names = names } as NominalType) end local macroexp an_invalid(w: Where): InvalidType @@ -2449,6 +2449,7 @@ end local function new_generic(ps: ParseState, i: integer, typeargs: {TypeArgType}, typ: Type): GenericType local gt = new_type(ps, i, "generic") as GenericType gt.typeargs = typeargs + assert(not typ is TypeDeclType) gt.t = typ return gt end @@ -7469,6 +7470,8 @@ do return t end + assert(not t is TypeDeclType) + local gt = a_type(t, "generic", { t = t } as GenericType) gt.typeargs = typeargs return gt @@ -7838,6 +7841,13 @@ do copy.elements, same = resolve(t.elements, same) end + if t.interface_list then + copy.interface_list = {} + for i, v in ipairs(t.interface_list) do + copy.interface_list[i], same = resolve(v, same) + end + end + copy.is_userdata = t.is_userdata copy.fields = {} @@ -8752,13 +8762,13 @@ do end end - function TypeChecker:type_of_self(w: Where): Type + function TypeChecker:type_of_self(w: Where): Type, TypeDeclType local t = self:find_var_type("@self") if not t then - return an_invalid(w) + return an_invalid(w), nil end assert(t is TypeDeclType) - return t.def + return t.def, t end -- ∃ x ∈ xs. t <: x @@ -8932,7 +8942,7 @@ do ["*"] = { ["boolean_context"] = compare_true, ["self"] = function(self: TypeChecker, a: Type, b: SelfType): boolean, {Error} - return self:same_type(a, self:type_of_self(b)) + return self:same_type(a, (self:type_of_self(b))) end, ["typevar"] = function(self: TypeChecker, a: Type, b: TypeVarType): boolean, {Error} return self:compare_or_infer_typevar(b.typevar, a, nil, self.same_type) @@ -9287,7 +9297,7 @@ do return ok, errs end, ["self"] = function(self: TypeChecker, a: Type, b: SelfType): boolean, {Error} - return self:is_a(a, self:type_of_self(b)) + return self:is_a(a, (self:type_of_self(b))) end, ["tuple"] = function(self: TypeChecker, a: Type, b: Type): boolean, {Error} return self:is_a(a_tuple(a, {a}), b) @@ -10515,7 +10525,7 @@ do end end - local function typedecl_to_nominal(node: Node, name: string, t: TypeDeclType, resolved?: Type): Type + local function typedecl_to_nominal(w: Where, name: string, t: TypeDeclType, resolved?: Type): Type local typevals: {Type} local def = t.def if def is GenericType then @@ -10527,7 +10537,7 @@ do } as TypeVarType)) end end - local nom = a_nominal(node, { name }) + local nom = a_nominal(w, { name }) nom.typevals = typevals nom.found = t nom.resolved = resolved @@ -13166,6 +13176,27 @@ do return t end + local resolve_self: function(self: TypeChecker, t: Type): Type, {Error} + do + local resolve_self_fns: TypeFunctionMap = { + ["self"] = function(tc: TypeChecker, t: SelfType): Type, boolean + local selftype, selfdecl = tc:type_of_self(t) + local checktype = selftype + if selftype is GenericType then + checktype = selftype.t + end + if checktype is RecordType then + return typedecl_to_nominal(t, checktype.declname, selfdecl) + end + return t + end, + } + + resolve_self = function(self: TypeChecker, t: Type): Type, {Error} + return map_type(self, t, resolve_self_fns) + end + end + do local function add_interface_fields(self: TypeChecker, fields: {string:Type}, field_order: {string}, resolved: RecordLikeType, named: NominalType, list?: MetaMode) for fname, ftype in fields_of(resolved, list) do @@ -13176,7 +13207,7 @@ do end else table.insert(field_order, fname) - fields[fname] = ftype + fields[fname] = resolve_self(self, ftype) end end end