Line data Source code
1 : /* valaproperty.vala
2 : *
3 : * Copyright (C) 2006-2012 Jürg Billeter
4 : *
5 : * This library is free software; you can redistribute it and/or
6 : * modify it under the terms of the GNU Lesser General Public
7 : * License as published by the Free Software Foundation; either
8 : * version 2.1 of the License, or (at your option) any later version.
9 :
10 : * This library is distributed in the hope that it will be useful,
11 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 : * Lesser General Public License for more details.
14 :
15 : * You should have received a copy of the GNU Lesser General Public
16 : * License along with this library; if not, write to the Free Software
17 : * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 : *
19 : * Author:
20 : * Jürg Billeter <j@bitron.ch>
21 : * Raffaele Sandrini <raffaele@sandrini.ch>
22 : */
23 :
24 : using GLib;
25 :
26 : /**
27 : * Represents a property declaration in the source code.
28 : */
29 452507 : public class Vala.Property : Symbol, Lockable {
30 : /**
31 : * The property type.
32 : */
33 : public DataType? property_type {
34 2680443 : get { return _data_type; }
35 852084 : set {
36 3381524 : _data_type = value;
37 852084 : if (value != null) {
38 852025 : _data_type.parent_node = this;
39 : }
40 : }
41 : }
42 :
43 : /**
44 : * The get accessor of this property if available.
45 : */
46 : public PropertyAccessor? get_accessor {
47 4121638 : get { return _get_accessor; }
48 870923 : set {
49 1292415 : _get_accessor = value;
50 870923 : if (value != null) {
51 421492 : value.owner = scope;
52 : }
53 : }
54 : }
55 :
56 : /**
57 : * The set/construct accessor of this property if available.
58 : */
59 : public PropertyAccessor? set_accessor {
60 3763726 : get { return _set_accessor; }
61 784742 : set {
62 1120053 : _set_accessor = value;
63 784742 : if (value != null) {
64 335311 : value.owner = scope;
65 : }
66 : }
67 : }
68 :
69 : /**
70 : * Represents the generated `this` parameter in this property.
71 : */
72 2257100 : public Parameter? this_parameter { get; set; }
73 :
74 : /**
75 : * Specifies whether automatic accessor code generation should be
76 : * disabled.
77 : */
78 2457 : public bool interface_only { get; set; }
79 :
80 : /**
81 : * Specifies whether this property is abstract. Abstract properties have
82 : * no accessor bodies, may only be specified within abstract classes and
83 : * interfaces, and must be overridden by derived non-abstract classes.
84 : */
85 1728094 : public bool is_abstract { get; set; }
86 :
87 : /**
88 : * Specifies whether this property is virtual. Virtual properties may be
89 : * overridden by derived classes.
90 : */
91 1185103 : public bool is_virtual { get; set; }
92 :
93 : /**
94 : * Specifies whether this property overrides a virtual or abstract
95 : * property of a base type.
96 : */
97 688143 : public bool overrides { get; set; }
98 :
99 : /**
100 : * Reference the Field that holds this property
101 : */
102 : public Field? field {
103 869084 : get {
104 869084 : if (!_field_checked) {
105 449363 : if (!is_abstract && source_type == SourceFileType.SOURCE) {
106 687 : bool has_get = (get_accessor != null);
107 687 : bool get_has_body = (has_get && get_accessor.body != null);
108 687 : bool has_set = (set_accessor != null);
109 687 : bool set_has_body = (has_set && set_accessor.body != null);
110 535 : if (set_has_body && (has_get && !get_has_body)) {
111 1 : error = true;
112 1 : Report.error (source_reference, "Property getter must have a body");
113 : }
114 687 : if (get_has_body && (has_set && !set_has_body)) {
115 1 : error = true;
116 1 : Report.error (source_reference, "Property setter must have a body");
117 : }
118 687 : if (!get_has_body && !set_has_body) {
119 505 : if (has_attribute ("GtkChild") && property_type.value_owned) {
120 1 : Report.warning (source_reference, "[GtkChild] properties must be declared as `unowned'");
121 1 : property_type.value_owned = false;
122 : }
123 :
124 : /* automatic property accessor body generation */
125 505 : _field = new Field ("_%s".printf (name), property_type.copy (), initializer, source_reference);
126 505 : _field.access = SymbolAccessibility.PRIVATE;
127 505 : _field.binding = binding;
128 : // apply gtk-child attribute to backing field for gtk-template support
129 505 : if (has_attribute ("GtkChild")) {
130 3 : _field.set_attribute_string ("GtkChild", "name", get_attribute_string ("GtkChild", "name", name));
131 3 : _field.set_attribute_bool ("GtkChild", "internal", get_attribute_bool ("GtkChild", "internal"));
132 : }
133 : // Inherit important attributes
134 505 : _field.copy_attribute_bool (this, "CCode", "array_length");
135 505 : _field.copy_attribute_string (this, "CCode", "array_length_type");
136 505 : _field.copy_attribute_bool (this, "CCode", "array_null_terminated");
137 505 : _field.copy_attribute_bool (this, "CCode", "delegate_target");
138 : }
139 : }
140 449363 : _field_checked = true;
141 : }
142 869084 : return _field;
143 : }
144 : }
145 :
146 : /**
147 : * Specifies whether this field may only be accessed with an instance of
148 : * the contained type.
149 : */
150 1238179 : public MemberBinding binding { get; set; default = MemberBinding.INSTANCE; }
151 :
152 : /**
153 : * The nick of this property
154 : */
155 : public string nick {
156 371 : get {
157 371 : if (_nick == null) {
158 371 : _nick = get_attribute_string ("Description", "nick");
159 371 : if (_nick == null) {
160 370 : _nick = name.replace ("_", "-");
161 : }
162 : }
163 371 : return _nick;
164 : }
165 : }
166 :
167 : /**
168 : * The blurb of this property
169 : */
170 : public string blurb {
171 371 : get {
172 371 : if (_blurb == null) {
173 371 : _blurb = get_attribute_string ("Description", "blurb");
174 371 : if (_blurb == null) {
175 370 : _blurb = name.replace ("_", "-");
176 : }
177 : }
178 371 : return _blurb;
179 : }
180 : }
181 :
182 : /**
183 : * Specifies whether this a property triggers a notify.
184 : */
185 : public bool notify {
186 994 : get {
187 994 : if (_notify == null) {
188 746 : _notify = get_attribute_bool ("CCode", "notify", true);
189 : }
190 994 : return _notify;
191 : }
192 : }
193 :
194 : /**
195 : * Specifies the virtual or abstract property this property overrides.
196 : * Reference must be weak as virtual properties set base_property to
197 : * themselves.
198 : */
199 : public Property base_property {
200 58597 : get {
201 58597 : find_base_properties ();
202 58597 : return _base_property;
203 : }
204 : }
205 :
206 : /**
207 : * Specifies the abstract interface property this property implements.
208 : */
209 : public Property base_interface_property {
210 254194 : get {
211 254194 : find_base_properties ();
212 254194 : return _base_interface_property;
213 : }
214 : }
215 :
216 : /**
217 : * Specifies the default value of this property.
218 : */
219 : public Expression initializer {
220 1965569 : get {
221 1965569 : return _initializer;
222 : }
223 42 : set {
224 84 : _initializer = value;
225 42 : _initializer.parent_node = this;
226 : }
227 : }
228 :
229 1288 : public bool lock_used { get; set; }
230 :
231 449431 : private Expression _initializer;
232 :
233 449431 : private DataType _data_type;
234 :
235 : private weak Property _base_property;
236 : private weak Property _base_interface_property;
237 : private bool base_properties_valid;
238 449431 : PropertyAccessor? _get_accessor;
239 449431 : PropertyAccessor? _set_accessor;
240 449431 : private string? _nick;
241 449431 : private string? _blurb;
242 449431 : private bool? _notify;
243 449431 : private Field? _field;
244 : private bool _field_checked;
245 :
246 : /**
247 : * Creates a new property.
248 : *
249 : * @param name property name
250 : * @param property_type property type
251 : * @param get_accessor get accessor
252 : * @param set_accessor set/construct accessor
253 : * @param source_reference reference to source code
254 : * @return newly created property
255 : */
256 1348234 : public Property (string name, DataType? property_type, PropertyAccessor? get_accessor, PropertyAccessor? set_accessor, SourceReference? source_reference = null, Comment? comment = null) {
257 449431 : base (name, source_reference, comment);
258 449431 : this.property_type = property_type;
259 449431 : this.get_accessor = get_accessor;
260 449431 : this.set_accessor = set_accessor;
261 : }
262 :
263 998631 : public override void accept (CodeVisitor visitor) {
264 998631 : visitor.visit_property (this);
265 : }
266 :
267 710270 : public override void accept_children (CodeVisitor visitor) {
268 710270 : property_type.accept (visitor);
269 :
270 710270 : if (get_accessor != null) {
271 666685 : get_accessor.accept (visitor);
272 : }
273 710270 : if (set_accessor != null) {
274 531737 : set_accessor.accept (visitor);
275 : }
276 :
277 710270 : if (initializer != null) {
278 77 : initializer.accept (visitor);
279 : }
280 : }
281 :
282 : /**
283 : * Checks whether the accessors of this property are compatible
284 : * with the specified base property.
285 : *
286 : * @param base_property a property
287 : * @param invalid_match error string about which check failed
288 : * @return true if the specified property is compatible to this property
289 : */
290 310 : public bool compatible (Property base_property, out string? invalid_match) {
291 310 : if ((get_accessor == null && base_property.get_accessor != null) ||
292 310 : (get_accessor != null && base_property.get_accessor == null)) {
293 1 : invalid_match = "incompatible get accessor";
294 1 : return false;
295 : }
296 :
297 309 : if ((set_accessor == null && base_property.set_accessor != null) ||
298 309 : (set_accessor != null && base_property.set_accessor == null)) {
299 0 : invalid_match = "incompatible set accessor";
300 0 : return false;
301 : }
302 :
303 309 : var object_type = SemanticAnalyzer.get_data_type_for_symbol (parent_symbol);
304 :
305 614 : if (get_accessor != null) {
306 : // check accessor value_type instead of property_type
307 : // due to possible ownership differences
308 309 : var actual_base_type = base_property.get_accessor.value_type.get_actual_type (object_type, null, this);
309 309 : if (!actual_base_type.equals (get_accessor.value_type)) {
310 4 : invalid_match = "incompatible get accessor type";
311 4 : return false;
312 : }
313 : }
314 :
315 397 : if (set_accessor != null) {
316 : // check accessor value_type instead of property_type
317 : // due to possible ownership differences
318 93 : var actual_base_type = base_property.set_accessor.value_type.get_actual_type (object_type, null, this);
319 93 : if (!actual_base_type.equals (set_accessor.value_type)) {
320 0 : invalid_match = "incompatible set accessor type";
321 0 : return false;
322 : }
323 :
324 93 : if (set_accessor.writable != base_property.set_accessor.writable) {
325 1 : invalid_match = "incompatible set accessor";
326 1 : return false;
327 : }
328 92 : if (set_accessor.construction != base_property.set_accessor.construction) {
329 0 : invalid_match = "incompatible set accessor";
330 0 : return false;
331 : }
332 : }
333 :
334 304 : invalid_match = null;
335 304 : return true;
336 : }
337 :
338 402632 : public override void replace_type (DataType old_type, DataType new_type) {
339 402632 : if (property_type == old_type) {
340 402632 : property_type = new_type;
341 : }
342 : }
343 :
344 0 : public override void replace_expression (Expression old_node, Expression new_node) {
345 0 : if (initializer == old_node) {
346 0 : initializer = new_node;
347 : }
348 : }
349 :
350 312791 : private void find_base_properties () {
351 312791 : if (base_properties_valid) {
352 : return;
353 : }
354 :
355 196370 : if (parent_symbol is Class) {
356 196267 : find_base_interface_property ((Class) parent_symbol);
357 196267 : if (is_virtual || overrides) {
358 236 : find_base_class_property ((Class) parent_symbol);
359 : }
360 103 : } else if (parent_symbol is Interface) {
361 93 : if (is_virtual || is_abstract) {
362 49 : _base_interface_property = this;
363 : }
364 : }
365 :
366 196370 : base_properties_valid = true;
367 : }
368 :
369 1000 : private void find_base_class_property (Class cl) {
370 616 : var sym = cl.scope.lookup (name);
371 837 : if (sym is Property) {
372 453 : var base_property = (Property) sym;
373 453 : if (base_property.is_abstract || base_property.is_virtual) {
374 : string invalid_match;
375 232 : if (!compatible (base_property, out invalid_match)) {
376 1 : error = true;
377 1 : Report.error (source_reference, "Type and/or accessors of overriding property `%s' do not match overridden property `%s': %s.", get_full_name (), base_property.get_full_name (), invalid_match);
378 1 : return;
379 : }
380 :
381 231 : _base_property = base_property;
382 231 : return;
383 : }
384 : }
385 :
386 384 : if (cl.base_class != null) {
387 380 : find_base_class_property (cl.base_class);
388 : }
389 : }
390 :
391 196267 : private void find_base_interface_property (Class cl) {
392 : // FIXME report error if multiple possible base properties are found
393 957371 : foreach (DataType type in cl.get_base_types ()) {
394 566549 : if (type.type_symbol is Interface) {
395 185997 : var sym = type.type_symbol.scope.lookup (name);
396 185997 : if (sym is Property) {
397 38 : var base_property = (Property) sym;
398 38 : if (base_property.is_abstract || base_property.is_virtual) {
399 : string invalid_match;
400 38 : if (!compatible (base_property, out invalid_match)) {
401 1 : error = true;
402 1 : Report.error (source_reference, "Type and/or accessors of overriding property `%s' do not match overridden property `%s': %s.", get_full_name (), base_property.get_full_name (), invalid_match);
403 1 : return;
404 : }
405 :
406 37 : _base_interface_property = base_property;
407 37 : return;
408 : }
409 : }
410 : }
411 : }
412 : }
413 :
414 490790 : public override bool check (CodeContext context) {
415 490790 : if (checked) {
416 72677 : return !error;
417 : }
418 :
419 418113 : checked = true;
420 :
421 836202 : if (context.profile == Profile.GOBJECT && parent_symbol is ObjectTypeSymbol
422 418089 : && ((ObjectTypeSymbol) parent_symbol).is_subtype_of (context.analyzer.object_type)) {
423 400916 : if (!is_valid_name (name)) {
424 2 : error = true;
425 2 : Report.error (source_reference, "Name `%s' is not valid for a GLib.Object property", name);
426 : }
427 : }
428 :
429 418113 : if (this_parameter != null) {
430 418104 : this_parameter.check (context);
431 : }
432 :
433 421040 : if (parent_symbol is Class && (is_abstract || is_virtual)) {
434 2929 : var cl = (Class) parent_symbol;
435 2929 : if (cl.is_compact && cl.base_class != null) {
436 1 : error = true;
437 1 : Report.error (source_reference, "Abstract and virtual properties may not be declared in derived compact classes");
438 1 : return false;
439 : }
440 2928 : if (cl.is_opaque) {
441 1 : error = true;
442 1 : Report.error (source_reference, "Abstract and virtual properties may not be declared in opaque compact classes");
443 1 : return false;
444 : }
445 : }
446 :
447 418111 : if (is_abstract) {
448 50426 : if (parent_symbol is Class) {
449 2853 : var cl = (Class) parent_symbol;
450 2853 : if (!cl.is_abstract) {
451 1 : error = true;
452 1 : Report.error (source_reference, "Abstract properties may not be declared in non-abstract classes");
453 1 : return false;
454 : }
455 44721 : } else if (!(parent_symbol is Interface)) {
456 1 : error = true;
457 1 : Report.error (source_reference, "Abstract properties may not be declared outside of classes and interfaces");
458 1 : return false;
459 : }
460 370537 : } else if (is_virtual) {
461 77 : if (!(parent_symbol is Class) && !(parent_symbol is Interface)) {
462 1 : error = true;
463 1 : Report.error (source_reference, "Virtual properties may not be declared outside of classes and interfaces");
464 1 : return false;
465 : }
466 370460 : } else if (overrides) {
467 220 : if (!(parent_symbol is Class)) {
468 1 : error = true;
469 1 : Report.error (source_reference, "Properties may not be overridden outside of classes");
470 1 : return false;
471 : }
472 370240 : } else if (access == SymbolAccessibility.PROTECTED) {
473 2 : if (!(parent_symbol is Class) && !(parent_symbol is Interface)) {
474 1 : error = true;
475 1 : Report.error (source_reference, "Protected properties may not be declared outside of classes and interfaces");
476 1 : return false;
477 : }
478 : }
479 :
480 418106 : var old_source_file = context.analyzer.current_source_file;
481 418106 : var old_symbol = context.analyzer.current_symbol;
482 :
483 418106 : if (source_reference != null) {
484 418106 : context.analyzer.current_source_file = source_reference.file;
485 : }
486 418106 : context.analyzer.current_symbol = this;
487 :
488 418106 : if (property_type is VoidType) {
489 1 : error = true;
490 1 : Report.error (source_reference, "'void' not supported as property type");
491 1 : return false;
492 : }
493 :
494 418105 : if (field != null) {
495 490 : field.check (context);
496 : }
497 :
498 418105 : property_type.check (context);
499 418105 : if (!external_package) {
500 722 : context.analyzer.check_type (property_type);
501 722 : property_type.check_type_arguments (context, true);
502 : }
503 :
504 418105 : if (get_accessor == null && set_accessor == null) {
505 2 : error = true;
506 2 : Report.error (source_reference, "Property `%s' must have a `get' accessor and/or a `set' mutator", get_full_name ());
507 2 : return false;
508 : }
509 :
510 418103 : if (get_accessor != null) {
511 392263 : get_accessor.check (context);
512 : }
513 418103 : if (set_accessor != null) {
514 312370 : if (has_attribute ("GtkChild")) {
515 1 : Report.warning (set_accessor.source_reference, "[GtkChild] property `%s' is not allowed to have `set' accessor", get_full_name ());
516 : }
517 312370 : set_accessor.check (context);
518 : }
519 :
520 418103 : if (initializer != null && field == null && !is_abstract) {
521 3 : Report.error (source_reference, "Property `%s' with custom `get' accessor and/or `set' mutator cannot have `default' value", get_full_name ());
522 : }
523 :
524 418103 : if (initializer != null) {
525 41 : initializer.check (context);
526 : }
527 :
528 : // check whether property type is at least as accessible as the property
529 418103 : if (!property_type.is_accessible (this)) {
530 1 : error = true;
531 1 : Report.error (source_reference, "property type `%s' is less accessible than property `%s'", property_type.to_string (), get_full_name ());
532 : }
533 :
534 418103 : if (overrides && base_property == null && base_interface_property == null) {
535 3 : Report.error (source_reference, "%s: no suitable property found to override", get_full_name ());
536 : }
537 :
538 418103 : if (!external_package && !overrides && !hides && get_hidden_member () != null) {
539 0 : Report.warning (source_reference, "%s hides inherited property `%s'. Use the `new' keyword if hiding was intentional", get_full_name (), get_hidden_member ().get_full_name ());
540 : }
541 :
542 : /* construct properties must be public */
543 418103 : if (set_accessor != null && set_accessor.construction) {
544 204704 : if (access != SymbolAccessibility.PUBLIC) {
545 1 : error = true;
546 1 : Report.error (source_reference, "%s: construct properties must be public", get_full_name ());
547 : }
548 : }
549 :
550 418103 : if (initializer != null && !initializer.error && initializer.value_type != null && !(initializer.value_type.compatible (property_type))) {
551 1 : error = true;
552 1 : Report.error (initializer.source_reference, "Expected initializer of type `%s' but got `%s'", property_type.to_string (), initializer.value_type.to_string ());
553 : }
554 :
555 418103 : context.analyzer.current_source_file = old_source_file;
556 418103 : context.analyzer.current_symbol = old_symbol;
557 :
558 418103 : return !error;
559 : }
560 :
561 : // Ported from glib's "g_param_spec_is_valid_name"
562 400916 : static bool is_valid_name (string name) {
563 : char* p;
564 :
565 801830 : if ((name[0] < 'A' || name[0] > 'Z')
566 400916 : && (name[0] < 'a' || name[0] > 'z')) {
567 2 : return false;
568 : }
569 :
570 4754576 : for (p = name; *p != '\0'; p++) {
571 4353662 : char c = *p;
572 8389282 : if (c != '-' && c != '_' && (c < '0' || c > '9')
573 4035620 : && (c < 'A' || c > 'Z') && (c < 'a' || c > 'z')) {
574 2 : return false;
575 : }
576 : }
577 :
578 400916 : return true;
579 : }
580 : }
|