From e0fa687958fa0a6c8523e38c7a1ff4211412c4a8 Mon Sep 17 00:00:00 2001 From: Hisham Muhammad Date: Sat, 11 Jan 2025 01:33:21 -0300 Subject: [PATCH] fix: check fields using self in table literals Fixes #846. --- CHANGELOG.md | 19 +++++++- spec/lang/inference/table_literal_spec.lua | 54 ++++++++++++++++++++++ tl.lua | 42 +++++++++-------- tl.tl | 42 +++++++++-------- 4 files changed, 120 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 43061d482..873d4ff64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +# 0.24.3 + +2025-01-11 + +A minor bugfix release, thanks to feedback received upon the release +of 0.24.2. + +This release features commits by Hisham Muhammad. + +## What's New + +### Fixes + +* Fixes a regression in generic records with a __call metamethod (#901). +* Record fields declared with self type were not being properly type checked + in table literals (#846). + # 0.24.2 2025-01-10 @@ -347,7 +364,7 @@ and Hisham Muhammad. 2023-01-23 A minor bugfix release, thanks to feedback received upon the release -of 0.15.03 +of 0.15.0. This release features commits by Hisham Muhammad. diff --git a/spec/lang/inference/table_literal_spec.lua b/spec/lang/inference/table_literal_spec.lua index fcd8b7abf..ce5bf526b 100644 --- a/spec/lang/inference/table_literal_spec.lua +++ b/spec/lang/inference/table_literal_spec.lua @@ -65,4 +65,58 @@ describe("bidirectional inference for table literals", function() ]], { { msg = 'in record field: item: got string "wtf", expected number' } })) + + it("resolves self type from records (regression test for #846)", util.check_type_error([[ + local record Struct + a: T + b: T + c: self + end + + local a: Struct = { + a = 1, + b = 2, + c = 3, + } + + print(a.a, a.b, a.c) + ]], { + { msg = 'in record field: c: got integer, expected Struct' } + })) + + it("resolves self type in function fields (regression test for #846)", util.check_type_error([[ + local record Struct + a: T + b: T + c: function(self) + end + + local a: Struct = { + a = 1, + b = 2, + c = function(a: integer) end, + } + + print(a.a, a.b, a.c) + ]], { + { msg = 'in record field: c: argument 1: got integer, expected Struct' } + })) + + it("resolves self type from interfaces (regression test for #846)", util.check_type_error([[ + local interface Struct + a: T + b: T + c: self + end + + local a: Struct = { + a = 1, + b = 2, + c = 3, + } + + print(a.a, a.b, a.c) + ]], { + { msg = 'in record field: c: got integer, expected Struct' } + })) end) diff --git a/tl.lua b/tl.lua index da19e12ad..b8cc49434 100644 --- a/tl.lua +++ b/tl.lua @@ -7388,6 +7388,7 @@ do + function TypeChecker:find_var(name, use) @@ -7834,6 +7835,7 @@ do copy.macroexp = t.macroexp copy.min_arity = t.min_arity copy.is_method = t.is_method + copy.is_record_function = t.is_record_function copy.args, same = resolve(t.args, same) copy.rets, same = resolve(t.rets, same) elseif t.fields then @@ -12171,6 +12173,13 @@ self:expand_type(node, values, elements) }) return infer_table_literal(self, node, children) end + if decltype.fields then + self:begin_scope() + self:add_var(nil, "@self", a_type(node, "typedecl", { def = decltype })) + decltype = self:resolve_self(decltype, true) + self:end_scope() + end + local force_array = nil local seen_keys = {} @@ -13181,24 +13190,21 @@ 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, - } + function TypeChecker:resolve_self(t, resolve_interface) + local selftype, selfdecl = self:type_of_self(t) + local checktype = selftype + if selftype.typename == "generic" then + checktype = selftype.t + end - resolve_self = function(self, t) - return map_type(self, t, resolve_self_fns) + if (resolve_interface and checktype.typename == "interface") or checktype.typename == "record" then + return map_type(self, t, { + ["self"] = function(_, typ) + return typedecl_to_nominal(typ, checktype.declname, selfdecl) + end, + }) + else + return t end end @@ -13215,7 +13221,7 @@ self:expand_type(node, values, elements) }) if ftype.typename == "typedecl" then fields[fname] = ftype else - fields[fname] = resolve_self(self, ftype) + fields[fname] = self:resolve_self(ftype) end end end diff --git a/tl.tl b/tl.tl index 0feb98722..510d6e1e9 100644 --- a/tl.tl +++ b/tl.tl @@ -7379,6 +7379,7 @@ do type_check_funcall: function(TypeChecker, node: Node, a: Type, b: TupleType, argdelta?: integer): InvalidOrTupleType expand_type: function(TypeChecker, w: Where, old: Type, new: Type): Type + resolve_self: function(TypeChecker, Type, resolve_interface?: boolean): Type, {Error} get_rets: function(TupleType): TupleType end @@ -7834,6 +7835,7 @@ do copy.macroexp = t.macroexp copy.min_arity = t.min_arity copy.is_method = t.is_method + copy.is_record_function = t.is_record_function copy.args, same = resolve(t.args, same) as (TupleType, boolean) copy.rets, same = resolve(t.rets, same) as (TupleType, boolean) elseif t is RecordLikeType then @@ -12171,6 +12173,13 @@ do return infer_table_literal(self, node, children) end + if decltype is RecordLikeType then + self:begin_scope() + self:add_var(nil, "@self", a_typedecl(node, decltype)) + decltype = self:resolve_self(decltype, true) + self:end_scope() + end + local force_array: Type = nil local seen_keys: {CheckableKey:Where} = {} @@ -13181,24 +13190,21 @@ 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, - } + function TypeChecker:resolve_self(t: Type, resolve_interface?: boolean): Type, {Error} + local selftype, selfdecl = self:type_of_self(t) + local checktype = selftype + if selftype is GenericType then + checktype = selftype.t + end - resolve_self = function(self: TypeChecker, t: Type): Type, {Error} - return map_type(self, t, resolve_self_fns) + if (resolve_interface and checktype is InterfaceType) or checktype is RecordType then + return map_type(self, t, { + ["self"] = function(_: TypeChecker, typ: SelfType): Type, boolean + return typedecl_to_nominal(typ, checktype.declname, selfdecl) + end, + }) + else + return t end end @@ -13215,7 +13221,7 @@ do if ftype is TypeDeclType then fields[fname] = ftype else - fields[fname] = resolve_self(self, ftype) + fields[fname] = self:resolve_self(ftype) end end end