Skip to content

Commit b321254

Browse files
mosuemCommit Queue
authored andcommitted
Add @mustBeConst annotation for parameters which should be
constant. As const-only parameters are not planned, see dart-lang/language#1684, an annotation with analyzer support could help a bit at least, see #29381. The motivation is to enforce const arguments for methods annotated with `@ResourceIdentifier`, to be able to record the argument values at build time, see https://dart-review.googlesource.com/c/sdk/+/329961. Tested: pkg/analyzer/test/src/diagnostics/const_argument_test.dart Change-Id: I2b8d2dce0c899fc0caa4985d892a5d031c747521 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/357701 Reviewed-by: Phil Quitslund <[email protected]> Reviewed-by: Lasse Nielsen <[email protected]> Reviewed-by: Brian Wilkerson <[email protected]> Commit-Queue: Moritz Sümmermann <[email protected]> Reviewed-by: Marya Belanger <[email protected]>
1 parent 4e68819 commit b321254

File tree

15 files changed

+904
-29
lines changed

15 files changed

+904
-29
lines changed

pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3721,6 +3721,8 @@ WarningCode.MUST_BE_IMMUTABLE:
37213721
status: noFix
37223722
WarningCode.MUST_CALL_SUPER:
37233723
status: hasFix
3724+
WarningCode.NON_CONST_ARGUMENT_FOR_CONST_PARAMETER:
3725+
status: needsEvaluation
37243726
WarningCode.NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR:
37253727
status: needsFix
37263728
notes: |-

