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=
39 import explain_assert_api
41 redef class ToolContext
43 # Phase modifying the AST to explain assets when they fail
44 var explain_assert_phase
: Phase = new ExplainAssertPhase(self, [modelize_class_phase
, typing_phase
, literal_phase
])
47 private class ExplainAssertPhase
50 redef fun process_nmodule
(nmodule
)
52 var mmodule
= nmodule
.mmodule
53 if mmodule
== null then return
55 # Skip if `mmodule` doesn't have access to `String`
56 var string_class
= toolcontext
.modelbuilder
.try_get_mclass_by_name
(nmodule
, mmodule
, "String")
57 if string_class
== null then return
59 # Launch a visitor on all elements of the AST
60 var visitor
= new ExplainAssertVisitor(toolcontext
, mmodule
, string_class
.mclass_type
)
61 visitor
.enter_visit nmodule
65 # Visitor to find and explain asserts
66 private class ExplainAssertVisitor
69 # The toolcontext is our entry point to most services
70 var toolcontext
: ToolContext
75 # Type of `String` (the generated code does not work without a `String`)
76 var string_mtype
: MType
78 # Tool to modify the AST
79 var builder
= new ASTBuilder(mmodule
) is lazy
83 # Recursively visit all sub-nodes
86 # Only work on asserts
87 if not node
isa AAssertExpr then return
88 var expr
= node
.n_expr
90 # Skip assert on a single boolean var and asserts on false:
94 # var x = false # Or any boolean expression
97 if expr
isa AVarExpr or expr
isa AFalseExpr then return
99 # Build the superstring to explain the assert
100 var explain_str
= new ASuperstringExpr
102 # Prepare attribute used by visited nodes
103 self.assert_node
= node
104 self.explain_str
= explain_str
105 expr
.accept_explain_assert
self
107 # Done! Store the superstring in the assert's node
108 if explain_str
.n_exprs
.not_empty
then
109 node
.explain_assert_str
= explain_str
113 # Visited assert node
114 var assert_node
: AAssertExpr is noinit
116 # Superstring in construction to explain the `assert_node`
117 var explain_str
: ASuperstringExpr is noinit
119 # Build an `AStringExpr` containing `value`
121 # Add it to `explain_str` if `auto_add == true`, the default.
122 fun explain_string
(value
: String, auto_add
: nullable Bool): AStringExpr
124 auto_add
= auto_add
or else true
127 tk
.text
= "\"{value}\
""
128 var op
= new AStringExpr
130 op
.mtype
= string_mtype
132 op
.location
= assert_node
.location
134 if auto_add
then explain_str
.n_exprs
.add op
138 # Add the value of `v_expr` to `explain_str` and protect null values
139 fun explain_expr
(v_expr
: AExpr)
141 var mtype
= v_expr
.mtype
142 if mtype
== null then
143 explain_string
"<unexpected error>"
147 # Set the expression value aside
148 var old_parent
= v_expr
.parent
149 var expr
= v_expr
.make_var_read
150 if old_parent
!= null then old_parent
.validate
152 # Protect nullable types
153 if mtype
isa MNullType then
154 explain_string
"null"
156 else if mtype
isa MNullableType then
157 var e
= new AOrElseExpr
159 e
.n_expr2
= explain_string
("null", false)
160 e
.location
= assert_node
.location
161 e
.mtype
= mmodule
.object_type
163 explain_str
.n_exprs
.add e
167 explain_str
.n_exprs
.add expr
170 # Add all the arguments in `AExprs` to `explain_str`
171 fun explain_args
(n_args
: AExprs)
174 for n_arg
in n_args
.to_a
do
184 redef class AAssertExpr
185 redef var explain_assert_str
= null
189 # Fill `v` to explain this node if the parent assert fails
190 private fun accept_explain_assert
(v
: ExplainAssertVisitor)
191 do if mtype
!= null then v
.explain_expr
self
194 redef class ABinopExpr
195 redef fun accept_explain_assert
(v
)
197 if n_expr
.mtype
== null or n_expr2
.mtype
== null then return
199 v
.explain_expr n_expr
200 v
.explain_string
" {n_op.text} "
201 v
.explain_expr n_expr2
205 redef class ACallExpr
206 redef fun accept_explain_assert
(v
)
208 if n_expr
.mtype
== null then return
210 v
.explain_expr n_expr
211 v
.explain_string
".{n_qid.n_id.text}"
213 if n_args
.to_a
.not_empty
then
215 v
.explain_args n_args
222 redef fun accept_explain_assert
(v
)
224 if n_expr
.mtype
== null then return
226 v
.explain_expr n_expr
228 v
.explain_args n_args
234 redef fun accept_explain_assert
(v
)
236 if n_expr
.mtype
== null then return
238 v
.explain_expr n_expr
239 v
.explain_string
" {n_kwisa.text} "
240 v
.explain_string n_type
.collect_text
245 redef fun accept_explain_assert
(v
)
247 v
.explain_string
"{n_kwnot.text} "
248 n_expr
.accept_explain_assert v
252 redef class ABinBoolExpr
253 # Don't explain the conditions using `and`, `or`, etc.
254 redef fun accept_explain_assert
(v
) do end