blob: 20dafe7458ee4304a227103935d9d7eda69ac445 [file] [log] [blame]
// 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/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 unitNode = thisOrAncestorOfType<CompilationUnitImpl>();
var unitFragment = unitNode?.declaredFragment;
if (unitFragment == null) return false;
var libraryElement = unitFragment.element;
var declaredVariables = libraryElement.session.declaredVariables;
var dependenciesFinder = ConstantExpressionsDependenciesFinder();
accept(dependenciesFinder);
computeConstants(
declaredVariables: declaredVariables,
constants: dependenciesFinder.dependencies.toList(),
featureSet: libraryElement.featureSet,
configuration: ConstantEvaluationConfiguration(),
);
var listener = _ConstantAnalysisErrorListener();
var errorReporter = ErrorReporter(listener, unitFragment.source);
accept(ConstantVerifier(errorReporter, libraryElement, declaredVariables));
return listener.hasConstError;
}
}
extension ConstructorDeclarationExtension on ConstructorDeclaration {
bool get canBeConst {
var element = declaredFragment!.element;
var classElement = element.enclosingElement2;
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 unitNode = thisOrAncestorOfType<CompilationUnitImpl>();
var unitFragment = unitNode?.declaredFragment;
if (unitFragment == null) {
return LinterConstantEvaluationResult._(null, []);
}
var libraryElement = unitFragment.element;
var declaredVariables = libraryElement.session.declaredVariables;
var evaluationEngine = ConstantEvaluationEngine(
declaredVariables: declaredVariables,
configuration: ConstantEvaluationConfiguration(),
);
var dependencies = <ConstantEvaluationTarget>[];
accept(ReferenceFinder(dependencies.add));
computeConstants(
declaredVariables: declaredVariables,
constants: dependencies,
featureSet: libraryElement.featureSet,
configuration: ConstantEvaluationConfiguration(),
);
var errorListener = RecordingErrorListener();
var visitor = ConstantVisitor(
evaluationEngine,
libraryElement,
ErrorReporter(errorListener, unitFragment.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.element;
if (element == null || !element.isConst) return false;
// Ensure that dependencies (e.g. default parameter values) are computed.
var implElement = element.baseElement;
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;
}
}
}