Merge branch 'explain-assert' into master
authorAlexis Laferrière <alexis.laf@xymus.net>
Fri, 16 Feb 2018 16:26:25 +0000 (11:26 -0500)
committerAlexis Laferrière <alexis.laf@xymus.net>
Fri, 16 Feb 2018 16:26:25 +0000 (11:26 -0500)
30 files changed:
lib/gamnit/dynamic_resolution.nit
lib/gamnit/flat/flat_core.nit
src/astbuilder.nit
src/compiler/abstract_compiler.nit
src/frontend/code_gen.nit
src/frontend/explain_assert.nit [new file with mode: 0644]
src/frontend/explain_assert_api.nit [new file with mode: 0644]
src/interpreter/naive_interpreter.nit
tests/nitvm.skip
tests/sav/assertions.res
tests/sav/base_notnull_lit_alt2.res
tests/sav/nitunit_args9.res
tests/sav/test_c_alt4.res [deleted file]
tests/sav/test_explain_assert.res [new file with mode: 0644]
tests/sav/test_explain_assert_alt1.res [new file with mode: 0644]
tests/sav/test_explain_assert_alt10.res [new file with mode: 0644]
tests/sav/test_explain_assert_alt11.res [new file with mode: 0644]
tests/sav/test_explain_assert_alt12.res [new file with mode: 0644]
tests/sav/test_explain_assert_alt13.res [new file with mode: 0644]
tests/sav/test_explain_assert_alt2.res [new file with mode: 0644]
tests/sav/test_explain_assert_alt3.res [new file with mode: 0644]
tests/sav/test_explain_assert_alt4.res [new file with mode: 0644]
tests/sav/test_explain_assert_alt5.res [new file with mode: 0644]
tests/sav/test_explain_assert_alt6.res [new file with mode: 0644]
tests/sav/test_explain_assert_alt7.res [new file with mode: 0644]
tests/sav/test_explain_assert_alt8.res [new file with mode: 0644]
tests/sav/test_explain_assert_alt9.res [new file with mode: 0644]
tests/test_c.nit
tests/test_explain_assert.nit [new file with mode: 0644]
tests/test_nitunit4/test_nitunit4.nit

index 53a44e8..2c6a4c8 100644 (file)
@@ -97,8 +97,7 @@ redef class App
                        glViewport(0, 0, display.width, display.height)
                        glClear gl_COLOR_BUFFER_BIT | gl_DEPTH_BUFFER_BIT
 
-                       var gl_error = glGetError
-                       assert gl_error == gl_NO_ERROR else print_error gl_error
+                       assert glGetError == gl_NO_ERROR
                        return
                end
 
@@ -112,8 +111,7 @@ redef class App
 
                glClear gl_COLOR_BUFFER_BIT | gl_DEPTH_BUFFER_BIT
 
-               var gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
        end
 
        # Draw the dynamic screen to the real screen if `dynamic_resolution_ratio != 1.0`
@@ -147,18 +145,15 @@ redef class App
                n_floats = 2
                glEnableVertexAttribArray dynres_program.tex_coord.location
                glVertexAttribPointeri(dynres_program.tex_coord.location, n_floats, gl_FLOAT, false, 0, offset)
-               var gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
 
                # Draw
                glDrawArrays(gl_TRIANGLE_STRIP, 0, 4)
-               gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
 
                # Take down
                glBindBuffer(gl_ARRAY_BUFFER, 0)
-               gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
 
                sys.perfs["gamnit flat dyn res"].add app.perf_clock_dynamic_resolution.lapse
        end
@@ -214,16 +209,14 @@ private class DynamicContext
                glBindFramebuffer(gl_FRAMEBUFFER, framebuffer)
                assert glIsFramebuffer(framebuffer)
                self.dynamic_framebuffer = framebuffer
-               var gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
 
                # Depth & texture/color
                var depthbuffer = glGenRenderbuffers(1).first
                self.depth_renderbuffer = depthbuffer
                var texture = glGenTextures(1).first
                self.texture = texture
-               gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
 
                resize(display, max_dynamic_resolution_ratio)
                assert glCheckFramebufferStatus(gl_FRAMEBUFFER) == gl_FRAMEBUFFER_COMPLETE
@@ -232,8 +225,7 @@ private class DynamicContext
                buffer_array = glGenBuffers(1).first
                glBindBuffer(gl_ARRAY_BUFFER, buffer_array)
                assert glIsBuffer(buffer_array)
-               gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
 
                ## coord
                var data = new Array[Float]