pkg/analyzer/lib/dart/element/element.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -617,6 +617,9 @@ abstract class Element implements AnalysisTarget {
617617
/// Whether the element has an annotation of the form `@literal`.
618618
bool get hasLiteral;
619619

620+
/// Whether the element has an annotation of the form `@mustBeConst`.
621+
bool get hasMustBeConst;
622+
620623
/// Whether the element has an annotation of the form `@mustBeOverridden`.
621624
bool get hasMustBeOverridden;
622625

@@ -872,6 +875,10 @@ abstract class ElementAnnotation implements ConstantEvaluationTarget {
872875
/// Whether the annotation marks the associated constructor as being literal.
873876
bool get isLiteral;
874877

878+
/// Whether the annotation marks the associated returned element as
879+
/// requiring a constant argument.
880+
bool get isMustBeConst;
881+
875882
/// Whether the annotation marks the associated member as requiring
876883
/// subclasses to override this member.
877884
bool get isMustBeOverridden;

pkg/analyzer/lib/src/dart/element/element.dart

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1531,6 +1531,10 @@ class ElementAnnotationImpl implements ElementAnnotation {
15311531
/// literal.
15321532
static const String _literalVariableName = 'literal';
15331533

1534+
/// The name of the top-level variable used to mark a returned element as
1535+
/// requiring use.
1536+
static const String _mustBeConstVariableName = 'mustBeConst';
1537+
15341538
/// The name of the top-level variable used to mark a type as having
15351539
/// "optional" type arguments.
15361540
static const String _optionalTypeArgsVariableName = 'optionalTypeArgs';
@@ -1732,6 +1736,9 @@ class ElementAnnotationImpl implements ElementAnnotation {
17321736
@override
17331737
bool get isLiteral => _isPackageMetaGetter(_literalVariableName);
17341738

1739+
@override
1740+
bool get isMustBeConst => _isPackageMetaGetter(_mustBeConstVariableName);
1741+
17351742
@override
17361743
bool get isMustBeOverridden => _isPackageMetaGetter(_mustBeOverridden);
17371744

@@ -2113,6 +2120,18 @@ abstract class ElementImpl implements Element {
21132120
return false;
21142121
}
21152122

2123+
@override
2124+
bool get hasMustBeConst {
2125+
final metadata = this.metadata;
2126+
for (var i = 0; i < metadata.length; i++) {
2127+
var annotation = metadata[i];
2128+
if (annotation.isMustBeConst) {
2129+
return true;
2130+
}
2131+
}
2132+
return false;
2133+
}
2134+
21162135
@override
21172136
bool get hasMustBeOverridden {
21182137
final metadata = this.metadata;
@@ -5509,6 +5528,9 @@ class MultiplyDefinedElementImpl implements MultiplyDefinedElement {
55095528
@override
55105529
bool get hasLiteral => false;
55115530

5531+
@override
5532+
bool get hasMustBeConst => false;
5533+
55125534
@override
55135535
bool get hasMustBeOverridden => false;
55145536

pkg/analyzer/lib/src/dart/element/member.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,9 @@ abstract class Member implements Element {
609609
@override
610610
bool get hasLiteral => _declaration.hasLiteral;
611611

612+
@override
613+
bool get hasMustBeConst => _declaration.hasMustBeConst;
614+
612615
@override
613616
bool get hasMustBeOverridden => _declaration.hasMustBeOverridden;
614617

pkg/analyzer/lib/src/error/codes.g.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6934,6 +6934,14 @@ class WarningCode extends AnalyzerErrorCode {
69346934
hasPublishedDocs: true,
69356935
);
69366936

6937+
/// Parameters:
6938+
/// 0: the name of the argument
6939+
static const WarningCode NON_CONST_ARGUMENT_FOR_CONST_PARAMETER = WarningCode(
6940+
'NON_CONST_ARGUMENT_FOR_CONST_PARAMETER',
6941+
"Argument '{0}' must be a constant.",
6942+
correctionMessage: "Try replacing the argument with a constant.",
6943+
);
6944+
69376945
/// Generates a warning for non-const instance creation using a constructor
69386946
/// annotated with `@literal`.
69396947
///
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:analyzer/dart/ast/ast.dart';
6+
import 'package:analyzer/dart/ast/syntactic_entity.dart';
7+
import 'package:analyzer/dart/ast/token.dart';
8+
import 'package:analyzer/dart/ast/visitor.dart';
9+
import 'package:analyzer/dart/element/type.dart';
10+
import 'package:analyzer/error/listener.dart';
11+
import 'package:analyzer/src/dart/ast/utilities.dart';
12+
import 'package:analyzer/src/dart/element/element.dart';
13+
import 'package:analyzer/src/error/codes.dart';
14+
15+
/// Checks if the arguments for a parameter label as `@mustBeConst` are actually
16+
/// constant.
17+
class ConstArgumentsVerifier extends SimpleAstVisitor<void> {
18+
final ErrorReporter _errorReporter;
19+
20+
final ConstantEvaluator _constantEvaluator;
21+
22+
ConstArgumentsVerifier(this._errorReporter)
23+
: _constantEvaluator = ConstantEvaluator();
24+
25+
@override
26+
void visitAssignmentExpression(AssignmentExpression node) {
27+
if (node.operator.type == TokenType.EQ) {
28+
_check(
29+
arguments: [node.rightHandSide],
30+
errorNode: node.operator,
31+
);
32+
} else if (node.rightHandSide.staticParameterElement?.hasMustBeConst ??
33+
false) {
34+
// If the operator is not `=`, then the argument cannot be const, as it
35+
// depends on the value of the left hand side.
36+
_errorReporter.atNode(
37+
node.rightHandSide,
38+
WarningCode.NON_CONST_ARGUMENT_FOR_CONST_PARAMETER,
39+
arguments: [node.rightHandSide],
40+
);
41+
}
42+
}
43+
44+
@override
45+
void visitBinaryExpression(BinaryExpression node) {
46+
_check(
47+
arguments: [node.rightOperand],
48+
errorNode: node.operator,
49+
);
50+
}
51+
52+
@override
53+
void visitFunctionExpressionInvocation(FunctionExpressionInvocation node) {
54+
if (node.staticInvokeType is FunctionType) {
55+
_check(
56+
arguments: node.argumentList.arguments,
57+
errorNode: node,
58+
);
59+
}
60+
}
61+
62+
@override
63+
void visitIndexExpression(IndexExpression node) {
64+
_check(
65+
arguments: [node.index],
66+
errorNode: node.leftBracket,
67+
);
68+
}
69+
70+
@override
71+
void visitInstanceCreationExpression(InstanceCreationExpression node) {
72+
if (node.inConstantContext) return;
73+
_check(
74+
arguments: node.argumentList.arguments,
75+
errorNode: node.constructorName,
76+
);
77+
}
78+
79+
@override
80+
void visitMethodInvocation(MethodInvocation node) {
81+
_check(
82+
arguments: node.argumentList.arguments,
83+
errorNode: node.methodName,
84+
);
85+
}
86+
87+
void _check({
88+
required List<Expression> arguments,
89+
required SyntacticEntity errorNode,
90+
}) {
91+
for (final argument in arguments) {
92+
final parameter = argument.staticParameterElement;
93+
if (parameter != null && parameter.hasMustBeConst) {
94+
Expression resolvedArgument;
95+
if (parameter.isNamed) {
96+
resolvedArgument = (argument as NamedExpression).expression;
97+
} else {
98+
resolvedArgument = argument;
99+
}
100+
if (resolvedArgument is Identifier) {
101+
final staticElement = resolvedArgument.staticElement;
102+
if (staticElement != null &&
103+
staticElement.nonSynthetic is ConstVariableElement) {
104+
return;
105+
}
106+
}
107+
if (resolvedArgument.accept(_constantEvaluator) ==
108+
ConstantEvaluator.NOT_A_CONSTANT) {
109+
_errorReporter.atNode(
110+
argument,
111+
WarningCode.NON_CONST_ARGUMENT_FOR_CONST_PARAMETER,
112+
arguments: [parameter.name],
113+
);
114+
}
115+
}
116+
}
117+
}
118+
}

pkg/analyzer/lib/src/error/error_code_values.g.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1046,6 +1046,7 @@ const List<ErrorCode> errorCodeValues = [
10461046
WarningCode.MIXIN_ON_SEALED_CLASS,
10471047
WarningCode.MUST_BE_IMMUTABLE,
10481048
WarningCode.MUST_CALL_SUPER,
1049+
WarningCode.NON_CONST_ARGUMENT_FOR_CONST_PARAMETER,
10491050
WarningCode.NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR,
10501051
WarningCode.NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR_USING_NEW,
10511052
WarningCode.NON_NULLABLE_EQUALS_PARAMETER,

pkg/analyzer/lib/src/generated/error_verifier.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import 'package:analyzer/src/dart/resolver/variance.dart';
3434
import 'package:analyzer/src/diagnostic/diagnostic.dart';
3535
import 'package:analyzer/src/diagnostic/diagnostic_factory.dart';
3636
import 'package:analyzer/src/error/codes.dart';
37+
import 'package:analyzer/src/error/const_argument_verifier.dart';
3738
import 'package:analyzer/src/error/constructor_fields_verifier.dart';
3839
import 'package:analyzer/src/error/correct_override.dart';
3940
import 'package:analyzer/src/error/duplicate_definition_verifier.dart';
@@ -241,6 +242,7 @@ class ErrorVerifier extends RecursiveAstVisitor<void>
241242

242243
final LibraryVerificationContext libraryVerificationContext;
243244
final RequiredParametersVerifier _requiredParametersVerifier;
245+
final ConstArgumentsVerifier _constArgumentsVerifier;
244246
final DuplicateDefinitionVerifier _duplicateDefinitionVerifier;
245247
final UseResultVerifier _checkUseVerifier;
246248
late final TypeArgumentsVerifier _typeArgumentsVerifier;
@@ -260,6 +262,7 @@ class ErrorVerifier extends RecursiveAstVisitor<void>
260262
_UninstantiatedBoundChecker(errorReporter),
261263
_checkUseVerifier = UseResultVerifier(errorReporter),
262264
_requiredParametersVerifier = RequiredParametersVerifier(errorReporter),
265+
_constArgumentsVerifier = ConstArgumentsVerifier(errorReporter),
263266
_duplicateDefinitionVerifier = DuplicateDefinitionVerifier(
264267
_inheritanceManager,
265268
_currentLibrary,
@@ -353,6 +356,8 @@ class ErrorVerifier extends RecursiveAstVisitor<void>
353356
_checkForDeadNullCoalesce(node.readType as TypeImpl, node.rightHandSide);
354357
}
355358
_checkForAssignmentToFinal(lhs);
359+
360+
_constArgumentsVerifier.visitAssignmentExpression(node);
356361
super.visitAssignmentExpression(node);
357362
}
358363

@@ -386,6 +391,7 @@ class ErrorVerifier extends RecursiveAstVisitor<void>
386391
}
387392

388393
checkForUseOfVoidResult(node.leftOperand);
394+
_constArgumentsVerifier.visitBinaryExpression(node);
389395

390396
super.visitBinaryExpression(node);
391397
}
@@ -918,6 +924,7 @@ class ErrorVerifier extends RecursiveAstVisitor<void>
918924
_typeArgumentsVerifier.checkFunctionExpressionInvocation(node);
919925
}
920926
_requiredParametersVerifier.visitFunctionExpressionInvocation(node);
927+
_constArgumentsVerifier.visitFunctionExpressionInvocation(node);
921928
_checkUseVerifier.checkFunctionExpressionInvocation(node);
922929
super.visitFunctionExpressionInvocation(node);
923930
}
@@ -1017,6 +1024,7 @@ class ErrorVerifier extends RecursiveAstVisitor<void>
10171024
_checkForInvalidGenerativeConstructorReference(constructorName);
10181025
_checkForConstOrNewWithMixin(node, namedType, type);
10191026
_requiredParametersVerifier.visitInstanceCreationExpression(node);
1027+
_constArgumentsVerifier.visitInstanceCreationExpression(node);
10201028
if (node.isConst) {
10211029
_checkForConstWithNonConst(node);
10221030
_checkForConstWithUndefinedConstructor(
@@ -1110,6 +1118,7 @@ class ErrorVerifier extends RecursiveAstVisitor<void>
11101118
}
11111119
_typeArgumentsVerifier.checkMethodInvocation(node);
11121120
_requiredParametersVerifier.visitMethodInvocation(node);
1121+
_constArgumentsVerifier.visitMethodInvocation(node);
11131122
_checkUseVerifier.checkMethodInvocation(node);
11141123
super.visitMethodInvocation(node);
11151124
}

pkg/analyzer/lib/src/test_utilities/mock_packages.dart

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ const _IsTestGroup isTestGroup = _IsTestGroup();
109109
110110
const _Literal literal = _Literal();
111111
112+
const mustBeConst = _MustBeConst();
113+
112114
const _MustBeOverridden mustBeOverridden = _MustBeOverridden();
113115
114116
const _MustCallSuper mustCallSuper = _MustCallSuper();
@@ -191,6 +193,14 @@ class _Checked {
191193
const _Checked();
192194
}
193195
196+
@Target({
197+
TargetKind.parameter,
198+
TargetKind.typedefType,
199+
})
200+
class _MustBeConst {
201+
const _MustBeConst();
202+
}
203+
194204
@Target({
195205
TargetKind.classType,
196206
TargetKind.function,
@@ -296,7 +306,6 @@ class Target {
296306
const Target(this.kinds);
297307
}
298308
299-
300309
class TargetKind {
301310
const TargetKind._(this.displayString, this.name);
302311

pkg/analyzer/messages.yaml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24889,6 +24889,48 @@ WarningCode:
2488924889
}
2489024890
}
2489124891
```
24892+
NON_CONST_ARGUMENT_FOR_CONST_PARAMETER:
24893+
problemMessage: "Argument '{0}' must be a constant."
24894+
correctionMessage: Try replacing the argument with a constant.
24895+
comment: |-
24896+
Parameters:
24897+
0: the name of the argument
24898+
hasPublishedDocs: false
24899+
documentation: |-
24900+
#### Description
24901+
24902+
The analyzer produces this diagnostic when a parameter is
24903+
annotated with the `@mustBeConst` annotation and the
24904+
corresponding argument is not a constant expression.
24905+
24906+
#### Example
24907+
24908+
The following code produces this diagnostic on the invocation of
24909+
the function `f` because the value of the argument passed to the
24910+
function `g` isn't a constant:
24911+
24912+
```dart
24913+
import 'package:meta/meta.dart' show mustBeConst;
24914+
24915+
int f(int value) => g([!value!]);
24916+
24917+
int g(@mustBeConst int value) => value + 1;
24918+
```
24919+
24920+
#### Common fixes
24921+
24922+
If a suitable constant is available to use, then replace the argument
24923+
with a constant:
24924+
24925+
```dart
24926+
import 'package:meta/meta.dart' show mustBeConst;
24927+
24928+
const v = 3;
24929+
24930+
int f() => g(v);
24931+
24932+
int g(@mustBeConst int value) => value + 1;
24933+
```
2489224934
NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR_USING_NEW:
2489324935
sharedName: NON_CONST_CALL_TO_LITERAL_CONSTRUCTOR
2489424936
problemMessage: "This instance creation must be 'const', because the {0} constructor is marked as '@literal'."

0 commit comments

Comments
 (0)