1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
15 # Explain failed assert to the console by modifying the AST.
17 # This module implements the service `AAssertExpr::explain_assert_str`,
18 # which should be used by the engines.
25 # assert x.is_approx(y, 0.5)
28 # Produces the following output on failure:
31 # Runtime assert: 0.0.is_approx(1.0, 0.5)
36 intrude import literal
# for value=
37 intrude import typing
# for mtype=
40 import explain_assert_api
42 redef class ToolContext
44 # Phase modifying the AST to explain assets when they fail
45 var explain_assert_phase
: Phase = new ExplainAssertPhase(self, [modelize_class_phase
, typing_phase
, literal_phase
])
48 private class ExplainAssertPhase
51 redef fun process_nmodule
(nmodule
)
53 var mmodule
= nmodule
.mmodule
54 if mmodule
== null then return
56 # Skip if `mmodule` doesn't have access to `String`
57 var string_class
= toolcontext
.modelbuilder
.try_get_mclass_by_name
(nmodule
, mmodule
, "String")
58 if string_class
== null then return
60 # Launch a visitor on all elements of the AST
61 var visitor
= new ExplainAssertVisitor(toolcontext
, mmodule
, string_class
.mclass_type
)
62 visitor
.enter_visit nmodule
66 # Visitor to find and explain asserts
67 private class ExplainAssertVisitor
70 # The toolcontext is our entry point to most services
71 var toolcontext
: ToolContext
76 # Type of `String` (the generated code does not work without a `String`)
77 var string_mtype
: MType
79 # Tool to modify the AST
80 var builder
= new ASTBuilder(mmodule
) is lazy
84 # Recursively visit all sub-nodes
87 # Only work on asserts
88 if not node
isa AAssertExpr then return
89 var expr
= node
.n_expr
91 # Skip assert on a single boolean var and asserts on false:
95 # var x = false # Or any boolean expression
98 if expr
isa AVarExpr or expr
isa AFalseExpr then return
100 # Build the superstring to explain the assert
101 var explain_str
= new ASuperstringExpr
103 # Prepare attribute used by visited nodes
104 self.assert_node
= node
105 self.explain_str
= explain_str
106 expr
.accept_explain_assert
self
108 # Done! Store the superstring in the assert's node
109 if explain_str
.n_exprs
.not_empty
then
110 node
.explain_assert_str
= explain_str
114 # Visited assert node
115 var assert_node
: AAssertExpr is noinit
117 # Superstring in construction to explain the `assert_node`
118 var explain_str
: ASuperstringExpr is noinit
120 # Build an `AStringExpr` containing `value`
122 # Add it to `explain_str` if `auto_add == true`, the default.
123 fun explain_string
(value
: String, auto_add
: nullable Bool): AStringExpr
125 auto_add
= auto_add
or else true
128 tk
.text
= "\"{value}\
""
129 var op
= new AStringExpr
131 op
.mtype
= string_mtype
133 op
.location
= assert_node
.location
135 if auto_add
then explain_str
.n_exprs
.add op
139 # Add the value of `v_expr` to `explain_str` and protect null values
140 fun explain_expr
(v_expr
: AExpr)
142 var mtype
= v_expr
.mtype
143 if mtype
== null then
144 explain_string
"<unexpected error>"
148 # Set the expression value aside
149 var old_parent
= v_expr
.parent
150 var expr
= v_expr
.make_var_read
151 if old_parent
!= null then old_parent
.validate
153 # Protect nullable types
154 if mtype
isa MNullType then
155 explain_string
"null"
157 else if mtype
isa MNullableType then
158 var e
= new AOrElseExpr
160 e
.n_expr2
= explain_string
("null", false)
161 e
.location
= assert_node
.location
162 e
.mtype
= mmodule
.object_type
164 explain_str
.n_exprs
.add e
168 explain_str
.n_exprs
.add expr
171 # Add all the arguments in `AExprs` to `explain_str`
172 fun explain_args
(n_args
: AExprs)
175 for n_arg
in n_args
.to_a
do
185 redef class AAssertExpr
186 redef var explain_assert_str
= null
190 # Fill `v` to explain this node if the parent assert fails
191 private fun accept_explain_assert
(v
: ExplainAssertVisitor)
192 do if mtype
!= null then v
.explain_expr
self
195 redef class ABinopExpr
196 redef fun accept_explain_assert
(v
)
198 if n_expr
.mtype
== null or n_expr2
.mtype
== null then return
200 v
.explain_expr n_expr
201 v
.explain_string
" {n_op.text} "
202 v
.explain_expr n_expr2
206 redef class ACallExpr
207 redef fun accept_explain_assert
(v
)
209 if n_expr
.mtype
== null then return
211 v
.explain_expr n_expr
212 v
.explain_string
".{n_qid.n_id.text}"
214 if n_args
.to_a
.not_empty
then
216 v
.explain_args n_args
223 redef fun accept_explain_assert
(v
)
225 if n_expr
.mtype
== null then return
227 v
.explain_expr n_expr
229 v
.explain_args n_args
235 redef fun accept_explain_assert
(v
)
237 if n_expr
.mtype
== null then return
239 v
.explain_expr n_expr
240 v
.explain_string
" {n_kwisa.text} "
241 v
.explain_string n_type
.collect_text
246 redef fun accept_explain_assert
(v
)
248 v
.explain_string
"{n_kwnot.text} "
249 n_expr
.accept_explain_assert v
253 redef class ABinBoolExpr
254 # Don't explain the conditions using `and`, `or`, etc.
255 redef fun accept_explain_assert
(v
) do end