@@ -251,8 +243,7 @@ private class DynamicContext
 
                glBindBuffer(gl_ARRAY_BUFFER, 0)
 
-               gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
        end
 
        # Init size or resize `depth_renderbuffer` and `texture`
@@ -271,8 +262,7 @@ private class DynamicContext
                assert glIsRenderbuffer(depthbuffer)
                glRenderbufferStorage(gl_RENDERBUFFER, gl_DEPTH_COMPONENT16, width, height)
                glFramebufferRenderbuffer(gl_FRAMEBUFFER, gl_DEPTH_ATTACHMENT, gl_RENDERBUFFER, depthbuffer)
-               var gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
 
                # Texture
                glBindTexture(gl_TEXTURE_2D, texture)
@@ -284,15 +274,13 @@ private class DynamicContext
                             0, gl_RGB, gl_UNSIGNED_BYTE, new Pointer.nul)
                glFramebufferTexture2D(gl_FRAMEBUFFER, gl_COLOR_ATTACHMENT0, gl_TEXTURE_2D, texture, 0)
 
-               gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
 
                # Take down
                glBindRenderbuffer(gl_RENDERBUFFER, 0)
                glBindFramebuffer(gl_FRAMEBUFFER, 0)
 
-               gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
        end
 
        var destroyed = false
@@ -303,8 +291,7 @@ private class DynamicContext
 
                # Free the buffer
                glDeleteBuffers([buffer_array])
-               var gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
                buffer_array = -1
 
                # Free the dynamic framebuffer and its attachments
index ef49d00..320d471 100644 (file)
@@ -432,8 +432,7 @@ redef class App
                var display = display
                assert display != null
 
-               var gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
 
                # Prepare program
                var program = simple_2d_program
@@ -455,8 +454,7 @@ redef class App
                glViewport(0, 0, display.width, display.height)
                glClearColor(0.0, 0.0, 0.0, 1.0)
 
-               gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
 
                # Prepare to draw
                for tex in all_root_textures do
@@ -501,8 +499,7 @@ redef class App
        redef fun frame_core(display)
        do
                # Check errors
-               var gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
 
                # Update game logic and set sprites
                perf_clock_main.lapse
@@ -516,8 +513,7 @@ redef class App
                display.flip
 
                # Check errors
-               gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
        end
 
        private var frame_dt = 0.0
@@ -1128,16 +1124,14 @@ private class SpriteContext
                buffer_array = bufs[0]
                buffer_element = bufs[1]
 
-               var gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
        end
 
        # Destroy `buffer_array` and `buffer_element`
        fun destroy
        do
                glDeleteBuffers([buffer_array, buffer_element])
-               var gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
 
                buffer_array = -1
                buffer_element = -1
@@ -1155,8 +1149,7 @@ private class SpriteContext
                glBindBuffer(gl_ARRAY_BUFFER, buffer_array)
                assert glIsBuffer(buffer_array)
                glBufferData(gl_ARRAY_BUFFER, array_bytes, new Pointer.nul, usage)
-               var gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
 
                # GL_TRIANGLES 6 vertices * sprite
                var n_indices = capacity * indices_per_sprite
@@ -1165,8 +1158,7 @@ private class SpriteContext
                glBindBuffer(gl_ELEMENT_ARRAY_BUFFER, buffer_element)
                assert glIsBuffer(buffer_element)
                glBufferData(gl_ELEMENT_ARRAY_BUFFER, element_bytes, new Pointer.nul, usage)
-               gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
 
                buffer_capacity = capacity
 
@@ -1294,8 +1286,7 @@ private class SpriteContext
                glBindBuffer(gl_ELEMENT_ARRAY_BUFFER, buffer_element)
                glBufferSubData(gl_ELEMENT_ARRAY_BUFFER, sprite_index*6*2, 6*2, indices.native_array)
 
-               gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
        end
 
        # Draw all `sprites`
@@ -1365,8 +1356,7 @@ private class SpriteContext
                        glBindTexture(gl_TEXTURE_2D, texture.gl_texture)
                        app.simple_2d_program.texture.uniform 0
                end
-               var gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
 
                var animation = animation_texture
                if animation != null then
@@ -1374,8 +1364,7 @@ private class SpriteContext
                        glBindTexture(gl_TEXTURE_2D, animation.gl_texture)
                        app.simple_2d_program.animation_texture.uniform 1
                end
-               gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
 
                # Configure attributes, in order:
                # vec4 translation, vec4 color, float scale, vec4 coord, vec2 tex_coord, vec4 rotation_row*,
@@ -1389,36 +1378,31 @@ private class SpriteContext
                glEnableVertexAttribArray p.translation.location
                glVertexAttribPointeri(p.translation.location, size, gl_FLOAT, false, bytes_per_vertex, offset)
                offset += size * sizeof_gl_float
-               gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
 
                size = 4
                glEnableVertexAttribArray p.color.location
                glVertexAttribPointeri(p.color.location, size, gl_FLOAT, false, bytes_per_vertex, offset)
                offset += size * sizeof_gl_float
-               gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
 
                size = 1
                glEnableVertexAttribArray p.scale.location
                glVertexAttribPointeri(p.scale.location, size, gl_FLOAT, false, bytes_per_vertex, offset)
                offset += size * sizeof_gl_float
-               gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
 
                size = 4
                glEnableVertexAttribArray p.coord.location
                glVertexAttribPointeri(p.coord.location, size, gl_FLOAT, false, bytes_per_vertex, offset)
                offset += size * sizeof_gl_float
-               gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
 
                size = 2
                glEnableVertexAttribArray p.tex_coord.location
                glVertexAttribPointeri(p.tex_coord.location, size, gl_FLOAT, false, bytes_per_vertex, offset)
                offset += size * sizeof_gl_float
-               gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
 
                size = 4
                for r in [p.rotation_row0, p.rotation_row1, p.rotation_row2, p.rotation_row3] do
@@ -1427,65 +1411,56 @@ private class SpriteContext
                                glVertexAttribPointeri(r.location, size, gl_FLOAT, false, bytes_per_vertex, offset)
                        end
                        offset += size * sizeof_gl_float
-                       gl_error = glGetError
-                       assert gl_error == gl_NO_ERROR else print_error gl_error
+                       assert glGetError == gl_NO_ERROR
                end
 
                size = 1
                glEnableVertexAttribArray p.animation_fps.location
                glVertexAttribPointeri(p.animation_fps.location, size, gl_FLOAT, false, bytes_per_vertex, offset)
                offset += size * sizeof_gl_float
-               gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
 
                size = 1
                glEnableVertexAttribArray p.animation_n_frames.location
                glVertexAttribPointeri(p.animation_n_frames.location, size, gl_FLOAT, false, bytes_per_vertex, offset)
                offset += size * sizeof_gl_float
-               gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
 
                size = 2
                glEnableVertexAttribArray p.animation_coord.location
                glVertexAttribPointeri(p.animation_coord.location, size, gl_FLOAT, false, bytes_per_vertex, offset)
                offset += size * sizeof_gl_float
-               gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
 
                size = 2
                glEnableVertexAttribArray p.animation_tex_coord.location
                glVertexAttribPointeri(p.animation_tex_coord.location, size, gl_FLOAT, false, bytes_per_vertex, offset)
                offset += size * sizeof_gl_float
-               gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
 
                size = 2
                glEnableVertexAttribArray p.animation_tex_diff.location
                glVertexAttribPointeri(p.animation_tex_diff.location, size, gl_FLOAT, false, bytes_per_vertex, offset)
                offset += size * sizeof_gl_float
-               gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
 
                size = 1
                glEnableVertexAttribArray p.animation_start.location
                glVertexAttribPointeri(p.animation_start.location, size, gl_FLOAT, false, bytes_per_vertex, offset)
                offset += size * sizeof_gl_float
-               gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
 
                size = 1
                glEnableVertexAttribArray p.animation_loops.location
                glVertexAttribPointeri(p.animation_loops.location, size, gl_FLOAT, false, bytes_per_vertex, offset)
                offset += size * sizeof_gl_float
-               gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
 
                # Actual draw
                for s in sprites.starts, e in sprites.ends do
                        var l = e-s
                        glDrawElementsi(gl_TRIANGLES, l*indices_per_sprite, gl_UNSIGNED_SHORT, 2*s*indices_per_sprite)
-                       gl_error = glGetError
-                       assert gl_error == gl_NO_ERROR else print_error gl_error
+                       assert glGetError == gl_NO_ERROR
                end
 
                # Take down
@@ -1493,14 +1468,12 @@ private class SpriteContext
                             p.rotation_row0, p.rotation_row1, p.rotation_row2, p.rotation_row3: Attribute] do
                        if not attr.is_active then continue
                        glDisableVertexAttribArray(attr.location)
-                       gl_error = glGetError
-                       assert gl_error == gl_NO_ERROR else print_error gl_error
+                       assert glGetError == gl_NO_ERROR
                end
 
                glBindBuffer(gl_ARRAY_BUFFER, 0)
                glBindBuffer(gl_ELEMENT_ARRAY_BUFFER, 0)
