- Notifications
You must be signed in to change notification settings - Fork1.7k
Description
(Note the sibiling issue,#62254, for instance-variable-declaring parameters.)
With primary constructors, you can write a class like so:
@visibleForTestingclassC(finalint x);
The constructor and class declarations are rather intertwined, and there is no way to annotate the constructor individually (you cannot writeclass C @visibleForTesting (final int x);). So given the above declaration, does@visibleForTesting apply to the class? The constructor? Both?
One option is to say:that annotation only applies to the class; if you want any annotation on the constructor, you cannot write this class with a primary constructor. That seems pretty harsh, and may block a significant set of potential primary constructor use cases.
A second option is to say:each tool (e.g. analyzer, freezed code generator, JsonSerializable code generator) must choose a sensible option, based on the annotation. (And the choice should be documented with the annotation.) I'd like to explore this option. Regarding the analyzer's static analysis, It only works for annotations that the analyzer understands and acts on, which includes annotations from:
dart:core(@Deprecated(),@override,@Since()),package:meta(@immutable,@mustCallSuper,@visibleForTesting, etc.),- a few other spots, like I think
dart:ffi,dart:js_interop(@Js),package:test_reflective_loader, @Previewfrom somewhere in Flutter,- possibly specific cases of
@pragma. - Maybe more. The analyzer does not act on external things like a
@freezedannotation (except maybe to report when it is applied wrongly, if it uses@Target; we'll get to that below).
(I'll note that "each tool must choose a sensible option" is not really drifting from how things work today. When you annotate a class with@visibleForTesting, we choose whether or not that applies to members, like constructors. When you annotate a method with@protected, we choose whether or not that applies to overrides.)
So, what might this look like? Let's rundownpackage:meta:
| Annotation | is considered to apply to |
|---|---|
@awaitNotRequired | n/a (Future-returning function) |
@doNotStore | The constructor |
@doNotSubmit | Both |
@experimental | Both |
@factory | n/a (static method) |
@immutable | The class |
@internal | Both |
@isTest | n/a (non-constructor function) |
@isTestGroup | n/a (non-constructor function) |
@literal | The constructor |
@mustBeConst | n/a (parameter) |
@mustBeOverridden | n/a (instance member) |
@mustCallSuper | n/a (instance member) |
@nonVirtual | n/a (instance member) |
@optionalTypeArgs | The class |
@protected | n/a (instance member) |
@RecordUse | Maybe the constructor |
@redeclare | n/a (instance member) |
@reopen | The class |
@Target | The class |
@sealed | The class |
@useResult | The constructor, or maybe n/a |
@visibleForOverriding | n/a (instance member) |
@visibleForTesting | Both |
In all of these cases, "The class" is shorthand for "The class, enum, extension type (or extension constructor?)" where applicable.
How aboutdart:core:
| Annotation | is considered to apply to |
|---|---|
@Deprecated | Both |
@override | n/a (instance member) |
@Since | Both |
@pragma | Does the analyzer act on any pragma annotations? |
If this seems a sensible route, we can move forward and define what annotations fromdart:ffi etc. apply to.
TargetKind
TargetKind offers a neat way for the analyzer to report on whether an arbitrary annotation has been applied to a sensible element. For example, the@immutable annotation is itself annotated with@Target({.classType, .extensionType, .mixinType}) which indicates that it is only appropriate to use this annotation on a class, extension type, or mixin declaration. If it is found on any other element, like a variable, the analyzer will report a warning.
That set of TargetKinds for@immutable serves as a good example: if a class with a primary constructor is annotated with@immutable, is that "ok?" It seems obviously "yes" in this case, we can take a wild guess at what the user is intending. And we don't have to complain that it looks like the annotation might apply to the constructor. Indeed, classes with a bunch of final fields declared in a primary constructor is a prime target for an@immutable annotation. So what about the other TargetKinds?
classType,enumType,extensionType,mixinType,typeare appropriate, as the annotation can be considered to apply to the type declaration.constructoris appropriate, as the annotation can be considered to apply to the constructor declaration.directive,enumValue,extension,field,function,getter,library,method,optionalParameter,overridableMember,parameter,setter,topLevelVariable,typedefType,typeParameter,valuesare not appropriate; continue to report.