lib/github: introduces object oriented interface for GithubAPI
[nit.git] / src / metrics / detect_variance_constraints.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
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
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
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.
14
15 # Collect metrics about detected variances constraints on formal types.
16 #
17 # In some OO-language that feature safe invariant generics by default,
18 # formal parameter types can annotated to be usable in covariant and contravariant positions.
19 # The price to pay is that the usage of formal parameter types in the class has constraints:
20 #
21 # * covariant-annotated formal types cannot be used in parameters or in attributes
22 # * contravariant-annotated formal types cannot be used in return or in attributes
23 #
24 # This module provide a backward analysis that infers the possible variance annotations
25 # of formal types in Nit programs by identifying the existing constraints on the usage of those formal type.
26 #
27 # It collects the types used in the signatures of properties then propagates constraints between types.
28 module detect_variance_constraints
29
30 import model
31 import metrics_base
32
33 redef class ToolContext
34 # --detect-variance-constraints
35 var opt_detect_variance_constraints = new OptionBool("Detect the definition-site variance constraints on formal parameters", "--detect-variance-constraints")
36
37 # The DetectVarianceConstraints phase
38 var detect_variance_constraints_phase: Phase = new DetectVarianceConstraintsPhase(self, null)
39 end
40
41 private class DetectVarianceConstraintsPhase
42 super Phase
43
44 init
45 do
46 toolcontext.option_context.add_option(toolcontext.opt_detect_variance_constraints)
47 end
48
49 redef fun process_mainmodule(mainmodule, given_mmodules)
50 do
51 if not toolcontext.opt_detect_variance_constraints.value and not toolcontext.opt_all.value then return
52
53 print "--- Metrics of covariance detection ---"
54
55 var k = new DetectVarianceConstraints
56 k.collect(mainmodule)
57
58 print "-- Generic classes --"
59 k.cpt_class.print_elements(10)
60 print " total classes: {k.cpt_class.sum}"
61 print " total formal parameters: {k.pts.length}"
62
63 k.propagate
64
65 print "-- Including `private` properties --"
66 k.print_stats
67
68 k = new DetectVarianceConstraints
69 k.exclude_private = true
70 k.collect(mainmodule)
71
72 k.propagate
73
74 print "-- Excluding `private` properties --"
75 k.print_stats
76 end
77 end
78
79 redef class MParameterType
80 # The fully-qualified name of the formal parameter.
81 fun full_name: String do return "{mclass.full_name}::{name}"
82 end
83
84 # A specific analysis that detects the variance constraints of formal parameters.
85 #
86 # The client has 3 steps to do:
87 #
88 # * call `collect` to initialize the attributes.
89 # * call `propagate` to propagate the variance constraints.
90 # * call `print_stats` to print the results.
91 class DetectVarianceConstraints
92 # Collect all types used in covariant and contravariant positions.
93 #
94 # The collect visits all classes and properties of `mainmodule` and its imported modules.
95 #
96 # After the visit, the attributes of `self` are filled.
97 fun collect(mainmodule: MModule)
98 do
99 for m in mainmodule.in_importation.greaters do
100 for cd in m.mclassdefs do
101 if cd.is_intro then
102 pts.add_all(cd.mclass.mparameters)
103 var a = cd.mclass.arity
104 if a == 0 then
105 cpt_class.inc("non generic")
106 else if a == 1 then
107 cpt_class.inc("with 1 formal type parameter")
108 else
109 cpt_class.inc("with {a} formal type parameters")
110 end
111 end
112 for t in cd.supertypes do
113 # Supertype (covariant)
114 if t.need_anchor then covar_classes.add(t)
115 end
116 for pd in cd.mpropdefs do
117 if exclude_private and pd.mproperty.visibility <= private_visibility then continue
118 if pd isa MMethodDef then
119 # Parameters (contravariant)
120 for p in pd.msignature.mparameters do
121 var t = p.mtype.as_notnullable
122 if not t.need_anchor then
123 # OK
124 else if t isa MParameterType then
125 contravar_pt.add(t)
126 else if t isa MVirtualType then
127 # TODO?
128 else if t isa MClassType then
129 contravar_classes.add(t)
130 else
131 abort
132 end
133 end
134 # Return (covariant)
135 var t = pd.msignature.return_mtype
136 if t != null and t.need_anchor then
137 t = t.as_notnullable
138 if t isa MParameterType then
139 covar_pt.add(t)
140 else if t isa MVirtualType then
141 # TODO?
142 else if t isa MClassType then
143 covar_classes.add(t)
144 else
145 abort
146 end
147 end
148 else if pd isa MAttributeDef then
149 # Attribute (invariant)
150 var t = pd.static_mtype
151 if t != null and t.need_anchor then
152 t = t.as_notnullable
153 if t isa MParameterType then
154 covar_pt.add t
155 contravar_pt.add t
156 else if t isa MVirtualType then
157 # TODO?
158 else if t isa MClassType then
159 covar_classes.add(t)
160 contravar_classes.add(t)
161 else
162 abort
163 end
164 end
165 else if pd isa MVirtualTypeDef then
166 # Virtual type bound (covariant)
167 var t = pd.bound
168 if t != null and t.need_anchor then
169 t = t.as_notnullable
170 if t isa MParameterType then
171 covar_pt.add t
172 else if t isa MVirtualType then
173 # TODO?
174 else if t isa MClassType then
175 covar_classes.add(t)
176 else
177 abort
178 end
179 end
180 end
181 end
182 end
183 end
184 end
185
186 # The set of all collected formal parameters
187 var pts = new HashSet[MParameterType]
188
189 # The set of generic types found in a covariant (and invariant) position
190 var covar_classes = new HashSet[MClassType]
191
192 # The set of formal parameters found in a covariant (and invariant) position
193 var covar_pt = new HashSet[MParameterType]
194
195 # The set of generic types found in a contravariant (and invariant) position
196 var contravar_classes = new HashSet[MClassType]
197
198 # The set of formal parameters found in a contravariant (and invariant) position
199 var contravar_pt = new HashSet[MParameterType]
200
201 # Classes by number of formal parameters
202 var cpt_class = new Counter[String]
203
204 # Does the collect exclude private properties?
205 # Default is `false`
206 var exclude_private = false
207
208 # Propagate the variance constraints on `covar_classes`, `covar_pt`, `contravar_classes` and `contravar_pt`
209 #
210 # The algorithm uses a fixed-point approach on the covariance/contravariance rules.
211 fun propagate
212 do
213 # Classes to add to the `covar_classes` set at the end of an iteration
214 var new_covar = new Array[MClassType]
215 # Classes to add to the `contravar_classes` set at the end of an iteration
216 var new_contravar = new Array[MClassType]
217 # Does a modification occurred, so that another iteration is needed?
218 var dirty = true
219 # Total number of iterations
220 var cpt = 0
221
222 while dirty do
223 cpt += 1
224 dirty = false
225 new_covar.clear
226 new_contravar.clear
227
228 # Process the generic types in a covariant position
229 for c in covar_classes do for i in [0..c.mclass.arity[ do
230 # The type used in the argument
231 var ta = c.arguments[i].as_notnullable
232 # The associated formal parameter
233 var tp = c.mclass.mparameters[i]
234
235 if not ta.need_anchor then
236 # Nothing to do
237 else if ta isa MParameterType then
238 # COVAR * COVAR = COVAR
239 if covar_pt.has(tp) and not covar_pt.has(ta) then
240 covar_pt.add(ta)
241 dirty = true
242 end
243 # COVAR * CONTRAVAR = CONTRAVAR
244 if contravar_pt.has(tp) and not contravar_pt.has(ta) then
245 contravar_pt.add(ta)
246 dirty = true
247 end
248 else if ta isa MVirtualType then
249 # TODO?
250 else if ta isa MClassType then
251 # COVAR * COVAR = COVAR
252 if covar_pt.has(tp) and not covar_classes.has(ta) then
253 new_covar.add ta
254 dirty = true
255 end
256 # COVAR * CONTRAVAR = CONTRAVAR
257 if contravar_pt.has(tp) and not contravar_classes.has(ta) then
258 new_contravar.add ta
259 dirty = true
260 end
261 end
262 end
263
264 # Process the generic types in a contravariant position
265 for c in contravar_classes do for i in [0..c.mclass.arity[ do
266 # The type used in the argument
267 var ta = c.arguments[i].as_notnullable
268 # The associated formal parameter
269 var tp = c.mclass.mparameters[i]
270
271 if not ta.need_anchor then
272 # Nothing to do
273 else if ta isa MParameterType then
274 # CONTRAVAR * CONTRAVAR = COVAR
275 if contravar_pt.has(tp) and not covar_pt.has(ta) then
276 covar_pt.add(ta)
277 dirty = true
278 end
279 # CONTRAVAR * COVAR = CONTRAVAR
280 if covar_pt.has(tp) and not contravar_pt.has(ta) then
281 contravar_pt.add(ta)
282 dirty = true
283 end
284 else if ta isa MVirtualType then
285 # TODO?
286 else if ta isa MClassType then
287 # CONTRAVAR * CONTRAVAR = COVAR
288 if contravar_pt.has(tp) and not covar_classes.has(ta) then
289 new_covar.add ta
290 dirty = true
291 end
292 # CONTRAVAR * COVAR = CONTRAVAR
293 if covar_pt.has(tp) and not contravar_classes.has(ta) then
294 new_contravar.add ta
295 dirty = true
296 end
297 end
298 end
299
300 covar_classes.add_all(new_covar)
301 contravar_classes.add_all(new_contravar)
302 end
303 end
304
305 # Print the final stats on the screen
306 fun print_stats
307 do
308 var nb_cov = 0
309 var nb_con = 0
310 var nb_inv = 0
311 var nb_biv = 0
312
313 for pt in pts do
314 if covar_pt.has(pt) then
315 if contravar_pt.has(pt) then
316 nb_inv += 1
317 else
318 nb_cov += 1
319 #print "covar: {pt.full_name}"
320 end
321 else
322 if contravar_pt.has(pt) then
323 nb_con += 1
324 #print "contravar: {pt.full_name}"
325 else
326 nb_biv += 1
327 #print "bivar: {pt.full_name}"
328 end
329 end
330 end
331
332 print " covariants: {nb_cov} ({div(nb_cov*100, pts.length)}%)"
333 print " contravariants: {nb_con} ({div(nb_con*100, pts.length)}%)"
334 print " bivariants: {nb_biv} ({div(nb_biv*100, pts.length)}%)"
335 print " invariants: {nb_inv} ({div(nb_inv*100, pts.length)}%)"
336 print " total: {pts.length}"
337 end
338 end