-               gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
        end
 end
 
index 2e500ec..79d0ba1 100644 (file)
@@ -23,7 +23,7 @@ intrude import semantize::scope
 # General factory to build semantic nodes in the AST of expressions
 class ASTBuilder
        # The module used as reference for the building
-       # It is used to gather types and other stufs
+       # It is used to gather types and other stuff
        var mmodule: MModule
 
        # The anchor used for some mechanism relying on types
@@ -35,7 +35,7 @@ class ASTBuilder
                return new AIntegerExpr.make(value, mmodule.int_type)
        end
 
-       # Make a new instatiation
+       # Make a new instantiation
        fun make_new(callsite: CallSite, args: nullable Array[AExpr]): ANewExpr
        do
                return new ANewExpr.make(callsite, args)
@@ -96,7 +96,7 @@ class ASTBuilder
                return new ABreakExpr.make(escapemark)
        end
 
-       # Make a new condinionnal
+       # Make a new conditional
        # `mtype` is the return type of the whole if, in case of a ternary operator.
        fun make_if(condition: AExpr, mtype: nullable MType): AIfExpr
        do
@@ -128,9 +128,9 @@ redef class AExpr
        private var variable_cache: nullable Variable
 
        # The `detach` method completely remove the node in the parent.
-       # Owever, sometime, it is useful to keep the emplacement of the removed child.
+       # However, sometime, it is useful to keep the emplacement of the removed child.
        #
-       # The standard usecase is the insertion of a node beetwen a parent `p` and a child `p.c`.
+       # The standard use case is the insertion of a node between a parent `p` and a child `p.c`.
        # To create the new node `n`, we need to attach the child to it.
        # But, to put `n` where `c` was in `p`, the place has to be remembered.
        #
@@ -162,7 +162,7 @@ redef class AExpr
 end
 
 # A placeholder for a `AExpr` node
-# Instances are transiantly used to mark some specific emplacments in the AST
+# Instances are transiantly used to mark some specific emplacements in the AST
 # during complex transformations.
 #
 # Their must not appear in a valid AST
@@ -181,7 +181,7 @@ redef class ABlockExpr
                self.is_typed = true
        end
 
-       redef fun add(expr: AExpr)
+       redef fun add(expr)
        do
                n_expr.add expr
        end
@@ -196,7 +196,7 @@ redef class ALoopExpr
                n_block.is_typed = true
        end
 
-       redef fun add(expr: AExpr)
+       redef fun add(expr)
        do
                n_block.add expr
        end
@@ -222,7 +222,7 @@ redef class ADoExpr
                return new ABreakExpr.make(escapemark)
        end
 
-       redef fun add(expr: AExpr)
+       redef fun add(expr)
        do
                n_block.add expr
        end
index 1a1bb07..08cdb07 100644 (file)
@@ -25,6 +25,7 @@ private import annotation
 import mixin
 import counter
 import pkgconfig
+private import explain_assert_api
 
 # Add compiling options
 redef class ToolContext
@@ -3615,6 +3616,9 @@ redef class AAssertExpr
                var cond = v.expr_bool(self.n_expr)
                v.add("if (unlikely(!{cond})) \{")
                v.stmt(self.n_else)
+
+               explain_assert v
+
                var nid = self.n_id
                if nid != null then
                        v.add_abort("Assert '{nid.text}' failed")
@@ -3623,6 +3627,24 @@ redef class AAssertExpr
                end
                v.add("\}")
        end
+
+       # Explain assert if it fails
+       private fun explain_assert(v: AbstractCompilerVisitor)
+       do
+               var explain_assert_str = explain_assert_str
+               if explain_assert_str == null then return
+
+               var nas = v.compiler.modelbuilder.model.get_mclasses_by_name("NativeArray")
+               if nas == null then return
+
+               var expr = explain_assert_str.expr(v)
+               if expr == null then return
+
+               var cstr = v.send(v.get_property("to_cstring", expr.mtype), [expr])
+               if cstr == null then return
+
+               v.add "PRINT_ERROR(\"Runtime assert: %s\\n\", {cstr});"
+       end
 end
 
 redef class AOrExpr
index ccbddde..48f1aa7 100644 (file)
@@ -18,3 +18,4 @@ module code_gen
 import frontend
 import actors_generation_phase
 import serialization_code_gen_phase
+import explain_assert
diff --git a/src/frontend/explain_assert.nit b/src/frontend/explain_assert.nit
new file mode 100644 (file)
index 0000000..326f588
--- /dev/null
@@ -0,0 +1,256 @@
+# 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.
+
+# Explain failed assert to the console by modifying the AST.
+#
+# This module implements the service `AAssertExpr::explain_assert_str`,
+# which should be used by the engines.
+#
+# Example assert:
+#
+# ~~~nitish
+# var x = 0.0
+# var y = 1.0
+# assert x.is_approx(y, 0.5)
+# ~~~
+#
+# Produces the following output on failure:
+#
+# ~~~raw
+# Runtime assert: 0.0.is_approx(1.0, 0.5)
+# ~~~
+module explain_assert
+
+import astbuilder
+intrude import literal # for value=
+intrude import typing # for mtype=
+import astvalidation
+
+import explain_assert_api
+
+redef class ToolContext
+
+       # Phase modifying the AST to explain assets when they fail
+       var explain_assert_phase: Phase = new ExplainAssertPhase(self, [modelize_class_phase, typing_phase, literal_phase])
+end
+
+private class ExplainAssertPhase
+       super Phase
+
+       redef fun process_nmodule(nmodule)
+       do
+               var mmodule = nmodule.mmodule
+               if mmodule == null then return
+
+               # Skip if `mmodule` doesn't have access to `String`
+               var string_class = toolcontext.modelbuilder.try_get_mclass_by_name(nmodule, mmodule, "String")
+               if string_class == null then return
+
+               # Launch a visitor on all elements of the AST
+               var visitor = new ExplainAssertVisitor(toolcontext, mmodule, string_class.mclass_type)
+               visitor.enter_visit nmodule
+       end
+end
+
+# Visitor to find and explain asserts
+private class ExplainAssertVisitor
+       super Visitor
+
+       # The toolcontext is our entry point to most services
+       var toolcontext: ToolContext
+
+       # The visited module
+       var mmodule: MModule
+
+       # Type of `String` (the generated code does not work without a `String`)
+       var string_mtype: MType
+
+       # Tool to modify the AST
+       var builder = new ASTBuilder(mmodule) is lazy
+
+       redef fun visit(node)
+       do
+               # Recursively visit all sub-nodes
+               node.visit_all(self)
+
+               # Only work on asserts
+               if not node isa AAssertExpr then return
+               var expr = node.n_expr
+
+               # Skip assert on a single boolean var and asserts on false:
+               # ~~~
+               # assert false
+               # # or
+               # var x = false # Or any boolean expression
+               # assert x
+               # ~~~
+               if expr isa AVarExpr or expr isa AFalseExpr then return
+
+               # Build the superstring to explain the assert
+               var explain_str = new ASuperstringExpr
+
+               # Prepare attribute used by visited nodes
+               self.assert_node = node
+               self.explain_str = explain_str
+               expr.accept_explain_assert self
+
+               # Done! Store the superstring in the assert's node
+               if explain_str.n_exprs.not_empty then
+                       node.explain_assert_str = explain_str
+               end
+       end
+
+       # Visited assert node
+       var assert_node: AAssertExpr is noinit
+
+       # Superstring in construction to explain the `assert_node`
+       var explain_str: ASuperstringExpr is noinit
+
+       # Build an `AStringExpr` containing `value`
+       #
+       # Add it to `explain_str` if `auto_add == true`, the default.
+       fun explain_string(value: String, auto_add: nullable Bool): AStringExpr
+       do
+               auto_add = auto_add or else true
+
+               var tk = new TString
+               tk.text = "\"{value}\""
+               var op = new AStringExpr
+               op.n_string = tk
+               op.mtype = string_mtype
+               op.value = value
+               op.location = assert_node.location
+
+               if auto_add then explain_str.n_exprs.add op
+               return op
+       end
+
+       # Add the value of `v_expr` to `explain_str` and protect null values
+       fun explain_expr(v_expr: AExpr)
+       do
+               var mtype = v_expr.mtype
+               if mtype == null then
+                       explain_string "<unexpected error>"
+                       return
+               end
+
+               # Set the expression value aside
+               var old_parent = v_expr.parent
+               var expr = v_expr.make_var_read
+               if old_parent != null then old_parent.validate
+
+               # Protect nullable types
+               if mtype isa MNullType then
+                       explain_string "null"
+                       return
+               else if mtype isa MNullableType then
+                       var e = new AOrElseExpr
+                       e.n_expr = expr
+                       e.n_expr2 = explain_string("null", false)
+                       e.location = assert_node.location
+                       e.mtype = mmodule.object_type
+
+                       explain_str.n_exprs.add e
+                       return
+               end
+
+               explain_str.n_exprs.add expr
+       end
+
+       # Add all the arguments in `AExprs` to `explain_str`
+       fun explain_args(n_args: AExprs)
+       do
+               var first = true
+               for n_arg in n_args.to_a do
+                       if not first then
+                               explain_string ", "
+                       else first = false
+
+                       explain_expr n_arg
+               end
+       end
+end
+
+redef class AAssertExpr
+       redef var explain_assert_str = null
+end
+
+redef class AExpr
+       # Fill `v` to explain this node if the parent assert fails
+       private fun accept_explain_assert(v: ExplainAssertVisitor)
+       do if mtype != null then v.explain_expr self
+end
+
+redef class ABinopExpr
+       redef fun accept_explain_assert(v)
+       do
+               if n_expr.mtype == null or n_expr2.mtype == null then return
+
+               v.explain_expr n_expr
+               v.explain_string " {n_op.text} "
+               v.explain_expr n_expr2
+       end
+end
+
+redef class ACallExpr
+       redef fun accept_explain_assert(v)
+       do
+               if n_expr.mtype == null then return
+
+               v.explain_expr n_expr
+               v.explain_string ".{n_qid.n_id.text}"
+
+               if n_args.to_a.not_empty then
+                       v.explain_string "("
+                       v.explain_args n_args
+                       v.explain_string ")"
+               end
+       end
+end
+
+redef class ABraExpr
+       redef fun accept_explain_assert(v)
+       do
+               if n_expr.mtype == null then return
+
+               v.explain_expr n_expr
+               v.explain_string "["
+               v.explain_args n_args
+               v.explain_string "]"
+       end
+end
+
+redef class AIsaExpr
+       redef fun accept_explain_assert(v)
+       do
+               if n_expr.mtype == null then return
+
+               v.explain_expr n_expr
+               v.explain_string " {n_kwisa.text} "
+               v.explain_string n_type.collect_text
+       end
+end
+
+redef class ANotExpr
+       redef fun accept_explain_assert(v)
+       do
+               v.explain_string "{n_kwnot.text} "
+               n_expr.accept_explain_assert v
+       end
+end
+
+redef class ABinBoolExpr
+       # Don't explain the conditions using `and`, `or`, etc.
+       redef fun accept_explain_assert(v) do end
+end
diff --git a/src/frontend/explain_assert_api.nit b/src/frontend/explain_assert_api.nit
new file mode 100644 (file)
index 0000000..6b36713
--- /dev/null
@@ -0,0 +1,28 @@
+# 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.
+
+# Explain failed assert to the console (service declaration only)
+#
+# The only service `assert_expr_str` is implemented by the
+# `explain_assert` module.
+module explain_assert_api
+
+import parser
+
+redef class AAssertExpr
+       # Superstring explaining `self` if the assert fails
+       #
+       # Engines should print out this superstring.
+       fun explain_assert_str: nullable ASuperstringExpr do return null
+end
index 6c86223..ac7b970 100644 (file)
@@ -23,6 +23,7 @@ private import parser::tables
 import mixin
 import primitive_types
 private import model::serialize_model
