annotation: add lookup_annotation_on_modules and related methods
[nit.git] / src / annotation.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 # Management and utilities on annotations
16 module annotation
17
18 import parser
19 import modelbuilder
20 import literal
21 import model::mmodule_data
22
23 redef class Prod
24 super ANode
25
26 # Try to get its single annotation with a given name
27 # If there is no such an annotation, null is returned.
28 # If there is more than one annotation, a error message is printed and the first annotation is returned
29 fun get_single_annotation(name: String, modelbuilder: ModelBuilder): nullable AAnnotation
30 do
31 var res = get_annotations(name)
32 if res.is_empty then return null
33 if res.length > 1 then
34 modelbuilder.error(res[1], "Error: multiple annotation `{name}`. A previous one is defined line {res[0].location.line_start}")
35 end
36 return res.first
37 end
38
39 # Return all its annotations of a given name in the order of their declaration
40 # Retun an empty array if no such an annotation.
41 fun get_annotations(name: String): Array[AAnnotation]
42 do
43 var res = new Array[AAnnotation]
44 var nas = n_annotations
45 if nas == null then return res
46 for na in nas.n_items do
47 if na.name != name then continue
48 res.add(na)
49 end
50 return res
51 end
52 end
53
54 redef class AAnnotation
55 # The name of the annotation
56 fun name: String
57 do
58 return n_atid.n_id.text
59 end
60
61 # Get the single argument of `self` as a `String`.
62 # Raise error and return null on any inconsistency.
63 fun arg_as_string(modelbuilder: ModelBuilder): nullable String
64 do
65 var args = n_args
66 if args.length == 1 then
67 var arg = args.first.as_string
68 if arg != null then return arg
69 end
70
71 modelbuilder.error(self, "Annotation error: \"{name}\" expects a single String as argument.")
72 return null
73 end
74
75 # Get the single argument of `self` as an `Int`.
76 # Raise error and return null on any inconsistency.
77 fun arg_as_int(modelbuilder: ModelBuilder): nullable Int
78 do
79 var args = n_args
80 if args.length == 1 then
81 var arg = args.first.as_int
82 if arg != null then return arg
83 end
84
85 modelbuilder.error(self, "Annotation error: \"{name}\" expects a single Int as argument.")
86 return null
87 end
88
89 # Get the single argument of `self` as an identifier.
90 # Raise error and return null on any inconsistency.
91 fun arg_as_id(modelbuilder: ModelBuilder): nullable String
92 do
93 var args = n_args
94 if args.length == 1 then
95 var arg = args.first.as_id
96 if arg != null then return arg
97 end
98
99 modelbuilder.error(self, "Annotation error: \"{name}\" expects a single identifier as argument.")
100 return null
101 end
102 end
103
104 redef class AAtArg
105 # Get `self` as a `String`.
106 # Return null if not a string.
107 fun as_string: nullable String
108 do
109 if not self isa AExprAtArg then return null
110 var nexpr = n_expr
111 if not nexpr isa AStringFormExpr then return null
112 return nexpr.value.as(not null)
113 end
114
115 # Get `self` as an `Int`.
116 # Return null if not an integer.
117 fun as_int: nullable Int
118 do
119 if not self isa AExprAtArg then return null
120 var nexpr = n_expr
121 if not nexpr isa AIntExpr then return null
122 return nexpr.value.as(not null)
123 end
124
125 # Get `self` as a single identifier.
126 # Return null if not a single identifier.
127 fun as_id: nullable String
128 do
129 if not self isa AExprAtArg then return null
130 var nexpr = n_expr
131 if not nexpr isa ACallExpr then return null
132 if not nexpr.n_expr isa AImplicitSelfExpr then return null
133 if not nexpr.n_args.n_exprs.is_empty then return null
134 return nexpr.n_id.text
135 end
136 end
137
138 redef class ModelBuilder
139 # Collect all annotations by `name` assocated to `mmodule` and its imported modules.
140 # Note that visibility is not considered.
141 fun collect_annotations_on_modules(name: String, mmodule: MModule): Array[AAnnotation]
142 do
143 var annotations = new Array[AAnnotation]
144 for mmod in mmodule.in_importation.greaters do
145 if not mmodule2nmodule.keys.has(mmod) then continue
146 var amod = mmodule2nmodule[mmod]
147 var module_decl = amod.n_moduledecl
148 if module_decl == null then continue
149 var aas = module_decl.get_annotations(name)
150 annotations.add_all aas
151 end
152 return annotations
153 end
154
155 # Return the single annotation `name` locally assocated to `mmodule`, if any.
156 # Obviously, if there is no ast associated to `mmodule`, then nothing is returned.
157 fun get_mmodule_annotation(name: String, mmodule: MModule): nullable AAnnotation
158 do
159 if not mmodule2nmodule.keys.has(mmodule) then return null
160 var amod = mmodule2nmodule[mmodule]
161 var module_decl = amod.n_moduledecl
162 if module_decl == null then return null
163 var res = module_decl.get_single_annotation(name, self)
164 return res
165 end
166
167 private var collect_annotations_data_cache = new HashMap[String, MModuleData[AAnnotation]]
168
169 # Collect all annotations by `name` in `mmodule` and its importations (direct and indirect)
170 # Note that visibility is not considered.
171 fun collect_annotations_data(name: String, mmodule: MModule): MModuleData[AAnnotation]
172 do
173 var res = collect_annotations_data_cache.get_or_null(name)
174 if res == null then
175 res = new MModuleData[AAnnotation](model)
176 collect_annotations_data_cache[name] = res
177 end
178
179 for mmod in mmodule.in_importation.greaters do
180 if res.has_mmodule(mmod) then continue
181 var ass = get_mmodule_annotation(name, mmod)
182 if ass == null then continue
183 res[mmod] = ass
184 end
185 return res
186 end
187
188 # Get an annotation by name from `mmodule` and its super modules. Will recursively search
189 # in imported module to find the "latest" declaration and detects priority conflicts.
190 fun lookup_annotation_on_modules(name: String, mmodule: MModule): nullable AAnnotation
191 do
192 var data = collect_annotations_data(name, mmodule)
193 var annotations = data.lookup_values(mmodule, none_visibility)
194 if annotations.is_empty then return null
195 if annotations.length > 1 then
196 var locs = new Array[Location]
197 for annot in annotations do locs.add(annot.location)
198
199 toolcontext.error(mmodule.location,
200 "Priority conflict on annotation {name}, it has been defined in: {locs.join(", ")}")
201 end
202 return annotations.first
203 end
204 end