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 expr
= v_expr
.make_var_read
150 # Protect nullable types
151 if mtype
isa MNullType then
152 explain_string
"null"
154 else if mtype
isa MNullableType then
155 var e
= new AOrElseExpr
157 e
.n_expr2
= explain_string
("null", false)
158 e
.location
= assert_node
.location
159 e
.mtype
= mmodule
.object_type
161 explain_str
.n_exprs
.add e
165 explain_str
.n_exprs
.add expr
168 # Add all the arguments in `AExprs` to `explain_str`
169 fun explain_args
(n_args
: AExprs)
172 for n_arg
in n_args
.to_a
do
182 redef class AAssertExpr
183 redef var explain_assert_str
= null
187 # Fill `v` to explain this node if the parent assert fails
188 private fun accept_explain_assert
(v
: ExplainAssertVisitor)
189 do if mtype
!= null then v
.explain_expr
self
192 redef class ABinopExpr
193 redef fun accept_explain_assert
(v
)
195 if n_expr
.mtype
== null or n_expr2
.mtype
== null then return
197 v
.explain_expr n_expr
198 v
.explain_string
" {n_op.text} "
199 v
.explain_expr n_expr2
203 redef class ACallExpr
204 redef fun accept_explain_assert
(v
)
206 if n_expr
.mtype
== null then return
208 v
.explain_expr n_expr
209 v
.explain_string
".{n_qid.n_id.text}"
211 if n_args
.to_a
.not_empty
then
213 v
.explain_args n_args
220 redef fun accept_explain_assert
(v
)
222 if n_expr
.mtype
== null then return
224 v
.explain_expr n_expr
226 v
.explain_args n_args
232 redef fun accept_explain_assert
(v
)
234 if n_expr
.mtype
== null then return
236 v
.explain_expr n_expr
237 v
.explain_string
" {n_kwisa.text} "
238 v
.explain_string n_type
.collect_text
243 redef fun accept_explain_assert
(v
)
245 v
.explain_string
"{n_kwnot.text} "
246 n_expr
.accept_explain_assert v
250 redef class ABinBoolExpr
251 # Don't explain the conditions using `and`, `or`, etc.
252 redef fun accept_explain_assert
(v
) do end