+private import frontend::explain_assert_api
 
 redef class ToolContext
        # --discover-call-trace
@@ -1880,6 +1881,22 @@ redef class AAssertExpr
                if not cond.is_true then
                        v.stmt(self.n_else)
                        if v.is_escaping then return
+
+                       # Explain assert if it fails
+                       var explain_assert_str = explain_assert_str
+                       if explain_assert_str != null then
+                               var i = v.expr(explain_assert_str)
+                               if i isa MutableInstance then
+                                       var res = v.send(v.force_get_primitive_method("to_cstring", i.mtype), [i])
+                                       if res != null then
+                                               var val = res.val
+                                               if val != null then
+                                                       print_error "Runtime assert: {val.to_s}"
+                                               end
+                                       end
+                               end
+                       end
+
                        var nid = self.n_id
                        if nid != null then
                                fatal(v, "Assert '{nid.text}' failed")
index b312543..08a50fd 100644 (file)
@@ -43,3 +43,6 @@ test_rubix_visual
 test_rubix_cube
 test_csv
 repeating_key_xor_solve
+test_explain_assert
+base_notnull_lit_alt2
+assertions
index d75c441..ee2f3b6 100644 (file)
@@ -1 +1,2 @@
+Runtime assert: 5 == 42
 Runtime error: Assert failed (../examples/rosettacode/assertions.nit:11)
