From 467637fc636b2537dd5de1d327f7e9ca10bf6db2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexis=20Laferri=C3=A8re?= Date: Sat, 26 Sep 2026 12:10:21 -0400 Subject: [PATCH 1/2] sema: type-check attribute bodies until fixed point MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Local variables with a static type defined within a loop need repeated visits until a fixed point is reached. We did that correctly for method bodies in #1257 but attribute bodies were left out. Let's apply the same logic for both method bodies and attributes. This addresses miscompiles. Signed-off-by: Alexis Laferrière --- src/semantize/typing.nit | 20 +++++++----- tests/base_adaptive_loop_attr.nit | 45 +++++++++++++++++++++++++++ tests/sav/base_adaptive_loop_attr.res | 3 ++ 3 files changed, 61 insertions(+), 7 deletions(-) create mode 100644 tests/base_adaptive_loop_attr.nit create mode 100644 tests/sav/base_adaptive_loop_attr.res diff --git a/src/semantize/typing.nit b/src/semantize/typing.nit index 2b78c9f127..afde80223a 100644 --- a/src/semantize/typing.nit +++ b/src/semantize/typing.nit @@ -629,6 +629,16 @@ private class TypeVisitor # Some loops had been visited during the visit var has_loop = false + # Visit `nbody`, the body of a property or a standalone expression + fun visit_body(nbody: AExpr) + do + loop + dirty = false + visit_stmt(nbody) + if not has_loop or not dirty then break + end + end + fun set_variable(node: AExpr, variable: Variable, mtype: nullable MType) do var flow = node.after_flow_context @@ -1014,11 +1024,7 @@ redef class AMethPropdef var nblock = self.n_block if nblock == null then return - loop - v.dirty = false - v.visit_stmt(nblock) - if not v.has_loop or not v.dirty then break - end + v.visit_body(nblock) var post_visitor = new PostTypingVisitor(v) post_visitor.enter_visit(self) @@ -1070,7 +1076,7 @@ redef class AAttrPropdef end var nblock = self.n_block if nblock != null then - v.visit_stmt(nblock) + v.visit_body(nblock) if not nblock.after_flow_context.is_unreachable then # We reach the end of the init without having a return, it is bad v.error(self, "Error: reached end of block; expected `return`.") @@ -1140,7 +1146,7 @@ redef class AExpr fun do_typing(modelbuilder: ModelBuilder, visited_mpropdef: MPropDef) do var type_visitor = new TypeVisitor(modelbuilder, visited_mpropdef) - type_visitor.visit_stmt(self) + type_visitor.visit_body(self) var post_visitor = new PostTypingVisitor(type_visitor) post_visitor.enter_visit(self) end diff --git a/tests/base_adaptive_loop_attr.nit b/tests/base_adaptive_loop_attr.nit new file mode 100644 index 0000000000..cee35d299b --- /dev/null +++ b/tests/base_adaptive_loop_attr.nit @@ -0,0 +1,45 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import core::kernel +class A + fun m: nullable Object do + var b = null + loop + if b != null then return b + b = 1 + end + end + + var x: nullable Object do + var b = null + loop + if b != null then return b + b = 2 + end + end + + var y: nullable Object is lazy do + var b = null + loop + if b != null then return b + b = 3 + end + end +end + +var a = new A +a.m.as(not null).output +a.x.as(not null).output +a.y.as(not null).output diff --git a/tests/sav/base_adaptive_loop_attr.res b/tests/sav/base_adaptive_loop_attr.res new file mode 100644 index 0000000000..01e79c32a8 --- /dev/null +++ b/tests/sav/base_adaptive_loop_attr.res @@ -0,0 +1,3 @@ +1 +2 +3 From bc40040fddaa40d1e0a99890d3e06ea9dd6d4b6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexis=20Laferri=C3=A8re?= Date: Sat, 26 Sep 2026 20:05:00 -0400 Subject: [PATCH 2/2] sema: apply the post-typing checks to attributes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add another missing type-checking step to attribute blocks. The warnings on useless null tests, type tests and casts are reported by a visit after the typing. This visit was introduced for method bodies in #1524 but attributes were left out, so these warnings were never reported in attribute blocks or in the expressions of attribute initializers. Let's gather the fixed point and this visit in `visit_body`, shared by method and attribute bodies. Remove the dead `do_typing` and its call site to the old `visit_body`. Signed-off-by: Alexis Laferrière --- src/semantize/typing.nit | 47 ++++++++++----------------- tests/base_attr_useless_check.nit | 43 ++++++++++++++++++++++++ tests/sav/base_attr_useless_check.res | 10 ++++++ 3 files changed, 71 insertions(+), 29 deletions(-) create mode 100644 tests/base_attr_useless_check.nit create mode 100644 tests/sav/base_attr_useless_check.res diff --git a/src/semantize/typing.nit b/src/semantize/typing.nit index afde80223a..0ca99d3d2d 100644 --- a/src/semantize/typing.nit +++ b/src/semantize/typing.nit @@ -629,14 +629,19 @@ private class TypeVisitor # Some loops had been visited during the visit var has_loop = false - # Visit `nbody`, the body of a property or a standalone expression - fun visit_body(nbody: AExpr) - do - loop - dirty = false - visit_stmt(nbody) - if not has_loop or not dirty then break + # Visit `nbody`, the body of `npropdef` + fun visit_body(npropdef: APropdef, nbody: nullable AExpr) + do + if nbody != null then + loop + dirty = false + visit_stmt(nbody) + if not has_loop or not dirty then break + end end + + var post_visitor = new PostTypingVisitor(self) + post_visitor.enter_visit(npropdef) end fun set_variable(node: AExpr, variable: Variable, mtype: nullable MType) @@ -1024,10 +1029,7 @@ redef class AMethPropdef var nblock = self.n_block if nblock == null then return - v.visit_body(nblock) - - var post_visitor = new PostTypingVisitor(v) - post_visitor.enter_visit(self) + v.visit_body(self, nblock) if not nblock.after_flow_context.is_unreachable and msignature.return_mtype != null then # We reach the end of the function without having a return, it is bad @@ -1075,12 +1077,11 @@ redef class AAttrPropdef v.visit_expr_subtype(nexpr, mtype) end var nblock = self.n_block - if nblock != null then - v.visit_body(nblock) - if not nblock.after_flow_context.is_unreachable then - # We reach the end of the init without having a return, it is bad - v.error(self, "Error: reached end of block; expected `return`.") - end + v.visit_body(self, nblock) + + if nblock != null and not nblock.after_flow_context.is_unreachable then + # We reach the end of the init without having a return, it is bad + v.error(self, "Error: reached end of block; expected `return`.") end end end @@ -1138,18 +1139,6 @@ redef class AExpr end return res end - - # Type the expression as if located in `visited_mpropdef` - # `TypeVisitor` and `PostTypingVisitor` will be used to do the typing, see them for more information. - # - # `visited_mpropdef`: Correspond to the evaluation context in which the expression is located. - fun do_typing(modelbuilder: ModelBuilder, visited_mpropdef: MPropDef) - do - var type_visitor = new TypeVisitor(modelbuilder, visited_mpropdef) - type_visitor.visit_body(self) - var post_visitor = new PostTypingVisitor(type_visitor) - post_visitor.enter_visit(self) - end end redef class ABlockExpr diff --git a/tests/base_attr_useless_check.nit b/tests/base_attr_useless_check.nit new file mode 100644 index 0000000000..6ea6287b2b --- /dev/null +++ b/tests/base_attr_useless_check.nit @@ -0,0 +1,43 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import core::kernel + +class A + fun m: Int do + var i = 1 + if i == null then return 0 + return i.as(Int) + end + + var x: Int do + var i = 2 + if i == null then return 0 + return i.as(Int) + end + + var y: Int is lazy do + var i = 3 + if i isa Int then return i + return 0 + end + + var z: Int = 4.as(Int) +end + +var a = new A +a.m.output +a.x.output +a.y.output +a.z.output diff --git a/tests/sav/base_attr_useless_check.res b/tests/sav/base_attr_useless_check.res new file mode 100644 index 0000000000..9c427f5246 --- /dev/null +++ b/tests/sav/base_attr_useless_check.res @@ -0,0 +1,10 @@ +base_attr_useless_check.nit:20,6: Warning: expression is not null, since it is a `Int`. +base_attr_useless_check.nit:21,10--18: Warning: expression is already a `Int`. +base_attr_useless_check.nit:26,6: Warning: expression is not null, since it is a `Int`. +base_attr_useless_check.nit:27,10--18: Warning: expression is already a `Int`. +base_attr_useless_check.nit:32,6--14: Warning: expression is already a `Int`. +base_attr_useless_check.nit:36,15--23: Warning: expression is already a `Int`. +1 +2 +3 +4