-
Notifications
You must be signed in to change notification settings - Fork 213
Statement metadata #1652
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
For context, consider lints where you want to mark something as OK, say I have a problem with parentheses if we don't do something, like add the required If you want it to mean Another option is to parenthesize the annotation, That's why my proposed solution is to use |
The problem is that
Whichever we choose as the default interpretation, we must then consider what you must add to get the other interpretation. For For In either case, makes very similar syntax mean something different based on whether there is a far-away expression or not. That's not good for readability. It's bad for readability if you cannot quickly determine how to read the code, and there is no pattern of punctuation here which is unambiguous. That's why I consider @foo(a + b + c + d ~e): [c];
@bar:(a + b + c + d ~e)[c]; Much easier to distinguish. |
The parser does not know the types. We only figure out what types are after having parsed the syntax of the program. |
What if statement level annotations are not allowed to contain parens? This doesn't limit the expressiveness as you can always define a const on a separate line that includes the full expression with parens. This would ensure that statement level annotations are easy to read, parse, and format without the ambiguity for users and parsers. All the use cases I can come up with for statement level annotations do not benefit from annotations with parens. I would be interested if anyone can think of a case where statement level annotations would benefit from parens. If we do want to support statement level annotations with parens, we could require the Examples: @foo (a + b)[c]; // parses as `@foo`
// To write:
@Foo(a+b) d[e]
// You would have to write
const fooAb = @Foo(a+b);
@fooAb d[e];
// or:
@Foo(a+b) : d[e] |
The I'm not aware of any exiting annotations with arguments that would apply to statements, but that might be because most of our existing annotations are targeted at helping clients of an API use the API correctly, so I'm not sure we have any annotations at the moment that would apply at the statement level. That said, it seems to me that using an argument to explain why the annotation is there would be a reasonable use case even for statement level annotations (such as why it was ok to not await a future in this context). Although I admit that is seems like a comment would be equally good in all such cases if we choose not to allow the constructor invocation form of annotations on statements. |
Another common annotation-with-arguments is One might reasonably expect the compiler annotations Consider if we want to encourage inlining of foo(@pragma('vm:prefer-inline'):bar(baz())) it is not crystal clear that the annotation applies only to
might inline I'm not sure we really need annotations on expressions, though, as most cases of an expression can be rewritten to use an intermediate variable declaration with an initializer expression, and the annotation could be applied to the variable, meaning the corresponding initializer expression. The step-wise breakdown using a current valid annotation context is clearer as there is only one call to inline:
If the syntax |
Can we expand a bit on why we'd want annotations on statements in the first place? Especially when comments are available, I'm not sure what annotations would do. If we want tooling to have access to them, we already have special comments like Having a concrete example might help with the reasoning and syntax here. |
The initial impetus, mentioned briefly here is to take the main() {
@unawaited
doSomethingAsyncButIgnoreTheReturnedFuture();
} Related: #1661 |
I'm still not sold on why these are better than regular comments. I always use an main() async {
// ignore: unawaited_futures
doSomethingAsyncButIgnoreTheReturnedFuture();
} More generally than my own preferences, it seems like the use of annotations here would be to explain what we're doing and maybe also why we're doing it:
Isn't this exactly what comments are for? I get the feeling we're trying to re-invent comments as "metadata that can go before anything". If tooling is a concern, we can use comments in a standard form, such as the main() {
int index = 5000; // this number is special to our company because...
myList [index]; // don't worry, this is a REALLY big list
} The example above might otherwise be two lints, |
I agree with Levi. I really dislike the "unawaited" function. // ignore: unawaited_future, explanation why we don't await the future
fetch() is much better than: // explanation why we don't await the future
unawaited(
fetch(),
); |
I think As per the lint docs, there are two options to ignore this rule in a fire-and-forget situation where you know what you're doing:
I prefer going with Here are some resources for this topic:
|
Thanks for reminding us. There is now an |
@irhn Perhaps having this as an auto-fix in VS Code and others is good, just like suggesting adding |
Dart currently allows metadata annotations (
@foo
or@Foo(args)
, or even@Foo<types>(args)
soon) on declarations.That makes sense if the primary way to access metadata is through
dart:mirrors
, and you can't mirror something you can't name.However, annotations are also used by source-processing tools like the analyzer and some code generators. They can find annotations anywhere in the source.
Assume that we would want to allow metadata annotations on statements in general.
Annotations have no semantic effect on the Dart program itself, so it's all about sending signals to tools. Say, to tell the analyzer to disable a lint.
Syntax
The syntax for annotations is currently
@id
,@SomeClass(args)
or (soon)@SomeClass<typeArgs>(args)
.The things you want to express for statements are not different from what you want to express for declarations, so it would be nice if all these annotations would be allowed on statements.
That's a problem, though, because it introduces grammar ambiguities if we just put the annotation before the statement, because an expression-statement allows almost any expression to follow it. Example:
this can be parsed as the annotation constructor invocation
@foo(a+b)
on the expression statement[c];
or the annotation identifier@foo
on the expression statement(a+b)[c];
Adding more parentheses to the expression is not necessarily a solution which scales (although if we disallow annotations on the empty statement,
;
, then@foo((a + b)[c]);
can only be parsed in one way).I'd recommend introducing a separator. Just like statement labels need a
:
, we could say that statement annotations need a:
, and you have to write one of:That also suggests a formatting style where the annotation, like the label, is placed above the statement:
Formatting issues are handled by treating statement annotations exactly like we treat labels.
That might be confusing when compared to existing annotations on local functions (which we allow), which don't need the trailing
:
:We may want to allow that annotation to have a
:
too, when it occurs inside a member body.This suggests allowing the
<metadata-with-colon>
production where we could otherwise add a label.Expression annotations
With a delimiter like
@...:
for annotations, we could potentially also allow annotations on expressions.It's more complicated because expressions have precedence, so
@foo:a + b
could mean(@foo: a) + b
or(@foo:(a+b))
.If we put annotations on the expression itself, so it can contain any expression, then you can always parenthesize the scope you want,
(@foo: any+expression)
.It's an ambiguity with statement annotations for
@foo:expr;
, but we can just always consider that a statement annotation and make you write(@foo:expr);
if you want it on the expression.In practice, you probably always want to parenthesize, so the maybe only allow it just inside parentheses. Change:
Summary
Allow a metadata production followed by a colon everywhere we allow a statement label.
Possibly, allow any number of metadata productions followed by colons (or just one colon?) inside at the start of a parenthesized expression.
I believe the grammar and parsing should be manageable.
The text was updated successfully, but these errors were encountered: