Skip to content

Commit

Permalink
analyzer: move constant-evaluation lint utilities to their own library
Browse files Browse the repository at this point in the history
These utilities are distinct from the base classes like AnalysisRule and
LinterContext. And their import requirements are also distinct. Separating them
in the filesystem should simplify how we slice up the public API.

Change-Id: I365c48c41e5d3db1e3aa649fd70b94cd7da1b069
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/395561
Commit-Queue: Samuel Rawlins <srawlins@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
  • Loading branch information
srawlins authored and Commit Queue committed Nov 15, 2024
1 parent 1bbfe85 commit 8aa0bf5
Show file tree
Hide file tree
Showing 17 changed files with 235 additions and 225 deletions.
2 changes: 1 addition & 1 deletion pkg/analysis_server/lib/src/computer/computer_color.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import 'package:analyzer/dart/constant/value.dart';
import 'package:analyzer/dart/element/element2.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/constant/value.dart' show GenericState;
import 'package:analyzer/src/lint/linter.dart';
import 'package:analyzer/src/lint/constants.dart';
import 'package:analyzer/src/utilities/extensions/flutter.dart';
import 'package:collection/collection.dart';
import 'package:path/path.dart' as path;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import 'package:analyzer/dart/element/nullability_suffix.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/lint/linter.dart';
import 'package:analyzer/src/lint/constants.dart';
import 'package:analyzer/src/utilities/extensions/ast.dart';
import 'package:analyzer/src/utilities/extensions/flutter.dart';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/source/source_range.dart';
import 'package:analyzer/src/lint/linter.dart';
import 'package:analyzer/src/lint/constants.dart';
import 'package:analyzer_plugin/src/utilities/change_builder/change_builder_dart.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import 'package:analysis_server_plugin/edit/dart/correction_producer.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/error/error.dart';
import 'package:analyzer/src/lint/linter.dart';
import 'package:analyzer/src/lint/constants.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
import 'package:analyzer_plugin/utilities/range_factory.dart';
Expand Down
1 change: 1 addition & 0 deletions pkg/analyzer/analyzer_use_new_elements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ lib/src/generated/static_type_analyzer.dart
lib/src/generated/testing/element_factory.dart
lib/src/generated/testing/test_type_provider.dart
lib/src/hint/sdk_constraint_verifier.dart
lib/src/lint/constants.dart
lib/src/lint/linter.dart
lib/src/services/top_level_declarations.dart
lib/src/summary2/ast_binary_reader.dart
Expand Down
2 changes: 1 addition & 1 deletion pkg/analyzer/lib/src/error/best_practices_verifier.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import 'package:analyzer/src/error/doc_comment_verifier.dart';
import 'package:analyzer/src/error/error_handler_verifier.dart';
import 'package:analyzer/src/error/must_call_super_verifier.dart';
import 'package:analyzer/src/error/null_safe_api_verifier.dart';
import 'package:analyzer/src/lint/linter.dart';
import 'package:analyzer/src/lint/constants.dart';
import 'package:analyzer/src/utilities/extensions/ast.dart';
import 'package:analyzer/src/utilities/extensions/element.dart';
import 'package:analyzer/src/workspace/workspace.dart';
Expand Down
219 changes: 219 additions & 0 deletions pkg/analyzer/lib/src/lint/constants.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/constant/value.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/error/error.dart';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/ast/token.dart';
import 'package:analyzer/src/dart/constant/compute.dart';
import 'package:analyzer/src/dart/constant/constant_verifier.dart';
import 'package:analyzer/src/dart/constant/evaluation.dart';
import 'package:analyzer/src/dart/constant/potentially_constant.dart';
import 'package:analyzer/src/dart/constant/utilities.dart';
import 'package:analyzer/src/dart/constant/value.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/error/codes.dart';

/// The result of attempting to evaluate an expression as a constant.
final class LinterConstantEvaluationResult {
/// The value of the expression, or `null` if has [errors].
final DartObject? value;

/// The errors reported during the evaluation.
final List<AnalysisError> errors;

LinterConstantEvaluationResult._(this.value, this.errors);
}

/// An error listener that only records whether any constant related errors have
/// been reported.
class _ConstantAnalysisErrorListener extends AnalysisErrorListener {
/// A flag indicating whether any constant related errors have been reported
/// to this listener.
bool hasConstError = false;

@override
void onError(AnalysisError error) {
ErrorCode errorCode = error.errorCode;
if (errorCode is CompileTimeErrorCode) {
switch (errorCode) {
case CompileTimeErrorCode
.CONST_CONSTRUCTOR_CONSTANT_FROM_DEFERRED_LIBRARY:
case CompileTimeErrorCode
.CONST_CONSTRUCTOR_WITH_FIELD_INITIALIZED_BY_NON_CONST:
case CompileTimeErrorCode.CONST_EVAL_EXTENSION_METHOD:
case CompileTimeErrorCode.CONST_EVAL_EXTENSION_TYPE_METHOD:
case CompileTimeErrorCode.CONST_EVAL_METHOD_INVOCATION:
case CompileTimeErrorCode.CONST_EVAL_PROPERTY_ACCESS:
case CompileTimeErrorCode.CONST_EVAL_TYPE_BOOL:
case CompileTimeErrorCode.CONST_EVAL_TYPE_BOOL_INT:
case CompileTimeErrorCode.CONST_EVAL_TYPE_BOOL_NUM_STRING:
case CompileTimeErrorCode.CONST_EVAL_TYPE_INT:
case CompileTimeErrorCode.CONST_EVAL_TYPE_NUM:
case CompileTimeErrorCode.CONST_EVAL_TYPE_NUM_STRING:
case CompileTimeErrorCode.CONST_EVAL_TYPE_STRING:
case CompileTimeErrorCode.CONST_EVAL_THROWS_EXCEPTION:
case CompileTimeErrorCode.CONST_EVAL_THROWS_IDBZE:
case CompileTimeErrorCode.CONST_EVAL_FOR_ELEMENT:
case CompileTimeErrorCode.CONST_MAP_KEY_NOT_PRIMITIVE_EQUALITY:
case CompileTimeErrorCode.CONST_SET_ELEMENT_NOT_PRIMITIVE_EQUALITY:
case CompileTimeErrorCode.CONST_TYPE_PARAMETER:
case CompileTimeErrorCode.CONST_WITH_NON_CONST:
case CompileTimeErrorCode.CONST_WITH_NON_CONSTANT_ARGUMENT:
case CompileTimeErrorCode.CONST_WITH_TYPE_PARAMETERS:
case CompileTimeErrorCode
.CONST_WITH_TYPE_PARAMETERS_CONSTRUCTOR_TEAROFF:
case CompileTimeErrorCode.INVALID_CONSTANT:
case CompileTimeErrorCode.MISSING_CONST_IN_LIST_LITERAL:
case CompileTimeErrorCode.MISSING_CONST_IN_MAP_LITERAL:
case CompileTimeErrorCode.MISSING_CONST_IN_SET_LITERAL:
case CompileTimeErrorCode.NON_BOOL_CONDITION:
case CompileTimeErrorCode.NON_CONSTANT_LIST_ELEMENT:
case CompileTimeErrorCode.NON_CONSTANT_MAP_ELEMENT:
case CompileTimeErrorCode.NON_CONSTANT_MAP_KEY:
case CompileTimeErrorCode.NON_CONSTANT_MAP_VALUE:
case CompileTimeErrorCode.NON_CONSTANT_RECORD_FIELD:
case CompileTimeErrorCode.NON_CONSTANT_SET_ELEMENT:
hasConstError = true;
}
}
}
}

extension on AstNode {
/// Whether [ConstantVerifier] reports an error when computing the value of
/// `this` as a constant.
bool get hasConstantVerifierError {
var unitElement = thisOrAncestorOfType<CompilationUnit>()?.declaredElement;
if (unitElement == null) return false;
var libraryElement = unitElement.library as LibraryElementImpl;

var dependenciesFinder = ConstantExpressionsDependenciesFinder();
accept(dependenciesFinder);
computeConstants(
declaredVariables: unitElement.session.declaredVariables,
constants: dependenciesFinder.dependencies.toList(),
featureSet: libraryElement.featureSet,
configuration: ConstantEvaluationConfiguration(),
);

var listener = _ConstantAnalysisErrorListener();
var errorReporter = ErrorReporter(listener, unitElement.source);

accept(
ConstantVerifier(
errorReporter,
libraryElement,
unitElement.session.declaredVariables,
),
);
return listener.hasConstError;
}
}

extension ConstructorDeclarationExtension on ConstructorDeclaration {
bool get canBeConst {
var element = declaredElement!;

var classElement = element.enclosingElement3;
if (classElement is ClassElement && classElement.hasNonFinalField) {
return false;
}

var oldKeyword = constKeyword;
var self = this as ConstructorDeclarationImpl;
try {
temporaryConstConstructorElements[element] = true;
self.constKeyword = KeywordToken(Keyword.CONST, offset);
return !hasConstantVerifierError;
} finally {
temporaryConstConstructorElements[element] = null;
self.constKeyword = oldKeyword;
}
}
}

extension ExpressionExtension on Expression {
/// Whether it would be valid for this expression to have a `const` keyword.
///
/// Note that this method can cause constant evaluation to occur, which can be
/// computationally expensive.
bool get canBeConst {
var self = this;
return switch (self) {
InstanceCreationExpressionImpl() => _canBeConstInstanceCreation(self),
TypedLiteralImpl() => _canBeConstTypedLiteral(self),
_ => false,
};
}

/// Computes the constant value of `this`, if it has one.
///
/// Returns a [LinterConstantEvaluationResult], containing both the computed
/// constant value, and a list of errors that occurred during the computation.
LinterConstantEvaluationResult computeConstantValue() {
var unitElement = thisOrAncestorOfType<CompilationUnit>()?.declaredElement;
if (unitElement == null) return LinterConstantEvaluationResult._(null, []);
var libraryElement = unitElement.library as LibraryElementImpl;

var errorListener = RecordingErrorListener();

var evaluationEngine = ConstantEvaluationEngine(
declaredVariables: unitElement.session.declaredVariables,
configuration: ConstantEvaluationConfiguration(),
);

var dependencies = <ConstantEvaluationTarget>[];
accept(ReferenceFinder(dependencies.add));

computeConstants(
declaredVariables: unitElement.session.declaredVariables,
constants: dependencies,
featureSet: libraryElement.featureSet,
configuration: ConstantEvaluationConfiguration(),
);

var visitor = ConstantVisitor(
evaluationEngine,
libraryElement,
ErrorReporter(errorListener, unitElement.source),
);

var constant = visitor.evaluateAndReportInvalidConstant(this);
var dartObject = constant is DartObjectImpl ? constant : null;
return LinterConstantEvaluationResult._(dartObject, errorListener.errors);
}

bool _canBeConstInstanceCreation(InstanceCreationExpressionImpl node) {
var element = node.constructorName.staticElement;
if (element == null || !element.isConst) return false;

// Ensure that dependencies (e.g. default parameter values) are computed.
var implElement = element.declaration as ConstructorElementImpl;
implElement.computeConstantDependencies();

// Verify that the evaluation of the constructor would not produce an
// exception.
var oldKeyword = node.keyword;
try {
node.keyword = KeywordToken(Keyword.CONST, offset);
return !hasConstantVerifierError;
} finally {
node.keyword = oldKeyword;
}
}

bool _canBeConstTypedLiteral(TypedLiteralImpl node) {
var oldKeyword = node.constKeyword;
try {
node.constKeyword = KeywordToken(Keyword.CONST, offset);
return !hasConstantVerifierError;
} finally {
node.constKeyword = oldKeyword;
}
}
}
Loading

0 comments on commit 8aa0bf5

Please sign in to comment.