index 522489c..f6076e4 100644 (file)
@@ -1 +1,2 @@
+Runtime assert: 5 == null
 Runtime error: Assert failed (alt/base_notnull_lit_alt2.nit:19)
index 18de7be..404c270 100644 (file)
@@ -31,6 +31,7 @@ test_nitunit4/test_bad_comp2.nit:17,7--22: Error: a class named `test_nitunit4::
        Before Test
        Tested method
        After Test
+       Runtime assert: <TestTestSuite>.before
        Runtime error: Assert failed (test_nitunit4/test_nitunit4_base.nit:28)
 
 [OK] test_nitunit4$TestTestSuite$test_bar
@@ -54,7 +55,7 @@ test_nitunit4/test_bad_comp2.nit:17,7--22: Error: a class named `test_nitunit4::
        After Test
 
 
-Docunits: Entities: 21; Documented ones: 0; With nitunits: 0
+Docunits: Entities: 22; Documented ones: 0; With nitunits: 0
 Test suites: Classes: 3; Test Cases: 8; Failures: 7
 [FAILURE] 7/8 tests failed.
 `nitunit.out` is not removed for investigation.
@@ -65,6 +66,7 @@ Test suites: Classes: 3; Test Cases: 8; Failures: 7
 </failure></testcase></testsuite><testsuite package="test_nitunit4::test_nitunit4"></testsuite><testsuite package="test_nitunit4"><testcase classname="nitunit.test_nitunit4.TestTestSuite" name="test_foo" time="0.0"><error message="Runtime Error in file nitunit.out&#47;gen_test_nitunit4.nit">Before Test
 Tested method
 After Test
+Runtime assert: &lt;TestTestSuite&gt;.before
 Runtime error: Assert failed (test_nitunit4&#47;test_nitunit4_base.nit:28)
 </error></testcase><testcase classname="nitunit.test_nitunit4.TestTestSuite" name="test_bar" time="0.0"><system-err>Before Test
 Tested method
diff --git a/tests/sav/test_c_alt4.res b/tests/sav/test_c_alt4.res
deleted file mode 100644 (file)
index e5246df..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-Runtime error: Assert failed (../lib/c.nit:37)
-0
-0
-1
-2
-3
-4
-[0,1,2,3,4]
diff --git a/tests/sav/test_explain_assert.res b/tests/sav/test_explain_assert.res
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tests/sav/test_explain_assert_alt1.res b/tests/sav/test_explain_assert_alt1.res
new file mode 100644 (file)
index 0000000..46fac94
--- /dev/null
@@ -0,0 +1,2 @@
+Runtime assert: 1 == some string
+Runtime error: Assert failed (alt/test_explain_assert_alt1.nit:27)
diff --git a/tests/sav/test_explain_assert_alt10.res b/tests/sav/test_explain_assert_alt10.res
new file mode 100644 (file)
index 0000000..048a8cb
--- /dev/null
@@ -0,0 +1,2 @@
+Runtime error: Assert failed (alt/test_explain_assert_alt10.nit:46)
+foo
diff --git a/tests/sav/test_explain_assert_alt11.res b/tests/sav/test_explain_assert_alt11.res
new file mode 100644 (file)
index 0000000..4ff007a
--- /dev/null
@@ -0,0 +1,2 @@
+Runtime assert: not 1 == 1
+Runtime error: Assert failed (alt/test_explain_assert_alt11.nit:48)
diff --git a/tests/sav/test_explain_assert_alt12.res b/tests/sav/test_explain_assert_alt12.res
new file mode 100644 (file)
index 0000000..0866f9f
--- /dev/null
@@ -0,0 +1,2 @@
+Runtime assert: 4 isa MyClass
+Runtime error: Assert failed (alt/test_explain_assert_alt12.nit:49)
diff --git a/tests/sav/test_explain_assert_alt13.res b/tests/sav/test_explain_assert_alt13.res
new file mode 100644 (file)
index 0000000..f5fa81d
--- /dev/null
@@ -0,0 +1 @@
+Runtime error: Assert failed (alt/test_explain_assert_alt13.nit:52)
diff --git a/tests/sav/test_explain_assert_alt2.res b/tests/sav/test_explain_assert_alt2.res
new file mode 100644 (file)
index 0000000..0b3893c
--- /dev/null
@@ -0,0 +1,3 @@
+alt/test_explain_assert_alt2.nit:28,8: Warning: expression is not null, since it is a `Int`.
+Runtime assert: 1 == null
+Runtime error: Assert failed (alt/test_explain_assert_alt2.nit:28)
diff --git a/tests/sav/test_explain_assert_alt3.res b/tests/sav/test_explain_assert_alt3.res
new file mode 100644 (file)
index 0000000..b1f0b91
--- /dev/null
@@ -0,0 +1,3 @@
+Runtime assert: 12 == null
+Runtime error: Assert failed (alt/test_explain_assert_alt3.nit:29)
+foo
diff --git a/tests/sav/test_explain_assert_alt4.res b/tests/sav/test_explain_assert_alt4.res
new file mode 100644 (file)
index 0000000..9cea4a2
--- /dev/null
@@ -0,0 +1,3 @@
+Runtime assert: null != null
+Runtime error: Assert failed (alt/test_explain_assert_alt4.nit:30)
+foo
diff --git a/tests/sav/test_explain_assert_alt5.res b/tests/sav/test_explain_assert_alt5.res
new file mode 100644 (file)
index 0000000..51dd34c
--- /dev/null
@@ -0,0 +1,2 @@
+Runtime assert: 0.0.is_approx(1.0, 0.5)
+Runtime error: Assert failed (alt/test_explain_assert_alt5.nit:34)
diff --git a/tests/sav/test_explain_assert_alt6.res b/tests/sav/test_explain_assert_alt6.res
new file mode 100644 (file)
index 0000000..d94a552
--- /dev/null
@@ -0,0 +1,2 @@
+Runtime assert: not true
+Runtime error: Assert failed (alt/test_explain_assert_alt6.nit:36)
diff --git a/tests/sav/test_explain_assert_alt7.res b/tests/sav/test_explain_assert_alt7.res
new file mode 100644 (file)
index 0000000..fcbe1e8
--- /dev/null
@@ -0,0 +1,2 @@
+Runtime assert: [true,false,true][1]
+Runtime error: Assert failed (alt/test_explain_assert_alt7.nit:39)
diff --git a/tests/sav/test_explain_assert_alt8.res b/tests/sav/test_explain_assert_alt8.res
new file mode 100644 (file)
index 0000000..692a365
--- /dev/null
@@ -0,0 +1,2 @@
+Runtime assert: [0,1,2].is_empty
+Runtime error: Assert failed (alt/test_explain_assert_alt8.nit:41)
diff --git a/tests/sav/test_explain_assert_alt9.res b/tests/sav/test_explain_assert_alt9.res
new file mode 100644 (file)
index 0000000..9bad256
--- /dev/null
@@ -0,0 +1,2 @@
+Runtime assert: <MyClass i:12 s:asdf> == 0
+Runtime error: Assert failed (alt/test_explain_assert_alt9.nit:43)
index ce7dc97..500c42f 100644 (file)
@@ -32,4 +32,4 @@ for i in ci do print i
 print ci.to_a
 
 ci.destroy
-#alt4#print ci[0]
+assert ci.destroyed
diff --git a/tests/test_explain_assert.nit b/tests/test_explain_assert.nit
new file mode 100644 (file)
index 0000000..7ebed4c
--- /dev/null
@@ -0,0 +1,52 @@
+# 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.
+
+class MyClass
+       var i = 12
+       var s = "asdf"
+       redef fun to_s do return "<{class_name} i:{i} s:{s}>"
+end
+
+fun foo(v: nullable Int): nullable Int do
+       print "foo"
+       return v
+end
+
+assert 1 == 1
+#alt1#assert 1 == "some string"
+#alt2#assert 1 == null
+#alt3#assert foo(12) == null
+#alt4#assert foo(null) != null
+
+#alt5#var x = 0.0
+#alt5#var y = 1.0
+#alt5#assert x.is_approx(y, 0.5)
+
+#alt6#assert not true
+
+#alt7#var a = [true, false, true]
+#alt7#assert a[1]
+
+#alt8#assert [0, 1, 2].is_empty
+
+#alt9#assert (new MyClass) == 0
+
+#alt10#var n = foo(null)
+#alt10#assert n != null and n.to_s == "crash" # Not explained
+
+#alt11#assert not 1 == 1
+#alt12#assert 4 isa MyClass
+
+#alt13#var f = false
+#alt13#assert f # Not explained
index dfb5e32..cf8ab83 100644 (file)
@@ -37,4 +37,6 @@ class TestTestSuite
        fun test_sav_conflict is test do
                print "Tested method"
        end
+
+       redef fun to_s do return "<{class_name}>"
 end