blob: 9a37e7432047fa8cda026e977d1f2c2b90d31fa6 [file] [log] [blame]
// Copyright (c) 2014, 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 'dart:collection';
import 'package:analyzer/dart/analysis/declared_variables.dart';
import 'package:analyzer/dart/analysis/features.dart';
import 'package:analyzer/dart/ast/syntactic_entity.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/nullability_suffix.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/ast/extensions.dart';
import 'package:analyzer/src/dart/ast/token.dart';
import 'package:analyzer/src/dart/constant/from_environment_evaluator.dart';
import 'package:analyzer/src/dart/constant/has_type_parameter_reference.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/dart/element/member.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/dart/element/type_algebra.dart';
import 'package:analyzer/src/dart/element/type_provider.dart';
import 'package:analyzer/src/dart/element/type_system.dart' show TypeSystemImpl;
import 'package:analyzer/src/diagnostic/diagnostic.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/generated/java_core.dart';
import 'package:analyzer/src/utilities/extensions/collection.dart';
import 'package:analyzer/src/utilities/extensions/object.dart';
class ConstantEvaluationConfiguration {
final Map<AstNode, AstNode> _errorNodes = {};
/// We evaluate constant values using expressions stored in elements.
/// But these expressions don't have offsets set.
/// This includes elements and expressions of the file being resolved.
/// So, to make sure that we report errors at right offsets, we "replace"
/// these constant expressions.
///
/// A similar issue happens for enum values, which are desugared into
/// synthetic [InstanceCreationExpression], which never had any offsets.
/// So, we remember that any errors should be reported at the corresponding
/// [EnumConstantDeclaration]s.
void addErrorNode({
required AstNode? fromElement,
required AstNode? fromAst,
}) {
if (fromElement != null && fromAst != null) {
_errorNodes[fromElement] = fromAst;
}
}
AstNode errorNode(AstNode node) {
return _errorNodes[node] ?? node;
}
}
/// Helper class encapsulating the methods for evaluating constants and
/// constant instance creation expressions.
class ConstantEvaluationEngine {
/// The set of variables declared on the command line using '-D'.
final DeclaredVariables _declaredVariables;
final ConstantEvaluationConfiguration configuration;
/// Initialize a newly created [ConstantEvaluationEngine].
///
/// [declaredVariables] is the set of variables declared on the command
/// line using '-D'.
ConstantEvaluationEngine({
required DeclaredVariables declaredVariables,
required this.configuration,
}) : _declaredVariables = declaredVariables;
/// Compute the constant value associated with the given [constant].
void computeConstantValue(ConstantEvaluationTarget constant) {
if (constant is FragmentImpl) {
var element = constant as FragmentImpl;
constant = element.declaration as ConstantEvaluationTarget;
}
var libraryFragment = constant.libraryFragment!;
var library = libraryFragment.element;
if (constant is FormalParameterElementImpl) {
var defaultValue = constant.constantInitializer?.expression;
if (defaultValue != null) {
var diagnosticListener = RecordingDiagnosticListener();
var diagnosticReporter = DiagnosticReporter(
diagnosticListener,
libraryFragment.source,
);
var constantVisitor = ConstantVisitor(
this,
library,
diagnosticReporter,
);
var dartConstant = constantVisitor.evaluateConstant(defaultValue);
constant.evaluationResult = dartConstant;
} else {
constant.evaluationResult = _nullObject(library);
}
} else if (constant is VariableElementImpl) {
var constantInitializer = constant.constantInitializer?.expression;
if (constantInitializer != null) {
var diagnosticReporter = DiagnosticReporter(
RecordingDiagnosticListener(),
libraryFragment.source,
);
var constantVisitor = ConstantVisitor(
this,
library,
diagnosticReporter,
);
var dartConstant = constantVisitor.evaluateConstant(
constantInitializer,
);
if (dartConstant is DartObjectImpl) {
// Only check the type for truly const declarations (don't check final
// fields with initializers, since their types may be generic. The
// type of the final field will be checked later, when the constructor
// is invoked).
if (constant.isConst) {
if (!library.typeSystem.runtimeTypeMatch(
dartConstant,
constant.type,
)) {
// If the static types are mismatched, an error would have already
// been reported.
if (library.typeSystem.isAssignableTo(
constantInitializer.typeOrThrow,
constant.type,
)) {
constant.evaluationResult = InvalidConstant.forEntity(
entity: constantInitializer,
diagnosticCode: CompileTimeErrorCode.VARIABLE_TYPE_MISMATCH,
arguments: [
dartConstant.type.getDisplayString(),
constant.type.getDisplayString(),
],
);
return;
}
}
// Associate with the variable.
dartConstant = DartObjectImpl.forVariable(dartConstant, constant);
}
var enumConstant = _enumConstant(constant);
if (enumConstant != null) {
dartConstant.updateEnumConstant(
index: enumConstant.index,
name: enumConstant.name,
);
}
}
constant.evaluationResult = dartConstant;
}
} else if (constant is ConstructorElementImpl) {
if (constant.isConst) {
// No evaluation needs to be done; constructor declarations are only in
// the dependency graph to ensure that any constants referred to in
// initializer lists and parameter defaults are evaluated before
// invocations of the constructor.
constant.firstFragment.isConstantEvaluated = true;
}
} else if (constant is ElementAnnotationImpl) {
var constNode = constant.annotationAst;
var element = constant.element2;
if (element is PropertyAccessorElement) {
// The annotation is a reference to a compile-time constant variable.
// Just copy the evaluation result.
var variableElement =
element.variable?.baseElement as VariableElementImpl?;
var evaluationResult = variableElement?.evaluationResult;
if (evaluationResult != null) {
constant.evaluationResult = evaluationResult;
} else {
// This could happen in the event that the annotation refers to a
// non-constant. The error is detected elsewhere, so just silently
// ignore it here.
constant.evaluationResult = null;
}
} else if (element is ConstructorElementMixin2 &&
element.isConst &&
constNode.arguments != null) {
var diagnosticListener = RecordingDiagnosticListener();
var diagnosticReporter = DiagnosticReporter(
diagnosticListener,
libraryFragment.source,
);
var constantVisitor = ConstantVisitor(
this,
library,
diagnosticReporter,
);
var result = evaluateAndFormatErrorsInConstructorCall(
library,
constNode,
element.returnType.typeArguments,
constNode.arguments!.arguments,
element,
constantVisitor,
);
constant.evaluationResult = result;
constant.additionalErrors = diagnosticListener.diagnostics;
} else {
// This may happen for invalid code (e.g. failing to pass arguments
// to an annotation which references a const constructor). The error
// is detected elsewhere, so just silently ignore it here.
constant.evaluationResult = null;
}
} else if (constant is VariableElement) {
// `constant` is a VariableElement but not a VariableElementImpl. This
// can happen sometimes in the case of invalid user code (for example, a
// constant expression that refers to a non-static field inside a generic
// class will wind up referring to a FieldMember). The error is detected
// elsewhere, so just silently ignore it here.
} else {
// Should not happen.
assert(false);
AnalysisEngine.instance.instrumentationService.logError(
"Constant value computer trying to compute "
"the value of a node of type ${constant.runtimeType}",
);
return;
}
}
/// Determine which constant elements need to have their values computed
/// prior to computing the value of [constant], and report them using
/// [callback].
void computeDependencies(
ConstantEvaluationTarget constant,
ReferenceFinderCallback callback,
) {
if (constant is FieldElementImpl && constant.isEnumConstant) {
var enclosing = constant.enclosingElement;
if (enclosing is EnumElementImpl) {
if (enclosing.name == 'values') {
return;
}
if (constant.name == enclosing.name) {
return;
}
}
}
ReferenceFinder referenceFinder = ReferenceFinder(callback);
if (constant case ConstructorElementMixin2 constructor) {
constant = constructor.baseElement;
}
if (constant is VariableElementImpl) {
var declaration = constant;
var initializer = declaration.constantInitializer?.expression;
if (initializer != null) {
initializer.accept(referenceFinder);
}
} else if (constant is ConstructorElementImpl) {
if (constant.isConst) {
var redirectedConstructor = getConstRedirectedConstructor(constant);
if (redirectedConstructor != null) {
var redirectedConstructorBase = redirectedConstructor.baseElement;
callback(redirectedConstructorBase);
return;
} else if (constant.isFactory) {
// Factory constructor, but getConstRedirectedConstructor returned
// null. This can happen if we're visiting one of the special
// external const factory constructors in the SDK, or if the code
// contains errors (such as delegating to a non-const constructor, or
// delegating to a constructor that can't be resolved). In any of
// these cases, we'll evaluate calls to this constructor without
// having to refer to any other constants. So we don't need to report
// any dependencies.
return;
}
bool defaultSuperInvocationNeeded = true;
var initializers = constant.constantInitializers;
for (ConstructorInitializer initializer in initializers) {
if (initializer is SuperConstructorInvocation ||
initializer is RedirectingConstructorInvocation) {
defaultSuperInvocationNeeded = false;
}
initializer.accept(referenceFinder);
}
if (defaultSuperInvocationNeeded) {
// No explicit superconstructor invocation found, so we need to
// manually insert a reference to the implicit superconstructor.
var superclass = constant.returnType.superclass;
if (superclass != null && !superclass.isDartCoreObject) {
var unnamedConstructor =
superclass.element.unnamedConstructor?.baseElement;
if (unnamedConstructor != null && unnamedConstructor.isConst) {
callback(unnamedConstructor);
}
}
}
for (var field in constant.enclosingElement.fields) {
// Note: non-static const isn't allowed but we handle it anyway so
// that we won't be confused by incorrect code.
if ((field.isFinal || field.isConst) &&
!field.isStatic &&
field.hasInitializer) {
callback(field);
}
}
for (var parameterElement in constant.formalParameters) {
callback(parameterElement.baseElement);
}
}
} else if (constant is ElementAnnotationImpl) {
Annotation constNode = constant.annotationAst;
var element = constant.element2;
if (element is PropertyAccessorElement) {
// The annotation is a reference to a compile-time constant variable,
// so it depends on the variable.
if (element.variable case var variable?) {
var baseElement = variable.baseElement as VariableElementImpl;
callback(baseElement);
}
} else if (element is ConstructorElement) {
// The annotation is a constructor invocation, so it depends on the
// constructor.
var baseElement = element.baseElement;
callback(baseElement as ConstructorElementImpl);
} else {
// This could happen in the event of invalid code. The error will be
// reported at constant evaluation time.
}
if (constNode.arguments != null) {
constNode.arguments!.accept(referenceFinder);
}
} else if (constant is VariableFragmentImpl) {
// `constant` is a VariableElement but not a VariableElementImpl. This
// can happen sometimes in the case of invalid user code (for example, a
// constant expression that refers to a non-static field inside a generic
// class will wind up referring to a FieldMember). So just don't bother
// computing any dependencies.
} else {
// Should not happen.
assert(false);
AnalysisEngine.instance.instrumentationService.logError(
"Constant value computer trying to compute "
"the value of a node of type ${constant.runtimeType}",
);
}
}
/// Evaluates the constructor call and format any [InvalidConstant]s if found.
Constant evaluateAndFormatErrorsInConstructorCall(
LibraryElementImpl library,
AstNode node,
List<TypeImpl>? typeArguments,
List<Expression> arguments,
ConstructorElementMixin2 constructor,
ConstantVisitor constantVisitor, {
ConstructorInvocation? invocation,
}) {
var result = _InstanceCreationEvaluator.evaluate(
this,
_declaredVariables,
library,
node,
constructor,
typeArguments,
arguments,
constantVisitor,
invocation: invocation,
);
if (result is! InvalidConstant) {
return result;
}
// If we found an evaluation exception, report a context message linking to
// where the exception was found.
if (result.isRuntimeException) {
var formattedMessage = formatList(
result.diagnosticCode.problemMessage,
result.arguments,
);
var contextMessage = DiagnosticMessageImpl(
filePath: library.source.fullName,
length: result.length,
message: "The exception is '$formattedMessage' and occurs here.",
offset: result.offset,
url: null,
);
var errorNode = configuration.errorNode(node);
result = InvalidConstant.forEntity(
entity: errorNode,
diagnosticCode: CompileTimeErrorCode.CONST_EVAL_THROWS_EXCEPTION,
contextMessages: [...result.contextMessages, contextMessage],
);
}
return result;
}
Constant evaluateConstructorCall(
LibraryElementImpl library,
AstNode node,
List<TypeImpl>? typeArguments,
List<Expression> arguments,
ConstructorElementMixin2 constructor,
ConstantVisitor constantVisitor, {
ConstructorInvocation? invocation,
}) {
return _InstanceCreationEvaluator.evaluate(
this,
_declaredVariables,
library,
node,
constructor,
typeArguments,
arguments,
constantVisitor,
invocation: invocation,
);
}
/// Generate an error indicating that the given [constant] is not a valid
/// compile-time constant because it references at least one of the constants
/// in the given [cycle], each of which directly or indirectly references the
/// constant.
void generateCycleError(
Iterable<ConstantEvaluationTarget> cycle,
ConstantEvaluationTarget constant,
) {
if (constant is VariableElementImpl) {
DiagnosticReporter diagnosticReporter = DiagnosticReporter(
RecordingDiagnosticListener(),
constant.libraryFragment!.source,
);
// TODO(paulberry): It would be really nice if we could extract enough
// information from the 'cycle' argument to provide the user with a
// description of the cycle.
diagnosticReporter.atElement2(
constant,
CompileTimeErrorCode.RECURSIVE_COMPILE_TIME_CONSTANT,
);
constant.evaluationResult = InvalidConstant.forElement(
element: constant,
diagnosticCode: CompileTimeErrorCode.RECURSIVE_COMPILE_TIME_CONSTANT,
);
} else if (constant is ConstructorElementImpl) {
// We don't report cycle errors on constructor declarations here since
// there is nowhere to put the error information.
//
// Instead we will report an error at each constructor in
// [ConstantVerifier.visitConstructorDeclaration].
} else {
// Should not happen. Formal parameter defaults and annotations should
// never appear as part of a cycle because they can't be referred to.
assert(false);
AnalysisEngine.instance.instrumentationService.logError(
"Constant value computer trying to report a cycle error "
"for a node of type ${constant.runtimeType}",
);
}
}
/// If [constructor] redirects to another const constructor, return the
/// const constructor it redirects to. Otherwise return `null`.
static ConstructorElementMixin2? getConstRedirectedConstructor(
ConstructorElementMixin2 constructor,
) {
if (!constructor.isFactory) {
return null;
}
var typeProvider = constructor.library.typeProvider;
if (constructor.enclosingElement == typeProvider.symbolElement) {
// The dart:core.Symbol has a const factory constructor that redirects
// to dart:_internal.Symbol. That in turn redirects to an external
// const constructor, which we won't be able to evaluate.
// So stop following the chain of redirections at dart:core.Symbol, and
// let [evaluateInstanceCreationExpression] handle it specially.
return null;
}
var redirectedConstructor = constructor.redirectedConstructor;
if (redirectedConstructor == null) {
// This can happen if constructor is an external factory constructor.
return null;
}
if (!redirectedConstructor.isConst) {
// Delegating to a non-const constructor--this is not allowed (and
// is checked elsewhere--see
// [ErrorVerifier.checkForRedirectToNonConstConstructor()]).
return null;
}
return redirectedConstructor;
}
static _EnumConstant? _enumConstant(VariableElementImpl element) {
if (element is FieldElementImpl && element.isEnumConstant) {
var enum_ = element.enclosingElement;
if (enum_ is EnumElementImpl) {
var index = enum_.constants.indexOf(element);
assert(index >= 0);
return _EnumConstant(index: index, name: element.name ?? '');
}
}
return null;
}
static DartObjectImpl _nullObject(LibraryElementImpl library) {
return DartObjectImpl(
library.typeSystem,
library.typeProvider.nullType,
NullState.NULL_STATE,
);
}
/// Returns the representation of a constant expression which has an
/// [InvalidType], with the given [defaultType].
static DartObjectImpl _unresolvedObject(
LibraryElementImpl library,
TypeImpl defaultType,
) {
// TODO(kallentu): Use a better representation of an unresolved object that
// doesn't need to rely on NullState.
return DartObjectImpl(
library.typeSystem,
defaultType,
NullState(isInvalid: true),
);
}
}
/// Interface for constant evaluation targets.
abstract class ConstantEvaluationTarget {
/// Return whether this constant is evaluated.
bool get isConstantEvaluated;
/// The library fragment with the first fragment of this constant.
LibraryFragmentImpl? get libraryFragment;
}
/// A visitor used to evaluate constant expressions to produce their
/// compile-time value.
class ConstantVisitor extends UnifyingAstVisitor<Constant> {
/// The evaluation engine used to access the feature set, type system, and
/// type provider.
final ConstantEvaluationEngine _evaluationEngine;
/// The library that contains the constant expression being evaluated.
final LibraryElementImpl _library;
/// A mapping of variable names to runtime values.
final Map<String, DartObjectImpl>? _lexicalEnvironment;
/// A mapping of type parameter names to runtime values (types).
final Map<TypeParameterElement, TypeImpl>? _lexicalTypeEnvironment;
final Substitution? _substitution;
/// Diagnostic reporter that we use to report errors accumulated while
/// computing the constant.
final DiagnosticReporter _diagnosticReporter;
/// Helper class used to compute constant values.
late final DartObjectComputer _dartObjectComputer;
/// Initializes a newly created constant visitor. The [_evaluationEngine] is
/// used to evaluate instance creation expressions. The [lexicalEnvironment]
/// is a map containing values which should override identifiers, or `null` if
/// no overriding is necessary. The [_diagnosticReporter] is used to report
/// errors found during evaluation.
///
/// The [substitution] is specified for instance creations.
ConstantVisitor(
this._evaluationEngine,
this._library,
this._diagnosticReporter, {
Map<String, DartObjectImpl>? lexicalEnvironment,
Map<TypeParameterElement, TypeImpl>? lexicalTypeEnvironment,
Substitution? substitution,
}) : _lexicalEnvironment = lexicalEnvironment,
_lexicalTypeEnvironment = lexicalTypeEnvironment,
_substitution = substitution {
_dartObjectComputer = DartObjectComputer(typeSystem, _library.featureSet);
}
/// Convenience getter to access the [_evaluationEngine]'s type system.
TypeSystemImpl get typeSystem => _library.typeSystem;
/// Convenience getter to access the [_evaluationEngine]'s type provider.
TypeProviderImpl get _typeProvider => _library.typeProvider;
/// Evaluates and reports an error if the evaluation result of [node] is an
/// [InvalidConstant].
///
/// If [InvalidConstant.avoidReporting] is marked `true`, no error is
/// reported.
Constant evaluateAndReportInvalidConstant(AstNode node) {
var result = evaluateConstant(node);
if (result case InvalidConstant(avoidReporting: false)) {
_diagnosticReporter.atOffset(
offset: result.offset,
length: result.length,
diagnosticCode: result.diagnosticCode,
arguments: result.arguments,
contextMessages: result.contextMessages,
);
}
return result;
}
/// Evaluates the expression of [node] using this [ConstantVisitor].
///
/// Returns the resulting constant value, which can be an [InvalidConstant]
/// if the expression fails to evaluate to a constant value.
///
/// The [ConstantVisitor] can't return any `null` values even though
/// [UnifyingAstVisitor] allows it. If we encounter an unexpected `null`
/// value, we will return an [InvalidConstant] instead.
Constant evaluateConstant(AstNode node) {
var result = node.accept(this);
if (result == null) {
// Should never reach this.
throw UnsupportedError(
'The constant evaluator returned an unexpected null value.',
);
}
return result;
}
@override
Constant visitAdjacentStrings(AdjacentStrings node) {
return _concatenateNodes(node, node.strings);
}
@override
Constant visitAsExpression(AsExpression node) {
var expression = evaluateConstant(node.expression);
if (expression is! DartObjectImpl) {
return expression;
}
var type = evaluateConstant(node.type);
if (type is! DartObjectImpl) {
return type;
}
return _dartObjectComputer.castToType(node, expression, type);
}
@override
Constant visitBinaryExpression(BinaryExpression node) {
var operatorElement = node.element;
var operatorContainer = operatorElement?.enclosingElement;
switch (operatorContainer) {
case ExtensionElement():
return InvalidConstant.forEntity(
entity: node,
diagnosticCode: CompileTimeErrorCode.CONST_EVAL_EXTENSION_METHOD,
);
case ExtensionTypeElement():
return InvalidConstant.forEntity(
entity: node,
diagnosticCode: CompileTimeErrorCode.CONST_EVAL_EXTENSION_TYPE_METHOD,
);
}
TokenType operatorType = node.operator.type;
var leftResult = evaluateConstant(node.leftOperand);
if (leftResult is! DartObjectImpl) {
return leftResult;
}
// Used for the [DartObjectComputer], which will handle any exceptions.
DartObjectImpl computeRightOperand() {
var constant = evaluateConstant(node.rightOperand);
switch (constant) {
case DartObjectImpl():
return constant;
case InvalidConstant():
throw EvaluationException(constant.diagnosticCode);
}
}
// Evaluate lazy operators.
if (operatorType == TokenType.AMPERSAND_AMPERSAND) {
if (leftResult.toBoolValue() == false) {
var error = _reportNotPotentialConstants(node.rightOperand);
if (error is InvalidConstant) {
return error;
}
}
return _dartObjectComputer.lazyAnd(node, leftResult, computeRightOperand);
} else if (operatorType == TokenType.BAR_BAR) {
if (leftResult.toBoolValue() == true) {
var error = _reportNotPotentialConstants(node.rightOperand);
if (error is InvalidConstant) {
return error;
}
}
return _dartObjectComputer.lazyOr(node, leftResult, computeRightOperand);
} else if (operatorType == TokenType.QUESTION_QUESTION) {
if (leftResult.isNull != true) {
var error = _reportNotPotentialConstants(node.rightOperand);
if (error is InvalidConstant) {
return error;
}
}
return _dartObjectComputer.lazyQuestionQuestion(
node,
leftResult,
() => evaluateConstant(node.rightOperand),
);
}
// Evaluate eager operators.
var rightResult = evaluateConstant(node.rightOperand);
if (rightResult is! DartObjectImpl) {
return rightResult;
}
if (operatorType == TokenType.AMPERSAND) {
return _dartObjectComputer.eagerAnd(node, leftResult, rightResult);
} else if (operatorType == TokenType.BANG_EQ) {
return _dartObjectComputer.notEqual(node, leftResult, rightResult);
} else if (operatorType == TokenType.BAR) {
return _dartObjectComputer.eagerOr(node, leftResult, rightResult);
} else if (operatorType == TokenType.CARET) {
return _dartObjectComputer.eagerXor(node, leftResult, rightResult);
} else if (operatorType == TokenType.EQ_EQ) {
return _dartObjectComputer.equalEqual(node, leftResult, rightResult);
} else if (operatorType == TokenType.GT) {
return _dartObjectComputer.greaterThan(node, leftResult, rightResult);
} else if (operatorType == TokenType.GT_EQ) {
return _dartObjectComputer.greaterThanOrEqual(
node,
leftResult,
rightResult,
);
} else if (operatorType == TokenType.GT_GT) {
return _dartObjectComputer.shiftRight(node, leftResult, rightResult);
} else if (operatorType == TokenType.GT_GT_GT) {
return _dartObjectComputer.logicalShiftRight(
node,
leftResult,
rightResult,
);
} else if (operatorType == TokenType.LT) {
return _dartObjectComputer.lessThan(node, leftResult, rightResult);
} else if (operatorType == TokenType.LT_EQ) {
return _dartObjectComputer.lessThanOrEqual(node, leftResult, rightResult);
} else if (operatorType == TokenType.LT_LT) {
return _dartObjectComputer.shiftLeft(node, leftResult, rightResult);
} else if (operatorType == TokenType.MINUS) {
return _dartObjectComputer.minus(node, leftResult, rightResult);
} else if (operatorType == TokenType.PERCENT) {
return _dartObjectComputer.remainder(node, leftResult, rightResult);
} else if (operatorType == TokenType.PLUS) {
return _dartObjectComputer.add(node, leftResult, rightResult);
} else if (operatorType == TokenType.STAR) {
return _dartObjectComputer.times(node, leftResult, rightResult);
} else if (operatorType == TokenType.SLASH) {
return _dartObjectComputer.divide(node, leftResult, rightResult);
} else if (operatorType == TokenType.TILDE_SLASH) {
return _dartObjectComputer.integerDivide(node, leftResult, rightResult);
} else {
// TODO(srawlins): Use a specific error code.
// https://github.com/dart-lang/sdk/issues/47061
return InvalidConstant.genericError(node: node);
}
}
@override
Constant visitBooleanLiteral(BooleanLiteral node) {
return DartObjectImpl(
typeSystem,
_typeProvider.boolType,
BoolState.from(node.value),
);
}
@override
Constant visitConditionalExpression(ConditionalExpression node) {
var condition = node.condition;
var conditionConstant = evaluateConstant(condition);
if (conditionConstant is! DartObjectImpl) {
return conditionConstant;
}
if (!conditionConstant.isBool) {
return InvalidConstant.forEntity(
entity: condition,
diagnosticCode: CompileTimeErrorCode.CONST_EVAL_TYPE_BOOL,
);
}
conditionConstant = _dartObjectComputer.applyBooleanConversion(
condition,
conditionConstant,
);
if (conditionConstant is! DartObjectImpl) {
return conditionConstant;
}
var conditionResultBool = conditionConstant.toBoolValue();
if (conditionResultBool == true) {
var error = _reportNotPotentialConstants(node.elseExpression);
if (error is InvalidConstant) {
return error;
}
return evaluateConstant(node.thenExpression);
} else if (conditionResultBool == false) {
var error = _reportNotPotentialConstants(node.thenExpression);
if (error is InvalidConstant) {
return error;
}
return evaluateConstant(node.elseExpression);
} else {
var thenConstant = evaluateConstant(node.thenExpression);
if (thenConstant is InvalidConstant) {
return thenConstant;
}
var elseConstant = evaluateConstant(node.elseExpression);
if (elseConstant is InvalidConstant) {
return elseConstant;
}
return DartObjectImpl.validWithUnknownValue(typeSystem, node.typeOrThrow);
}
}
@override
Constant visitConstructorReference(ConstructorReference node) {
var constructorFunctionType = node.typeOrThrow;
if (constructorFunctionType is! FunctionTypeImpl) {
return InvalidConstant.forEntity(
entity: node,
diagnosticCode: CompileTimeErrorCode.INVALID_CONSTANT,
);
}
var classType = constructorFunctionType.returnType as InterfaceTypeImpl;
var typeArguments = classType.typeArguments;
// The result is already instantiated during resolution;
// [_dartObjectComputer.typeInstantiate] is unnecessary.
var typeElement = node.constructorName.type.element;
TypeAliasElement? viaTypeAlias;
if (typeElement is TypeAliasElementImpl) {
if (constructorFunctionType.typeParameters.isNotEmpty &&
!typeElement.isProperRename) {
// The type alias is not a proper rename of the aliased class, so
// the constructor tear-off is distinct from the associated
// constructor function of the aliased class.
viaTypeAlias = typeElement;
}
}
var constructorElement =
node.constructorName.element?.baseElement
.ifTypeOrNull<ConstructorElementImpl>();
if (constructorElement == null) {
return InvalidConstant.forEntity(
entity: node,
diagnosticCode: CompileTimeErrorCode.INVALID_CONSTANT,
);
}
return DartObjectImpl(
typeSystem,
node.typeOrThrow,
FunctionState(
constructorElement,
typeArguments: typeArguments,
viaTypeAlias: viaTypeAlias,
),
);
}
@override
Constant visitDotShorthandConstructorInvocation(
covariant DotShorthandConstructorInvocationImpl node,
) {
// This check is used by the [ConstantVerifier] to check for constant
// default parameters and other instances where the invocation must be
// constant.
if (!node.isConst) {
// TODO(kallentu): Use a specific error code.
// https://github.com/dart-lang/sdk/issues/47061
return InvalidConstant.genericError(node: node);
}
var constructor = node.constructorName.element;
if (constructor is ConstructorElementMixin2) {
return _evaluationEngine.evaluateAndFormatErrorsInConstructorCall(
_library,
node,
constructor.returnType.typeArguments,
node.argumentList.arguments,
constructor,
this,
);
}
// Couldn't resolve the constructor so we can't compute a value. No
// problem - the error has already been reported.
return InvalidConstant.forEntity(
entity: node,
diagnosticCode: CompileTimeErrorCode.INVALID_CONSTANT,
);
}
@override
Constant visitDotShorthandInvocation(DotShorthandInvocation node) {
return _invalidConstantForMethodInvocation(node);
}
@override
Constant visitDotShorthandPropertyAccess(
covariant DotShorthandPropertyAccessImpl node,
) {
return _getConstantValue(
errorNode: node,
expression: node,
identifier: node.propertyName,
element: node.propertyName.element,
);
}
@override
Constant visitDoubleLiteral(DoubleLiteral node) {
return DartObjectImpl(
typeSystem,
_typeProvider.doubleType,
DoubleState(node.value),
);
}
@override
Constant visitFunctionReference(covariant FunctionReferenceImpl node) {
var functionResult = evaluateConstant(node.function);
if (functionResult is! DartObjectImpl) {
return functionResult;
}
// Report an error if any of the _inferred_ type argument types refer to a
// type parameter. If, however, `node.typeArguments` is not `null`, then
// any type parameters contained therein are reported as non-constant in
// [ConstantVerifier].
if (node.typeArguments == null) {
var typeArgumentTypes = node.typeArgumentTypes;
if (typeArgumentTypes != null) {
var instantiatedTypeArgumentTypes = typeArgumentTypes.map((type) {
if (type is TypeParameterType) {
return _lexicalTypeEnvironment?[type.element] ?? type;
} else {
return type;
}
});
if (instantiatedTypeArgumentTypes.any(hasTypeParameterReference)) {
return InvalidConstant.forEntity(
entity: node,
diagnosticCode:
CompileTimeErrorCode
.CONST_WITH_TYPE_PARAMETERS_FUNCTION_TEAROFF,
);
}
}
}
var typeArgumentList = node.typeArguments;
if (typeArgumentList == null) {
return _instantiateFunctionType(node, functionResult);
}
var typeArguments = <TypeImpl>[];
for (var typeArgument in typeArgumentList.arguments) {
var typeArgumentConstant = evaluateConstant(typeArgument);
switch (typeArgumentConstant) {
case InvalidConstant(
diagnosticCode: CompileTimeErrorCode.CONST_TYPE_PARAMETER,
):
// If there's a type parameter error in the evaluated constant, we
// convert the message to a more specific function reference error.
return InvalidConstant.forEntity(
entity: typeArgument,
diagnosticCode:
CompileTimeErrorCode
.CONST_WITH_TYPE_PARAMETERS_FUNCTION_TEAROFF,
);
case InvalidConstant():
return typeArgumentConstant;
case DartObjectImpl():
var typeArgumentType = typeArgumentConstant.toTypeValue();
if (typeArgumentType == null) {
return InvalidConstant.forEntity(
entity: typeArgument,
diagnosticCode: CompileTimeErrorCode.INVALID_CONSTANT,
);
}
// TODO(srawlins): Test type alias types (`typedef i = int`) used as
// type arguments. Possibly change implementation based on
// canonicalization rules.
typeArguments.add(typeArgumentType);
}
}
return _dartObjectComputer.typeInstantiate(
functionResult,
typeArguments,
node.function,
typeArgumentList,
);
}
@override
Constant visitGenericFunctionType(covariant GenericFunctionTypeImpl node) {
return DartObjectImpl(
typeSystem,
_typeProvider.typeType,
TypeState(node.type),
);
}
@override
Constant visitInstanceCreationExpression(
covariant InstanceCreationExpressionImpl node,
) {
if (!node.isConst) {
// TODO(srawlins): Use a specific error code.
// https://github.com/dart-lang/sdk/issues/47061
return InvalidConstant.genericError(node: node);
}
var constructor = node.constructorName.element;
if (constructor == null) {
// Couldn't resolve the constructor so we can't compute a value. No
// problem - the error has already been reported.
// TODO(kallentu): Use a better error code for this.
return InvalidConstant.forEntity(
entity: node,
diagnosticCode: CompileTimeErrorCode.INVALID_CONSTANT,
);
}
return _evaluationEngine.evaluateAndFormatErrorsInConstructorCall(
_library,
node,
constructor.returnType.typeArguments,
node.argumentList.arguments,
constructor,
this,
);
}
@override
Constant visitIntegerLiteral(IntegerLiteral node) {
if (node.staticType == _typeProvider.doubleType) {
return DartObjectImpl(
typeSystem,
_typeProvider.doubleType,
DoubleState(node.value?.toDouble()),
);
}
return DartObjectImpl(
typeSystem,
_typeProvider.intType,
IntState(node.value),
);
}
@override
Constant visitInterpolationExpression(InterpolationExpression node) {
var result = evaluateConstant(node.expression);
if (result is! DartObjectImpl) {
return result;
}
if (!result.isBoolNumStringOrNull) {
return InvalidConstant.forEntity(
entity: node,
diagnosticCode: CompileTimeErrorCode.CONST_EVAL_TYPE_BOOL_NUM_STRING,
);
}
return _dartObjectComputer.performToString(node, result);
}
@override
Constant visitInterpolationString(InterpolationString node) {
return DartObjectImpl(
typeSystem,
_typeProvider.stringType,
StringState(node.value),
);
}
@override
Constant visitIsExpression(IsExpression node) {
var expression = evaluateConstant(node.expression);
if (expression is! DartObjectImpl) {
return expression;
}
var type = evaluateConstant(node.type);
if (type is! DartObjectImpl) {
return type;
}
return _dartObjectComputer.typeTest(node, expression, type);
}
@override
Constant visitListLiteral(ListLiteral node) {
if (!node.isConst) {
return InvalidConstant.forEntity(
entity: node,
diagnosticCode: CompileTimeErrorCode.MISSING_CONST_IN_LIST_LITERAL,
);
}
var nodeType = node.staticType;
var elementType =
nodeType is InterfaceTypeImpl && nodeType.typeArguments.isNotEmpty
? nodeType.typeArguments[0]
: _typeProvider.dynamicType;
var listType = _typeProvider.listType(elementType);
var list = <DartObjectImpl>[];
return _buildListConstant(list, node.elements, typeSystem, listType);
}
@override
Constant visitMethodInvocation(MethodInvocation node) {
var element = node.methodName.element;
if (element is TopLevelFunctionElementImpl) {
if (element.isDartCoreIdentical) {
var arguments = node.argumentList.arguments;
var leftArgument = evaluateConstant(arguments[0]);
if (leftArgument is! DartObjectImpl) {
return leftArgument;
}
var rightArgument = evaluateConstant(arguments[1]);
if (rightArgument is! DartObjectImpl) {
return rightArgument;
}
return _dartObjectComputer.isIdentical(
node,
leftArgument,
rightArgument,
);
}
}
return _invalidConstantForMethodInvocation(node);
}
@override
Constant visitNamedExpression(NamedExpression node) =>
evaluateConstant(node.expression);
@override
Constant visitNamedType(NamedType node) {
var type = node.typeOrThrow;
if (node.isTypeLiteralInConstantPattern &&
hasTypeParameterReference(type)) {
return InvalidConstant.forEntity(
entity: node,
diagnosticCode: CompileTimeErrorCode.CONST_TYPE_PARAMETER,
);
} else if (node.isDeferred) {
return _getDeferredLibraryError(node, node.name);
}
if (_substitution != null) {
type = _substitution.substituteType(type);
}
return _getConstantValue(
errorNode: node,
expression: null,
identifier: null,
element: node.element,
givenType: type,
);
}
@override
Constant visitNode(AstNode node) {
// TODO(srawlins): Use a specific error code.
// https://github.com/dart-lang/sdk/issues/47061
return InvalidConstant.genericError(node: node);
}
@override
Constant visitNullLiteral(NullLiteral node) {
return ConstantEvaluationEngine._nullObject(_library);
}
@override
Constant visitParenthesizedExpression(ParenthesizedExpression node) =>
evaluateConstant(node.expression);
@override
Constant visitPrefixedIdentifier(covariant PrefixedIdentifierImpl node) {
var prefixNode = node.prefix;
var prefixElement = prefixNode.element;
// A top-level constant, imported with a prefix.
if (prefixElement is PrefixElement) {
if (node.isDeferred) {
return _getDeferredLibraryError(node, node.identifier);
}
} else if (prefixElement is! ExtensionElement) {
var prefixResult = evaluateConstant(prefixNode);
if (prefixResult is! DartObjectImpl) {
return prefixResult;
}
// For example, `String.length`.
if (prefixElement is! InterfaceElement) {
var propertyAccessResult = _evaluatePropertyAccess(
prefixResult,
node.identifier,
node,
isNullAware: false,
);
if (propertyAccessResult != null) {
return propertyAccessResult;
}
}
}
// Validate prefixed identifier.
return _getConstantValue(
errorNode: node,
expression: node,
identifier: node.identifier,
element: node.identifier.element,
);
}
@override
Constant visitPrefixExpression(PrefixExpression node) {
var operatorElement = node.element;
var operatorContainer = operatorElement?.enclosingElement;
switch (operatorContainer) {
case ExtensionElement():
return InvalidConstant.forEntity(
entity: node,
diagnosticCode: CompileTimeErrorCode.CONST_EVAL_EXTENSION_METHOD,
);
case ExtensionTypeElement():
return InvalidConstant.forEntity(
entity: node,
diagnosticCode: CompileTimeErrorCode.CONST_EVAL_EXTENSION_TYPE_METHOD,
);
}
var operand = evaluateConstant(node.operand);
if (operand is! DartObjectImpl) {
return operand;
}
if (node.operator.type == TokenType.BANG) {
return _dartObjectComputer.logicalNot(node, operand);
} else if (node.operator.type == TokenType.TILDE) {
return _dartObjectComputer.bitNot(node, operand);
} else if (node.operator.type == TokenType.MINUS) {
return _dartObjectComputer.negated(node, operand);
} else {
// TODO(srawlins): Use a specific error code.
// https://github.com/dart-lang/sdk/issues/47061
return InvalidConstant.genericError(node: node);
}
}
@override
Constant visitPropertyAccess(covariant PropertyAccessImpl node) {
var target = node.target;
if (target != null) {
if (target is PrefixedIdentifierImpl &&
(target.element is ExtensionElement ||
target.element is ExtensionTypeElement)) {
var prefix = target.prefix;
if (prefix.element is PrefixElement && target.isDeferred) {
return _getDeferredLibraryError(node, target.identifier);
}
// For example, `async.FutureExtensions.wait`.
return _getConstantValue(
errorNode: node,
expression: node,
identifier: node.propertyName,
element: node.propertyName.element,
);
}
var prefixResult = evaluateConstant(target);
if (prefixResult is! DartObjectImpl) {
return prefixResult;
}
var propertyAccessResult = _evaluatePropertyAccess(
prefixResult,
node.propertyName,
node,
isNullAware: node.isNullAware,
);
if (propertyAccessResult != null) {
return propertyAccessResult;
}
}
return _getConstantValue(
errorNode: node,
expression: node,
identifier: node.propertyName,
element: node.propertyName.element,
);
}
@override
Constant visitRecordLiteral(RecordLiteral node) {
var positionalFields = <DartObjectImpl>[];
var namedFields = <String, DartObjectImpl>{};
for (var field in node.fields) {
if (field is NamedExpression) {
var name = field.name.label.name;
var value = evaluateConstant(field.expression);
if (value is! DartObjectImpl) {
return value;
}
namedFields[name] = value;
} else {
var value = evaluateConstant(field);
if (value is! DartObjectImpl) {
return value;
}
positionalFields.add(value);
}
}
var nodeType = RecordTypeImpl.fromApi(
positional: positionalFields.map((e) => e.type).toList(),
named: namedFields.map((name, value) => MapEntry(name, value.type)),
nullabilitySuffix: NullabilitySuffix.none,
);
return DartObjectImpl(
typeSystem,
nodeType,
RecordState(positionalFields, namedFields),
);
}
@override
Constant? visitRecordTypeAnnotation(covariant RecordTypeAnnotationImpl node) {
return DartObjectImpl(
typeSystem,
_typeProvider.typeType,
TypeState(node.type),
);
}
@override
Constant visitSetOrMapLiteral(SetOrMapLiteral node) {
// Note: due to dartbug.com/33441, it's possible that a set/map literal
// resynthesized from a summary will have neither its `isSet` or `isMap`
// boolean set to `true`. We work around the problem by assuming such
// literals are maps.
// TODO(paulberry): when dartbug.com/33441 is fixed, add an assertion here
// to verify that `node.isSet == !node.isMap`.
bool isMap = !node.isSet;
if (isMap) {
if (!node.isConst) {
return InvalidConstant.forEntity(
entity: node,
diagnosticCode: CompileTimeErrorCode.MISSING_CONST_IN_MAP_LITERAL,
);
}
var keyType = _typeProvider.dynamicType;
var valueType = _typeProvider.dynamicType;
var nodeType = node.staticType;
if (nodeType is InterfaceTypeImpl) {
var typeArguments = nodeType.typeArguments;
if (typeArguments.length >= 2) {
keyType = typeArguments[0];
valueType = typeArguments[1];
}
}
var mapType = _typeProvider.mapType(keyType, valueType);
var map = <DartObjectImpl, DartObjectImpl>{};
var result = _buildMapConstant(map, node.elements, typeSystem, mapType);
if (result is InvalidConstant && !node.isMap) {
// We don't report the error if we know this is an ambiguous map or
// set. [CompileTimeErrorCode.AMBIGUOUS_SET_OR_MAP_LITERAL_BOTH]
// or [CompileTimeErrorCode.AMBIGUOUS_SET_OR_MAP_LITERAL_EITHER] is
// already reported elsewhere.
result.avoidReporting = true;
}
return result;
} else {
if (!node.isConst) {
return InvalidConstant.forEntity(
entity: node,
diagnosticCode: CompileTimeErrorCode.MISSING_CONST_IN_SET_LITERAL,
);
}
var nodeType = node.staticType;
var elementType =
nodeType is InterfaceTypeImpl && nodeType.typeArguments.isNotEmpty
? nodeType.typeArguments[0]
: _typeProvider.dynamicType;
var setType = _typeProvider.setType(elementType);
var set = <DartObjectImpl>{};
return _buildSetConstant(set, node.elements, typeSystem, setType);
}
}
@override
Constant visitSimpleIdentifier(covariant SimpleIdentifierImpl node) {
var value = _lexicalEnvironment?[node.name];
if (value != null) {
return _instantiateFunctionTypeForSimpleIdentifier(node, value);
}
return _getConstantValue(
errorNode: node,
expression: node,
identifier: node,
element: node.element,
);
}
@override
Constant visitSimpleStringLiteral(SimpleStringLiteral node) {
return DartObjectImpl(
typeSystem,
_typeProvider.stringType,
StringState(node.value),
);
}
@override
Constant visitStringInterpolation(StringInterpolation node) {
return _concatenateNodes(node, node.elements);
}
@override
Constant visitSymbolLiteral(SymbolLiteral node) {
StringBuffer buffer = StringBuffer();
List<Token> components = node.components;
for (int i = 0; i < components.length; i++) {
if (i > 0) {
buffer.writeCharCode(0x2E);
}
buffer.write(components[i].lexeme);
}
return DartObjectImpl(
typeSystem,
_typeProvider.symbolType,
SymbolState(buffer.toString()),
);
}
@override
Constant visitTypeLiteral(TypeLiteral node) => evaluateConstant(node.type);
/// Builds a list constant by adding the evaluated entries of [elements] to
/// the given [list].
///
/// The [typeSystem] and [listType] are used to create a valid constant. We
/// return an [InvalidConstant] if the evaluation of any of the elements
/// failed.
Constant _buildListConstant(
List<DartObjectImpl> list,
List<CollectionElement> elements,
TypeSystemImpl typeSystem,
InterfaceTypeImpl listType,
) {
for (var element in elements) {
switch (element) {
case Expression():
var expression = evaluateConstant(element);
switch (expression) {
case InvalidConstant():
return expression;
case DartObjectImpl():
list.add(expression);
}
case ForElement():
return InvalidConstant.forEntity(
entity: element,
diagnosticCode: CompileTimeErrorCode.CONST_EVAL_FOR_ELEMENT,
);
case IfElement():
var condition = evaluateConstant(element.expression);
switch (condition) {
case InvalidConstant():
return condition;
case DartObjectImpl():
// If the condition is unknown, we mark this list as unknown.
if (condition.isUnknown) {
var elementType = listType.typeArguments.first;
return DartObjectImpl(
typeSystem,
listType,
ListState.unknown(elementType),
);
}
var conditionValue = condition.toBoolValue();
Constant? branchResult;
if (conditionValue == null) {
return InvalidConstant.forEntity(
entity: element.expression,
diagnosticCode: CompileTimeErrorCode.NON_BOOL_CONDITION,
);
} else if (conditionValue) {
branchResult = _buildListConstant(
list,
[element.thenElement],
typeSystem,
listType,
);
} else if (element.elseElement != null) {
branchResult = _buildListConstant(
list,
[element.elseElement!],
typeSystem,
listType,
);
}
if (branchResult is InvalidConstant) {
return branchResult;
}
}
case MapLiteralEntry():
return InvalidConstant.forEntity(
entity: element,
diagnosticCode: CompileTimeErrorCode.MAP_ENTRY_NOT_IN_MAP,
);
case SpreadElement():
var spread = evaluateConstant(element.expression);
switch (spread) {
case InvalidConstant():
return spread;
case DartObjectImpl():
// Special case for ...?
if (spread.isNull && element.isNullAware) {
continue;
}
var listValue = spread.toListValue() ?? spread.toSetValue();
if (listValue == null) {
return InvalidConstant.forEntity(
entity: element.expression,
diagnosticCode:
CompileTimeErrorCode.CONST_SPREAD_EXPECTED_LIST_OR_SET,
);
}
list.addAll(listValue);
}
case NullAwareElement():
var value = evaluateConstant(element.value);
switch (value) {
case InvalidConstant():
return value;
case DartObjectImpl():
if (value.isNull) {
continue;
}
var result = _buildListConstant(
list,
[element.value],
typeSystem,
listType,
);
return result;
}
}
}
var elementType = listType.typeArguments.first;
return DartObjectImpl(
typeSystem,
listType,
ListState(elementType: elementType, elements: list),
);
}
/// Builds a map constant by adding the evaluated entries of [elements] to
/// the given [map].
///
/// The [typeSystem] and [mapType] are used to create a valid map constant.
/// We return an [InvalidConstant] if the evaluation of any of the elements
/// failed.
Constant _buildMapConstant(
Map<DartObjectImpl, DartObjectImpl> map,
List<CollectionElement> elements,
TypeSystemImpl typeSystem,
InterfaceTypeImpl mapType,
) {
for (var element in elements) {
switch (element) {
case Expression():
return InvalidConstant.forEntity(
entity: element,
diagnosticCode: CompileTimeErrorCode.EXPRESSION_IN_MAP,
);
case ForElement():
return InvalidConstant.forEntity(
entity: element,
diagnosticCode: CompileTimeErrorCode.CONST_EVAL_FOR_ELEMENT,
);
case IfElement():
var condition = evaluateConstant(element.expression);
switch (condition) {
case InvalidConstant():
return condition;
case DartObjectImpl():
// If the condition is unknown, we mark this map as unknown.
if (condition.isUnknown) {
var keyType = mapType.typeArguments[0];
var valueType = mapType.typeArguments[1];
return DartObjectImpl(
typeSystem,
mapType,
MapState.unknown(keyType, valueType),
);
}
Constant? branchResult;
var conditionValue = condition.toBoolValue();
if (conditionValue == null) {
return InvalidConstant.forEntity(
entity: element.expression,
diagnosticCode: CompileTimeErrorCode.NON_BOOL_CONDITION,
);
} else if (conditionValue) {
branchResult = _buildMapConstant(
map,
[element.thenElement],
typeSystem,
mapType,
);
} else if (element.elseElement != null) {
branchResult = _buildMapConstant(
map,
[element.elseElement!],
typeSystem,
mapType,
);
}
if (branchResult is InvalidConstant) {
return branchResult;
}
}
case MapLiteralEntry():
var keyResult = evaluateConstant(element.key);
var valueResult = evaluateConstant(element.value);
switch (keyResult) {
case InvalidConstant():
return keyResult;
case DartObjectImpl():
switch (valueResult) {
case InvalidConstant():
return valueResult;
case DartObjectImpl():
map[keyResult] = valueResult;
}
}
case SpreadElement():
var spread = evaluateConstant(element.expression);
switch (spread) {
case InvalidConstant():
return spread;
case DartObjectImpl():
// Special case for ...?
if (spread.isNull && element.isNullAware) {
continue;
}
var mapValue = spread.toMapValue();
if (mapValue == null) {
return InvalidConstant.forEntity(
entity: element.expression,
diagnosticCode:
CompileTimeErrorCode.CONST_SPREAD_EXPECTED_MAP,
);
}
map.addAll(mapValue);
}
case NullAwareElement():
// TODO(cstefantsova): Should it rather be its own code, for example,
// `CompileTimeErrorCode.NULL_AWARE_ELEMENT_IN_MAP`?
return InvalidConstant.forEntity(
entity: element,
diagnosticCode: CompileTimeErrorCode.EXPRESSION_IN_MAP,
);
}
}
var keyType = mapType.typeArguments[0];
var valueType = mapType.typeArguments[1];
return DartObjectImpl(
typeSystem,
mapType,
MapState(keyType: keyType, valueType: valueType, entries: map),
);
}
/// Builds a set constant by adding the evaluated entries of [elements] to
/// the given [set].
///
/// The [typeSystem] and [setType] are used to create a valid set constant.
/// We return an [InvalidConstant] if the evaluation of any of the elements
/// failed.
Constant _buildSetConstant(
Set<DartObjectImpl> set,
List<CollectionElement> elements,
TypeSystemImpl typeSystem,
InterfaceTypeImpl setType,
) {
for (var element in elements) {
switch (element) {
case Expression():
var expression = evaluateConstant(element);
switch (expression) {
case InvalidConstant():
return expression;
case DartObjectImpl():
set.add(expression);
}
case ForElement():
return InvalidConstant.forEntity(
entity: element,
diagnosticCode: CompileTimeErrorCode.CONST_EVAL_FOR_ELEMENT,
);
case IfElement():
var condition = evaluateConstant(element.expression);
switch (condition) {
case InvalidConstant():
return condition;
case DartObjectImpl():
// If the condition is unknown, we mark this set as unknown.
if (condition.isUnknown) {
var elementType = setType.typeArguments.first;
return DartObjectImpl(
typeSystem,
setType,
SetState.unknown(elementType),
);
}
Constant? branchResult;
var conditionValue = condition.toBoolValue();
if (conditionValue == null) {
return InvalidConstant.forEntity(
entity: element.expression,
diagnosticCode: CompileTimeErrorCode.NON_BOOL_CONDITION,
);
} else if (conditionValue) {
branchResult = _buildSetConstant(
set,
[element.thenElement],
typeSystem,
setType,
);
} else if (element.elseElement != null) {
branchResult = _buildSetConstant(
set,
[element.elseElement!],
typeSystem,
setType,
);
}
if (branchResult is InvalidConstant) {
return branchResult;
}
}
case MapLiteralEntry():
return InvalidConstant.forEntity(
entity: element,
diagnosticCode: CompileTimeErrorCode.MAP_ENTRY_NOT_IN_MAP,
);
case SpreadElement():
var spread = evaluateConstant(element.expression);
switch (spread) {
case InvalidConstant():
return spread;
case DartObjectImpl():
// Special case for ...?
if (spread.isNull && element.isNullAware) {
continue;
}
var setValue = spread.toSetValue() ?? spread.toListValue();
if (setValue == null) {
return InvalidConstant.forEntity(
entity: element.expression,
diagnosticCode:
CompileTimeErrorCode.CONST_SPREAD_EXPECTED_LIST_OR_SET,
);
}
set.addAll(setValue);
}
case NullAwareElement():
var value = evaluateConstant(element.value);
switch (value) {
case InvalidConstant():
return value;
case DartObjectImpl():
if (value.isNull) {
continue;
}
var result = _buildSetConstant(
set,
[element.value],
typeSystem,
setType,
);
return result;
}
}
}
var elementType = setType.typeArguments.first;
return DartObjectImpl(
typeSystem,
setType,
SetState(elementType: elementType, elements: set),
);
}
/// Returns the result of concatenating [astNodes].
///
/// If there's an [InvalidConstant] found, it will return early.
Constant _concatenateNodes(Expression node, List<AstNode> astNodes) {
Constant? result;
for (AstNode astNode in astNodes) {
var constant = evaluateConstant(astNode);
if (constant is! DartObjectImpl) {
return constant;
}
if (result == null) {
result = constant;
} else if (result is DartObjectImpl) {
result = _dartObjectComputer.concatenate(node, result, constant);
if (result is InvalidConstant) {
return result;
}
}
}
if (result == null) {
// No errors have been detected, but we did not concatenate any nodes.
return DartObjectImpl(
typeSystem,
_typeProvider.stringType,
StringState.UNKNOWN_VALUE,
);
}
return result;
}
/// Attempt to evaluate a constant property access.
///
/// Return a valid [DartObjectImpl] if the given [targetResult] represents a
/// `String` and the [identifier] is `length`, an [InvalidConstant] if there's
/// an error, and `null` otherwise.
Constant? _evaluatePropertyAccess(
DartObjectImpl targetResult,
SimpleIdentifier identifier,
AstNode errorNode, {
required bool isNullAware,
}) {
var propertyElement = identifier.element;
if (propertyElement is GetterElement && propertyElement.isStatic) {
return null;
}
var propertyContainer = propertyElement?.enclosingElement;
switch (propertyContainer) {
case ExtensionElement():
return InvalidConstant.forEntity(
entity: errorNode,
diagnosticCode: CompileTimeErrorCode.CONST_EVAL_EXTENSION_METHOD,
);
case ExtensionTypeElement():
return InvalidConstant.forEntity(
entity: errorNode,
diagnosticCode: CompileTimeErrorCode.CONST_EVAL_EXTENSION_TYPE_METHOD,
);
}
var targetType = targetResult.type;
// Evaluate a constant that reads the length of a `String`.
if (identifier.name == 'length') {
if (targetType is InterfaceType && targetType.isDartCoreString) {
return _dartObjectComputer.stringLength(errorNode, targetResult);
} else if (targetType.isDartCoreNull && isNullAware) {
return ConstantEvaluationEngine._nullObject(_library);
}
}
var element = identifier.element;
if (element != null && element is ExecutableElement && element.isStatic) {
return null;
}
// No other property access is allowed except for `.length` of a `String`.
return InvalidConstant.forEntity(
entity: errorNode,
diagnosticCode: CompileTimeErrorCode.CONST_EVAL_PROPERTY_ACCESS,
arguments: [identifier.name, targetType.getDisplayString()],
);
}
/// Returns a [Constant] based on the [element] provided.
///
/// The [errorNode] is the node to be used if an error needs to be reported,
/// the [expression] is used to identify type parameter errors, and
/// [identifier] to determine the constant of any [ExecutableElement]s.
///
// TODO(kallentu): Revisit this method and clean it up a bit.
Constant _getConstantValue({
required AstNode errorNode,
required Expression? expression,
required SimpleIdentifierImpl? identifier,
required Element? element,
TypeImpl? givenType,
}) {
var errorNode2 = _evaluationEngine.configuration.errorNode(errorNode);
element = element?.baseElement;
var variableElement =
element is PropertyAccessorElement ? element.variable : element;
// TODO(srawlins): Remove this check when [FunctionReference]s are inserted
// for generic function instantiation for pre-constructor-references code.
if (expression is SimpleIdentifier &&
(expression.tearOffTypeArgumentTypes?.any(hasTypeParameterReference) ??
false)) {
return InvalidConstant.forEntity(
entity: expression,
diagnosticCode: CompileTimeErrorCode.CONST_TYPE_PARAMETER,
);
}
if (variableElement is VariableElementImpl) {
// We access values of constant variables here in two cases: when we
// compute values of other constant variables, or when we compute values
// and errors for other constant expressions. In either case we have
// already computed values of all dependencies first (or detect a cycle),
// so the value has already been computed and we can just return it.
var evaluationResult = variableElement.evaluationResult;
if (variableElement.isConst) {
switch (evaluationResult) {
case null:
// The constant value isn't computed yet, or there is an error while
// computing. We will mark it and determine whether or not to
// continue the evaluation upstream.
return InvalidConstant.genericError(
node: errorNode,
isUnresolved: true,
);
case DartObjectImpl():
if (identifier == null) {
return InvalidConstant.forEntity(
entity: errorNode,
diagnosticCode: CompileTimeErrorCode.INVALID_CONSTANT,
);
}
return _instantiateFunctionTypeForSimpleIdentifier(
identifier,
evaluationResult,
);
case InvalidConstant():
// TODO(kallentu): Investigate and fix the test failures that occur
// if we remove `avoidReporting`.
return InvalidConstant.forEntity(
entity: errorNode,
diagnosticCode: CompileTimeErrorCode.INVALID_CONSTANT,
avoidReporting: true,
isUnresolved: true,
);
}
}
} else if (variableElement is ConstructorElementImpl &&
expression != null) {
return DartObjectImpl(
typeSystem,
expression.typeOrThrow,
FunctionState(variableElement),
);
} else if (variableElement is ExecutableElementImpl) {
if (variableElement.isStatic) {
var rawType = DartObjectImpl(
typeSystem,
variableElement.type,
FunctionState(variableElement),
);
if (identifier == null) {
return InvalidConstant.forEntity(
entity: errorNode,
diagnosticCode: CompileTimeErrorCode.INVALID_CONSTANT,
);
}
return _instantiateFunctionTypeForSimpleIdentifier(identifier, rawType);
}
} else if (variableElement is InterfaceElementImpl) {
var type =
givenType ??
variableElement.instantiateImpl(
typeArguments:
variableElement.typeParameters
.map((t) => _typeProvider.dynamicType)
.toFixedList(),
nullabilitySuffix: NullabilitySuffix.none,
);
return DartObjectImpl(
typeSystem,
_typeProvider.typeType,
TypeState(type),
);
} else if (variableElement is DynamicElementImpl) {
return DartObjectImpl(
typeSystem,
_typeProvider.typeType,
TypeState(_typeProvider.dynamicType),
);
} else if (variableElement is TypeAliasElementImpl) {
var type =
givenType ??
variableElement.instantiate(
typeArguments:
variableElement.typeParameters
.map((t) => t.bound ?? _typeProvider.dynamicType)
.toList(),
nullabilitySuffix: NullabilitySuffix.none,
);
return DartObjectImpl(
typeSystem,
_typeProvider.typeType,
TypeState(type),
);
} else if (variableElement is NeverElementImpl) {
return DartObjectImpl(
typeSystem,
_typeProvider.typeType,
TypeState(_typeProvider.neverType),
);
} else if (variableElement is TypeParameterElement) {
// Constants may refer to type parameters only if the constructor-tearoffs
// feature is enabled.
if (_library.featureSet.isEnabled(Feature.constructor_tearoffs)) {
var typeArgument = _lexicalTypeEnvironment?[variableElement];
if (typeArgument != null) {
return DartObjectImpl(
typeSystem,
_typeProvider.typeType,
TypeState(typeArgument),
);
}
return InvalidConstant.forEntity(
entity: errorNode2,
diagnosticCode: CompileTimeErrorCode.CONST_TYPE_PARAMETER,
);
}
}
// The expression is unresolved by the time we are evaluating it. We'll mark
// it and return immediately.
if (expression != null && expression.staticType is InvalidType) {
return InvalidConstant.genericError(node: errorNode, isUnresolved: true);
}
// TODO(srawlins): Use a specific error code.
// https://github.com/dart-lang/sdk/issues/47061
return InvalidConstant.genericError(node: errorNode2);
}
/// Returns the appropriate error for accessing an element in a deferred
/// library.
///
/// If no specific error can be chosen, an [InvalidConstant] error using
/// [CompileTimeErrorCode.INVALID_CONSTANT] is returned.
InvalidConstant _getDeferredLibraryError(
AstNode node,
SyntacticEntity errorTarget,
) {
var errorCode = () {
AstNode? previous;
for (AstNode? current = node; current != null;) {
if (current is Annotation) {
return CompileTimeErrorCode
.INVALID_ANNOTATION_CONSTANT_VALUE_FROM_DEFERRED_LIBRARY;
} else if (current is ConstantContextForExpressionImpl) {
return CompileTimeErrorCode
.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE_FROM_DEFERRED_LIBRARY;
} else if (current is DefaultFormalParameter) {
return CompileTimeErrorCode
.NON_CONSTANT_DEFAULT_VALUE_FROM_DEFERRED_LIBRARY;
} else if (current is IfElement && current.expression == node) {
return CompileTimeErrorCode
.IF_ELEMENT_CONDITION_FROM_DEFERRED_LIBRARY;
} else if (current is InstanceCreationExpression) {
return CompileTimeErrorCode
.CONST_CONSTRUCTOR_CONSTANT_FROM_DEFERRED_LIBRARY;
} else if (current is ListLiteral) {
return CompileTimeErrorCode
.NON_CONSTANT_LIST_ELEMENT_FROM_DEFERRED_LIBRARY;
} else if (current is MapLiteralEntry) {
if (previous == current.key) {
return CompileTimeErrorCode
.NON_CONSTANT_MAP_KEY_FROM_DEFERRED_LIBRARY;
} else {
return CompileTimeErrorCode
.NON_CONSTANT_MAP_VALUE_FROM_DEFERRED_LIBRARY;
}
} else if (current is RecordLiteral) {
return CompileTimeErrorCode
.NON_CONSTANT_RECORD_FIELD_FROM_DEFERRED_LIBRARY;
} else if (current is SetOrMapLiteral) {
return CompileTimeErrorCode.SET_ELEMENT_FROM_DEFERRED_LIBRARY;
} else if (current is SpreadElement) {
return CompileTimeErrorCode.SPREAD_EXPRESSION_FROM_DEFERRED_LIBRARY;
} else if (current is SwitchCase) {
return CompileTimeErrorCode
.NON_CONSTANT_CASE_EXPRESSION_FROM_DEFERRED_LIBRARY;
} else if (current is SwitchPatternCase) {
return CompileTimeErrorCode.PATTERN_CONSTANT_FROM_DEFERRED_LIBRARY;
} else if (current is VariableDeclaration) {
return CompileTimeErrorCode
.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE_FROM_DEFERRED_LIBRARY;
}
previous = current;
current = current.parent;
}
}();
if (errorCode != null) {
return InvalidConstant.forEntity(
entity: errorTarget,
diagnosticCode: errorCode,
);
}
return InvalidConstant.forEntity(
entity: node,
diagnosticCode: CompileTimeErrorCode.INVALID_CONSTANT,
);
}
/// If the type of [value] is a generic [FunctionType], and [node] has type
/// argument types, returns [value] type-instantiated with those [node]'s
/// type argument types, otherwise returns [value].
DartObjectImpl _instantiateFunctionType(
FunctionReferenceImpl node,
DartObjectImpl value,
) {
var functionElement = value.toFunctionValue2();
if (functionElement is! ExecutableElement2OrMember) {
return value;
}
var valueType = functionElement.type;
if (valueType.typeParameters.isNotEmpty) {
var typeArgumentTypes = node.typeArgumentTypes;
if (typeArgumentTypes != null && typeArgumentTypes.isNotEmpty) {
var instantiatedType = functionElement.type.instantiate(
typeArgumentTypes,
);
var substitution = _substitution;
if (substitution != null) {
instantiatedType = substitution.mapFunctionType(instantiatedType);
}
return value.typeInstantiate(
typeSystem,
instantiatedType,
typeArgumentTypes,
);
}
}
return value;
}
/// If the type of [value] is a generic [FunctionType], and [node] is a
/// [SimpleIdentifier] with tear-off type argument types, returns [value]
/// type-instantiated with those [node]'s tear-off type argument types,
/// otherwise returns [value].
Constant _instantiateFunctionTypeForSimpleIdentifier(
SimpleIdentifierImpl node,
DartObjectImpl value,
) {
// TODO(srawlins): When all code uses [FunctionReference]s generated via
// generic function instantiation, remove this method and all call sites.
var functionElement = value.toFunctionValue2();
if (functionElement is! ExecutableElement2OrMember) {
return value;
}
var valueType = functionElement.type;
if (valueType.typeParameters.isNotEmpty) {
var tearOffTypeArgumentTypes = node.tearOffTypeArgumentTypes;
if (tearOffTypeArgumentTypes != null &&
tearOffTypeArgumentTypes.isNotEmpty) {
var instantiatedType = functionElement.type.instantiate(
tearOffTypeArgumentTypes,
);
return value.typeInstantiate(
typeSystem,
instantiatedType,
tearOffTypeArgumentTypes,
);
}
}
return value;
}
// Common invalid constants for method invocations and dot shorthand
// invocations.
Constant _invalidConstantForMethodInvocation(Expression node) {
// Some methods aren't resolved by the time we are evaluating it. We'll mark
// it and return immediately.
if (node.staticType is InvalidType) {
return InvalidConstant.forEntity(
entity: node,
diagnosticCode: CompileTimeErrorCode.INVALID_CONSTANT,
isUnresolved: true,
);
}
return InvalidConstant.forEntity(
entity: node,
diagnosticCode: CompileTimeErrorCode.CONST_EVAL_METHOD_INVOCATION,
);
}
/// Returns the first not-potentially constant error found with [node] or
/// `null` if there are none.
InvalidConstant? _reportNotPotentialConstants(AstNode node) {
var notPotentiallyConstants = getNotPotentiallyConstants(
node,
featureSet: _library.featureSet,
);
if (notPotentiallyConstants.isEmpty) return null;
// Only report the first invalid constant we see.
return InvalidConstant.forEntity(
entity: notPotentiallyConstants.first,
diagnosticCode: CompileTimeErrorCode.INVALID_CONSTANT,
);
}
/// Return the value of the given [expression], or a representation of a fake
/// constant to continue the evaluation if the expression is unresolved.
Constant _valueOf(Expression expression, TypeImpl defaultType) {
var expressionValue = evaluateConstant(expression);
switch (expressionValue) {
// TODO(kallentu): g3 relies on reporting errors found here, but also
// being able to continue the evaluation with populating fields. Fix the
// interaction with g3 more elegantly.
case InvalidConstant(isUnresolved: true):
if (!expressionValue.avoidReporting) {
_diagnosticReporter.atOffset(
offset: expressionValue.offset,
length: expressionValue.length,
diagnosticCode: expressionValue.diagnosticCode,
arguments: expressionValue.arguments,
contextMessages: expressionValue.contextMessages,
);
}
return ConstantEvaluationEngine._unresolvedObject(
_library,
defaultType,
);
case Constant():
return expressionValue;
}
}
}
/// A utility class that contains methods for manipulating instances of a Dart
/// class and for collecting errors during evaluation.
class DartObjectComputer {
final TypeSystemImpl _typeSystem;
final FeatureSet _featureSet;
DartObjectComputer(this._typeSystem, this._featureSet);
Constant add(
BinaryExpression node,
DartObjectImpl leftOperand,
DartObjectImpl rightOperand,
) {
try {
return leftOperand.add(_typeSystem, rightOperand);
} on EvaluationException catch (exception) {
return InvalidConstant.forEntity(
entity: node,
diagnosticCode: exception.diagnosticCode,
);
}
}
/// Return the result of applying boolean conversion to the
/// [evaluationResult]. The [node] is the node against which errors should be
/// reported.
Constant applyBooleanConversion(
AstNode node,
DartObjectImpl evaluationResult,
) {
try {
return evaluationResult.convertToBool(_typeSystem);
} on EvaluationException catch (exception) {
return InvalidConstant.forEntity(
entity: node,
diagnosticCode: exception.diagnosticCode,
);
}
}
Constant bitNot(Expression node, DartObjectImpl evaluationResult) {
try {
return evaluationResult.bitNot(_typeSystem);
} on EvaluationException catch (exception) {
return InvalidConstant.forEntity(
entity: node,
diagnosticCode: exception.diagnosticCode,
);
}
}
Constant castToType(
AsExpression node,
DartObjectImpl expression,
DartObjectImpl type,
) {
try {
return expression.castToType(_typeSystem, type);
} on EvaluationException catch (exception) {
return InvalidConstant.forEntity(
entity: node,
diagnosticCode: exception.diagnosticCode,
);
}
}
Constant concatenate(
Expression node,
DartObjectImpl leftOperand,
DartObjectImpl rightOperand,
) {
try {
return leftOperand.concatenate(_typeSystem, rightOperand);
} on EvaluationException catch (exception) {
return InvalidConstant.forEntity(
entity: node,
diagnosticCode: exception.diagnosticCode,
);
}
}
Constant divide(
BinaryExpression node,
DartObjectImpl leftOperand,
DartObjectImpl rightOperand,
) {
try {
return leftOperand.divide(_typeSystem, rightOperand);
} on EvaluationException catch (exception) {
return InvalidConstant.forEntity(
entity: node,
diagnosticCode: exception.diagnosticCode,
);
}
}
Constant eagerAnd(
BinaryExpression node,
DartObjectImpl leftOperand,
DartObjectImpl rightOperand,
) {
try {
return leftOperand.eagerAnd(_typeSystem, rightOperand);
} on EvaluationException catch (exception) {
return InvalidConstant.forEntity(
entity: node,
diagnosticCode: exception.diagnosticCode,
);
}
}
Constant eagerOr(
BinaryExpression node,
DartObjectImpl leftOperand,
DartObjectImpl rightOperand,
) {
try {
return leftOperand.eagerOr(_typeSystem, rightOperand);
} on EvaluationException catch (exception) {
return InvalidConstant.forEntity(
entity: node,
diagnosticCode: exception.diagnosticCode,
);
}
}
Constant eagerXor(
BinaryExpression node,
DartObjectImpl leftOperand,
DartObjectImpl rightOperand,
) {
try {
return leftOperand.eagerXor(_typeSystem, rightOperand);
} on EvaluationException catch (exception) {
return InvalidConstant.forEntity(
entity: node,
diagnosticCode: exception.diagnosticCode,
);
}
}
Constant equalEqual(
Expression node,
DartObjectImpl leftOperand,
DartObjectImpl rightOperand,
) {
try {
return leftOperand.equalEqual(_typeSystem, _featureSet, rightOperand);
} on EvaluationException catch (exception) {
return InvalidConstant.forEntity(
entity: node,
diagnosticCode: exception.diagnosticCode,
);
}
}
Constant greaterThan(
BinaryExpression node,
DartObjectImpl leftOperand,
DartObjectImpl rightOperand,
) {
try {
return leftOperand.greaterThan(_typeSystem, rightOperand);
} on EvaluationException catch (exception) {
return InvalidConstant.forEntity(
entity: node,
diagnosticCode: exception.diagnosticCode,
);
}
}
Constant greaterThanOrEqual(
BinaryExpression node,
DartObjectImpl leftOperand,
DartObjectImpl rightOperand,
) {
try {
return leftOperand.greaterThanOrEqual(_typeSystem, rightOperand);
} on EvaluationException catch (exception) {
return InvalidConstant.forEntity(
entity: node,
diagnosticCode: exception.diagnosticCode,
);
}
}
Constant integerDivide(
BinaryExpression node,
DartObjectImpl leftOperand,
DartObjectImpl rightOperand,
) {
try {
return leftOperand.integerDivide(_typeSystem, rightOperand);
} on EvaluationException catch (exception) {
return InvalidConstant.forEntity(
entity: node,
diagnosticCode: exception.diagnosticCode,
isRuntimeException: exception.isRuntimeException,
);
}
}
Constant isIdentical(
Expression node,
DartObjectImpl leftOperand,
DartObjectImpl rightOperand,
) {
try {
return leftOperand.isIdentical2(_typeSystem, rightOperand);
} on EvaluationException catch (exception) {
return InvalidConstant.forEntity(
entity: node,
diagnosticCode: exception.diagnosticCode,
);
}
}
Constant lazyAnd(
BinaryExpression node,
DartObjectImpl leftOperand,
DartObjectImpl Function() rightOperandComputer,
) {
try {
return leftOperand.lazyAnd(_typeSystem, rightOperandComputer);
} on EvaluationException catch (exception) {
return InvalidConstant.forEntity(
entity: node,
diagnosticCode: exception.diagnosticCode,
);
}
}
Constant lazyOr(
BinaryExpression node,
DartObjectImpl leftOperand,
DartObjectImpl Function() rightOperandComputer,
) {
try {
return leftOperand.lazyOr(_typeSystem, rightOperandComputer);
} on EvaluationException catch (exception) {
return InvalidConstant.forEntity(
entity: node,
diagnosticCode: exception.diagnosticCode,
);
}
}
Constant lazyQuestionQuestion(
Expression node,
DartObjectImpl leftOperand,
Constant Function() rightOperandComputer,
) {
if (leftOperand.isNull) {
return rightOperandComputer();
}
return leftOperand;
}
Constant lessThan(
BinaryExpression node,
DartObjectImpl leftOperand,
DartObjectImpl rightOperand,
) {
try {
return leftOperand.lessThan(_typeSystem, rightOperand);
} on EvaluationException catch (exception) {
return InvalidConstant.forEntity(
entity: node,
diagnosticCode: exception.diagnosticCode,
);
}
}
Constant lessThanOrEqual(
BinaryExpression node,
DartObjectImpl leftOperand,
DartObjectImpl rightOperand,
) {
try {
return leftOperand.lessThanOrEqual(_typeSystem, rightOperand);
} on EvaluationException catch (exception) {
return InvalidConstant.forEntity(
entity: node,
diagnosticCode: exception.diagnosticCode,
);
}
}
Constant logicalNot(Expression node, DartObjectImpl evaluationResult) {
try {
return evaluationResult.logicalNot(_typeSystem);
} on EvaluationException catch (exception) {
return InvalidConstant.forEntity(
entity: node,
diagnosticCode: exception.diagnosticCode,
);
}
}
Constant logicalShiftRight(
BinaryExpression node,
DartObjectImpl leftOperand,
DartObjectImpl rightOperand,
) {
try {
return leftOperand.logicalShiftRight(_typeSystem, rightOperand);
} on EvaluationException catch (exception) {
return InvalidConstant.forEntity(
entity: node,
diagnosticCode: exception.diagnosticCode,
);
}
}
Constant minus(
BinaryExpression node,
DartObjectImpl leftOperand,
DartObjectImpl rightOperand,
) {
try {
return leftOperand.minus(_typeSystem, rightOperand);
} on EvaluationException catch (exception) {
return InvalidConstant.forEntity(
entity: node,
diagnosticCode: exception.diagnosticCode,
);
}
}
Constant negated(Expression node, DartObjectImpl evaluationResult) {
try {
return evaluationResult.negated(_typeSystem);
} on EvaluationException catch (exception) {
return InvalidConstant.forEntity(
entity: node,
diagnosticCode: exception.diagnosticCode,
);
}
}
Constant notEqual(
BinaryExpression node,
DartObjectImpl leftOperand,
DartObjectImpl rightOperand,
) {
try {
return leftOperand.notEqual(_typeSystem, _featureSet, rightOperand);
} on EvaluationException catch (exception) {
return InvalidConstant.forEntity(
entity: node,
diagnosticCode: exception.diagnosticCode,
);
}
}
Constant performToString(AstNode node, DartObjectImpl evaluationResult) {
try {
return evaluationResult.performToString(_typeSystem);
} on EvaluationException catch (exception) {
return InvalidConstant.forEntity(
entity: node,
diagnosticCode: exception.diagnosticCode,
);
}
}
Constant remainder(
BinaryExpression node,
DartObjectImpl leftOperand,
DartObjectImpl rightOperand,
) {
try {
return leftOperand.remainder(_typeSystem, rightOperand);
} on EvaluationException catch (exception) {
return InvalidConstant.forEntity(
entity: node,
diagnosticCode: exception.diagnosticCode,
);
}
}
Constant shiftLeft(
BinaryExpression node,
DartObjectImpl leftOperand,
DartObjectImpl rightOperand,
) {
try {
return leftOperand.shiftLeft(_typeSystem, rightOperand);
} on EvaluationException catch (exception) {
return InvalidConstant.forEntity(
entity: node,
diagnosticCode: exception.diagnosticCode,
);
}
}
Constant shiftRight(
BinaryExpression node,
DartObjectImpl leftOperand,
DartObjectImpl rightOperand,
) {
try {
return leftOperand.shiftRight(_typeSystem, rightOperand);
} on EvaluationException catch (exception) {
return InvalidConstant.forEntity(
entity: node,
diagnosticCode: exception.diagnosticCode,
);
}
}
Constant stringLength(AstNode node, DartObjectImpl evaluationResult) {
try {
return evaluationResult.stringLength(_typeSystem);
} on EvaluationException catch (exception) {
return InvalidConstant.forEntity(
entity: node,
diagnosticCode: exception.diagnosticCode,
);
}
}
Constant times(
BinaryExpression node,
DartObjectImpl leftOperand,
DartObjectImpl rightOperand,
) {
try {
return leftOperand.times(_typeSystem, rightOperand);
} on EvaluationException catch (exception) {
return InvalidConstant.forEntity(
entity: node,
diagnosticCode: exception.diagnosticCode,
);
}
}
Constant typeInstantiate(
DartObjectImpl function,
List<TypeImpl> typeArguments,
Expression node,
TypeArgumentList typeArgumentsErrorNode,
) {
var rawType = function.type;
if (rawType is FunctionTypeImpl) {
if (typeArguments.length != rawType.typeParameters.length) {
if (node is SimpleIdentifier) {
return InvalidConstant.forEntity(
entity: typeArgumentsErrorNode,
diagnosticCode:
CompileTimeErrorCode.WRONG_NUMBER_OF_TYPE_ARGUMENTS_FUNCTION,
arguments: [
node.name,
rawType.typeParameters.length,
typeArguments.length,
],
);
}
return InvalidConstant.forEntity(
entity: typeArgumentsErrorNode,
diagnosticCode:
CompileTimeErrorCode
.WRONG_NUMBER_OF_TYPE_ARGUMENTS_ANONYMOUS_FUNCTION,
arguments: [rawType.typeParameters.length, typeArguments.length],
);
}
var type = rawType.instantiate(typeArguments);
return function.typeInstantiate(_typeSystem, type, typeArguments);
} else {
return InvalidConstant.forEntity(
entity: node,
diagnosticCode: CompileTimeErrorCode.INVALID_CONSTANT,
);
}
}
Constant typeTest(
IsExpression node,
DartObjectImpl expression,
DartObjectImpl type,
) {
try {
DartObjectImpl result = expression.hasType(_typeSystem, type);
if (node.notOperator != null) {
return result.logicalNot(_typeSystem);
}
return result;
} on EvaluationException catch (exception) {
return InvalidConstant.forEntity(
entity: node,
diagnosticCode: exception.diagnosticCode,
);
}
}
}
class _EnumConstant {
final int index;
final String name;
_EnumConstant({required this.index, required this.name});
}
/// The result of evaluation the initializers declared on a const constructor.
class _InitializersEvaluationResult {
/// The result of a const evaluation of an initializer.
///
/// If the evaluation of the const instance creation expression is incomplete,
/// then [result] will be `null`.
///
/// If a redirecting initializer which redirects to a const constructor was
/// encountered, [result] is the result of evaluating that call.
///
/// If an assert initializer is encountered, and the evaluation of this assert
/// results in an error or a `false` value, [result] is an [InvalidConstant].
final Constant? result;
/// Whether evaluation of the const instance creation expression which led to
/// evaluating constructor initializers is complete.
///
/// If `true`, `result` should be used as the result of said const instance
/// creation expression evaluation.
final bool evaluationIsComplete;
/// If a superinitializer was encountered, the name of the super constructor,
/// otherwise `null`.
final String? superName;
/// If a superinitializer was encountered, the arguments passed to the super
/// constructor, otherwise `null`.
final List<Expression>? superArguments;
_InitializersEvaluationResult(
this.result, {
required this.evaluationIsComplete,
this.superName,
this.superArguments,
});
}
/// An evaluator which evaluates a const instance creation expression.
///
/// [_InstanceCreationEvaluator.evaluate] is the main entrypoint.
class _InstanceCreationEvaluator {
/// Parameter to "fromEnvironment" methods that denotes the default value.
static const String _defaultValueParam = 'defaultValue';
final ConstantEvaluationEngine _evaluationEngine;
/// The set of variables declared on the command line using '-D'.
final DeclaredVariables _declaredVariables;
final LibraryElementImpl _library;
final BooleanDiagnosticListener _externalDiagnosticListener =
BooleanDiagnosticListener();
/// An error reporter for errors determined while computing values for field
/// initializers, or default values for the constructor parameters.
///
/// Such errors cannot be reported into [ConstantVisitor._diagnosticReporter],
/// because they usually happen in a different source. But they still should
/// cause a constant evaluation error for the current node.
late final DiagnosticReporter _externalDiagnosticReporter =
DiagnosticReporter(
_externalDiagnosticListener,
_constructor.firstFragment.libraryFragment.source,
);
late final ConstantVisitor _initializerVisitor = ConstantVisitor(
_evaluationEngine,
_constructor.library,
_externalDiagnosticReporter,
lexicalEnvironment: _parameterMap,
lexicalTypeEnvironment: _typeParameterMap,
substitution: Substitution.fromInterfaceType(definingType),
);
/// The node used for most error reporting.
final AstNode _errorNode;
final ConstructorElementMixin2 _constructor;
final List<TypeImpl>? _typeArguments;
final ConstructorInvocation _invocation;
final Map<String, NamedExpression> _namedNodes;
final Map<String, DartObjectImpl> _namedValues;
final List<DartObjectImpl> _argumentValues;
final Map<TypeParameterElement, TypeImpl> _typeParameterMap = HashMap();
final Map<String, DartObjectImpl> _parameterMap = HashMap();
final Map<String, DartObjectImpl> _fieldMap = HashMap();
/// Constructor for [_InstanceCreationEvaluator].
///
/// This constructor is private, as the entry point for using a
/// [_InstanceCreationEvaluator] is the static method,
/// [_InstanceCreationEvaluator.evaluate].
_InstanceCreationEvaluator._(
this._evaluationEngine,
this._declaredVariables,
this._library,
this._errorNode,
this._constructor,
this._typeArguments, {
required Map<String, NamedExpression> namedNodes,
required Map<String, DartObjectImpl> namedValues,
required List<DartObjectImpl> argumentValues,
required ConstructorInvocation invocation,
}) : _namedNodes = namedNodes,
_namedValues = namedValues,
_argumentValues = argumentValues,
_invocation = invocation;
InterfaceTypeImpl get definingType => _constructor.returnType;
DartObjectImpl? get firstArgument => _argumentValues[0];
TypeProviderImpl get typeProvider => _library.typeProvider;
TypeSystemImpl get typeSystem => _library.typeSystem;
/// Evaluates this constructor call as a factory constructor call.
Constant evaluateFactoryConstructorCall(List<Expression> arguments) {
var definingClass = _constructor.enclosingElement;
var argumentCount = arguments.length;
if (_constructor.name == "fromEnvironment") {
if (!_checkFromEnvironmentArguments(arguments, definingType)) {
return InvalidConstant.forEntity(
entity: _errorNode,
diagnosticCode: CompileTimeErrorCode.CONST_EVAL_THROWS_EXCEPTION,
);
}
String? variableName =
argumentCount < 1 ? null : firstArgument?.toStringValue();
if (definingClass == typeProvider.boolElement) {
// Special cases: https://github.com/dart-lang/sdk/issues/50045
if (variableName == 'dart.library.js_util' ||
variableName == 'dart.library.js_interop') {
return DartObjectImpl(
typeSystem,
typeProvider.boolType,
BoolState.UNKNOWN_VALUE,
);
}
return FromEnvironmentEvaluator(
typeSystem,
_declaredVariables,
).getBool2(variableName, _namedValues, _constructor);
} else if (definingClass == typeProvider.intElement) {
return FromEnvironmentEvaluator(
typeSystem,
_declaredVariables,
).getInt2(variableName, _namedValues, _constructor);
} else if (definingClass == typeProvider.stringElement) {
return FromEnvironmentEvaluator(
typeSystem,
_declaredVariables,
).getString2(variableName, _namedValues, _constructor);
}
} else if (_constructor.name == 'hasEnvironment' &&
definingClass == typeProvider.boolElement) {
var name = argumentCount < 1 ? null : firstArgument?.toStringValue();
return FromEnvironmentEvaluator(
typeSystem,
_declaredVariables,
).hasEnvironment(name);
} else if (_constructor.name == 'new' &&
definingClass == typeProvider.symbolElement &&
argumentCount == 1) {
if (!_checkSymbolArguments(arguments)) {
return InvalidConstant.forEntity(
entity: _errorNode,
diagnosticCode: CompileTimeErrorCode.CONST_EVAL_THROWS_EXCEPTION,
);
}
return DartObjectImpl(
typeSystem,
definingType,
SymbolState(firstArgument?.toStringValue()),
);
}
// Either it's an external const factory constructor that we can't
// emulate, or an error occurred (a cycle, or a const constructor trying to
// delegate to a non-const constructor).
//
// In the former case, the best we can do is consider it an unknown value.
// In the latter case, the error has already been reported, so considering
// it an unknown value will suppress further errors.
return DartObjectImpl.validWithUnknownValue(typeSystem, definingType);
}
Constant evaluateGenerativeConstructorCall(List<Expression> arguments) {
InvalidConstant? error;
// Start with final fields that are initialized at their declaration site.
error = _checkFields();
if (error != null) {
return error;
}
_checkTypeParameters();
error = _checkParameters(arguments);
if (error != null) {
return error;
}
var evaluationResult = _checkInitializers();
var result = evaluationResult.result;
if (result != null && evaluationResult.evaluationIsComplete) {
return result;
}
error = _checkSuperConstructorCall(
superName: evaluationResult.superName,
superArguments: evaluationResult.superArguments,
);
if (error != null) {
return error;
}
var definingType = this.definingType;
if (definingType.element case ExtensionTypeElement element) {
var representation = _fieldMap[element.representation.name];
if (representation != null) {
return representation;
}
}
return DartObjectImpl(
typeSystem,
definingType,
GenericState(_fieldMap, invocation: _invocation),
);
}
void _addImplicitArgumentsFromSuperFormals(List<Expression> superArguments) {
var positionalIndex = 0;
for (var parameter in _constructor.formalParameters) {
if (parameter is SuperFormalParameterElement) {
var value =
SimpleIdentifierImpl(
token: StringToken(
TokenType.STRING,
parameter.name ?? '',
parameter.firstFragment.nameOffset ?? -1,
),
)
..element = parameter
..setPseudoExpressionStaticType(parameter.type);
if (parameter.isPositional) {
superArguments.insert(positionalIndex++, value);
} else {
superArguments.add(
NamedExpressionImpl(
name: LabelImpl(
label: SimpleIdentifierImpl(
token: StringToken(
TokenType.STRING,
parameter.name ?? '',
parameter.firstFragment.nameOffset ?? -1,
),
)..element = parameter,
colon: StringToken(TokenType.COLON, ':', -1),
),
expression: value,
)..setPseudoExpressionStaticType(value.typeOrThrow),
);
}
}
}
}
/// Checks for any errors in the fields of [_constructor].
///
/// Returns an [InvalidConstant] if one is found, or `null` otherwise.
InvalidConstant? _checkFields() {
var substitution = Substitution.fromInterfaceType(_constructor.returnType);
var fields = _constructor.baseElement.enclosingElement.fields;
for (var field in fields) {
if ((field.isFinal || field.isConst) && !field.isStatic) {
var fieldValue = field.evaluationResult;
// It is possible that the evaluation result is null.
// This happens for example when we have duplicate fields.
// `class Test {final x = 1; final x = 2; const Test();}`
if (fieldValue == null || fieldValue is! DartObjectImpl) {
continue;
}
// Match the value and the type.
var fieldType = substitution.substituteType(field.type);
if (!typeSystem.runtimeTypeMatch(fieldValue, fieldType)) {
var isRuntimeException = hasTypeParameterReference(field.type);
var errorNode = field.constantInitializer?.expression ?? _errorNode;
return InvalidConstant.forEntity(
entity: errorNode,
diagnosticCode:
CompileTimeErrorCode.CONST_CONSTRUCTOR_FIELD_TYPE_MISMATCH,
arguments: [
fieldValue.type.getDisplayString(),
field.name ?? '',
fieldType.getDisplayString(),
],
isRuntimeException: isRuntimeException,
);
}
_fieldMap[field.name ?? ''] = fieldValue;
}
}
return null;
}
/// Checks that the arguments to a call to `fromEnvironment()` are correct.
///
/// The [arguments] are the AST nodes of the arguments. The
/// [expectedDefaultValueType] is the allowed type of the "defaultValue"
/// parameter (if present). Note: "defaultValue" is always allowed to be
/// `null`. Returns `true` if the arguments are correct, `false` otherwise.
bool _checkFromEnvironmentArguments(
List<Expression> arguments,
InterfaceTypeImpl expectedDefaultValueType,
) {
var argumentCount = arguments.length;
if (argumentCount < 1 || argumentCount > 2) {
return false;
}
if (arguments[0] is NamedExpression) {
return false;
}
if (firstArgument!.type != typeProvider.stringType) {
return false;
}
if (argumentCount == 2) {
var secondArgument = arguments[1];
if (secondArgument is NamedExpression) {
if (!(secondArgument.name.label.name == _defaultValueParam)) {
return false;
}
var defaultValueType = _namedValues[_defaultValueParam]!.type;
if (!(defaultValueType == expectedDefaultValueType ||
defaultValueType == typeProvider.nullType)) {
return false;
}
} else {
return false;
}
}
return true;
}
/// Checks for any errors in the constant initializers of [_constructor].
///
/// Returns an [_InitializersEvaluationResult] which contain a result from a
/// redirecting constructor invocation, an [InvalidConstant], or an
/// incomplete state for further evaluation.
_InitializersEvaluationResult _checkInitializers() {
var constructorBase = _constructor.baseElement;
// If we encounter a superinitializer, store the name of the constructor,
// and the arguments.
String? superName;
List<Expression>? superArguments;
for (var initializer in constructorBase.constantInitializers) {
if (initializer is ConstructorFieldInitializer) {
var initializerExpression = initializer.expression;
var evaluationResult = _initializerVisitor.evaluateConstant(
initializerExpression,
);
switch (evaluationResult) {
case DartObjectImpl():
var fieldName = initializer.fieldName.name;
if (_fieldMap.containsKey(fieldName)) {
return _InitializersEvaluationResult(
InvalidConstant.forEntity(
entity: _errorNode,
diagnosticCode:
CompileTimeErrorCode.CONST_EVAL_THROWS_EXCEPTION,
),
evaluationIsComplete: true,
);
}
_fieldMap[fieldName] = evaluationResult;
var getter = definingType.getGetter(fieldName);
if (getter != null) {
var field = getter.variable;
if (field == null) {
return _InitializersEvaluationResult(
InvalidConstant.forElement(
element: getter,
diagnosticCode:
CompileTimeErrorCode.CONST_EVAL_THROWS_EXCEPTION,
),
evaluationIsComplete: true,
);
}
if (!typeSystem.runtimeTypeMatch(evaluationResult, field.type)) {
// Mark the type mismatch error as a runtime exception if the
// initializer is statically assignable to the field.
// TODO(kallentu): https://github.com/dart-lang/sdk/issues/53263
var isRuntimeException = _library.typeSystem.isAssignableTo(
initializerExpression.typeOrThrow,
field.type,
);
var errorNode =
isRuntimeException ? initializerExpression : _errorNode;
return _InitializersEvaluationResult(
InvalidConstant.forEntity(
entity: errorNode,
diagnosticCode:
CompileTimeErrorCode
.CONST_CONSTRUCTOR_FIELD_TYPE_MISMATCH,
arguments: [
evaluationResult.type.getDisplayString(),
fieldName,
field.type.getDisplayString(),
],
isRuntimeException: isRuntimeException,
),
evaluationIsComplete: true,
);
}
}
case InvalidConstant(isRuntimeException: false):
// Add additional information to the error in the field initializer
// because the error is reported at the location of [_errorNode].
if (evaluationResult.contextMessages.isEmpty) {
evaluationResult.contextMessages.add(
DiagnosticMessageImpl(
filePath:
_constructor
.firstFragment
.libraryFragment
.source
.fullName,
length: evaluationResult.length,
message:
"The error is in the field initializer of "
"'${_constructor.displayName}', and occurs here.",
offset: evaluationResult.offset,
url: null,
),
);
}
return _InitializersEvaluationResult(
InvalidConstant.copyWithEntity(
other: evaluationResult,
entity: _errorNode,
),
evaluationIsComplete: true,
);
case InvalidConstant():
return _InitializersEvaluationResult(
evaluationResult,
evaluationIsComplete: true,
);
}
} else if (initializer is SuperConstructorInvocation) {
var name = initializer.constructorName;
if (name != null) {
superName = name.name;
}
superArguments = initializer.argumentList.arguments.toList();
_addImplicitArgumentsFromSuperFormals(superArguments);
} else if (initializer is RedirectingConstructorInvocationImpl) {
// This is a redirecting constructor, so just evaluate the constructor
// it redirects to.
var baseElement = initializer.element;
if (baseElement != null && baseElement.isConst) {
// Instantiate the constructor with the in-scope type arguments.
var constructor = ConstructorMember.from2(baseElement, definingType);
var result = _evaluationEngine.evaluateConstructorCall(
_library,
_errorNode,
_typeArguments,
initializer.argumentList.arguments,
constructor,
_initializerVisitor,
invocation: _invocation,
);
return _InitializersEvaluationResult(
result,
evaluationIsComplete: true,
);
}
} else if (initializer is AssertInitializer) {
var condition = initializer.condition;
var evaluationResult = _initializerVisitor.evaluateConstant(condition);
switch (evaluationResult) {
case DartObjectImpl():
if (!evaluationResult.isBool ||
evaluationResult.toBoolValue() == false) {
InvalidConstant? invalidConstant;
// Adds the assert message if we are able to evaluate it.
if (initializer.message case var message?) {
var messageConstant = _initializerVisitor.evaluateConstant(
message,
);
if (messageConstant is DartObjectImpl) {
if (messageConstant.toStringValue() case var assertMessage?) {
invalidConstant = InvalidConstant.forEntity(
entity: initializer,
diagnosticCode:
CompileTimeErrorCode
.CONST_EVAL_ASSERTION_FAILURE_WITH_MESSAGE,
arguments: [assertMessage],
isRuntimeException: true,
);
}
}
}
invalidConstant ??= InvalidConstant.forEntity(
entity: initializer,
diagnosticCode:
CompileTimeErrorCode.CONST_EVAL_ASSERTION_FAILURE,
isRuntimeException: true,
);
return _InitializersEvaluationResult(
invalidConstant,
evaluationIsComplete: true,
);
}
case InvalidConstant(isRuntimeException: false):
// Add additional information to the error in the assert initializer
// because the error is reported at the location of [_errorNode].
if (evaluationResult.contextMessages.isEmpty) {
evaluationResult.contextMessages.add(
DiagnosticMessageImpl(
filePath:
_constructor
.firstFragment
.libraryFragment
.source
.fullName,
length: evaluationResult.length,
message:
"The error is in the assert initializer of "
"'${_constructor.displayName}', and occurs here.",
offset: evaluationResult.offset,
url: null,
),
);
}
return _InitializersEvaluationResult(
InvalidConstant.copyWithEntity(
other: evaluationResult,
entity: _errorNode,
),
evaluationIsComplete: true,
);
case InvalidConstant():
return _InitializersEvaluationResult(
evaluationResult,
evaluationIsComplete: true,
);
}
}
}
if (definingType.superclass != null && superArguments == null) {
superArguments = [];
_addImplicitArgumentsFromSuperFormals(superArguments);
}
return _InitializersEvaluationResult(
null,
evaluationIsComplete: false,
superName: superName,
superArguments: superArguments,
);
}
/// Checks for any errors in the parameters of [_constructor].
///
/// Returns an [InvalidConstant] if one is found, or `null` otherwise.
InvalidConstant? _checkParameters(List<Expression> arguments) {
var parameters = _constructor.formalParameters;
var parameterCount = parameters.length;
for (var i = 0; i < parameterCount; i++) {
var parameter = parameters[i];
var baseParameter = parameter.baseElement;
DartObjectImpl? argumentValue;
AstNode? errorTarget;
if (baseParameter.isNamed) {
argumentValue = _namedValues[baseParameter.name ?? ''];
errorTarget = _namedNodes[baseParameter.name ?? ''];
} else if (i < _argumentValues.length) {
argumentValue = _argumentValues[i];
errorTarget = arguments[i];
}
// No argument node that we can direct error messages to, because we
// are handling an optional parameter that wasn't specified. So just
// direct error messages to the constructor call.
errorTarget ??= _errorNode;
if (argumentValue == null && baseParameter.isOptional) {
// The parameter is an optional positional parameter for which no value
// was provided, so use the default value.
var evaluationResult = baseParameter.evaluationResult;
if (evaluationResult == null) {
// No default was provided, so the default value is null.
argumentValue = ConstantEvaluationEngine._nullObject(_library);
} else if (evaluationResult is DartObjectImpl) {
argumentValue = evaluationResult;
}
}
if (argumentValue != null) {
if (!argumentValue.isInvalid &&
!typeSystem.runtimeTypeMatch(argumentValue, parameter.type)) {
// Mark the type mismatch error as a runtime exception if the argument
// is statically assignable to the parameter.
// TODO(kallentu): https://github.com/dart-lang/sdk/issues/53263
var isEvaluationException =
errorTarget is Expression &&
_library.typeSystem.isAssignableTo(
errorTarget.typeOrThrow,
parameter.type,
);
return InvalidConstant.forEntity(
entity: errorTarget,
diagnosticCode:
CompileTimeErrorCode.CONST_CONSTRUCTOR_PARAM_TYPE_MISMATCH,
arguments: [
argumentValue.type.getDisplayString(),
parameter.type.getDisplayString(),
],
isRuntimeException: isEvaluationException,
);
}
if (baseParameter.isInitializingFormal) {
var field = (parameter as FieldFormalParameterElement).field;
if (field != null) {
var fieldType = field.type as TypeImpl;
if (fieldType != parameter.type) {
// We've already checked that the argument can be assigned to the
// parameter; we also need to check that it can be assigned to
// the field.
if (!argumentValue.isInvalid &&
!typeSystem.runtimeTypeMatch(argumentValue, fieldType)) {
return InvalidConstant.forEntity(
entity: errorTarget,
diagnosticCode:
CompileTimeErrorCode
.CONST_CONSTRUCTOR_PARAM_TYPE_MISMATCH,
arguments: [
argumentValue.type.getDisplayString(),
fieldType.getDisplayString(),
],
);
}
}
var fieldName = field.name ?? '';
if (_fieldMap.containsKey(fieldName)) {
return InvalidConstant.forEntity(
entity: _errorNode,
diagnosticCode:
CompileTimeErrorCode.CONST_EVAL_THROWS_EXCEPTION,
);
}
_fieldMap[fieldName] = argumentValue;
}
}
_parameterMap[baseParameter.name ?? ''] = argumentValue;
}
}
return null;
}
/// Checks for errors in an explicit or implicit call to `super()`
///
/// Returns an [InvalidConstant] if an error is found, or `null` otherwise.
///
/// If a superinitializer was declared on the constructor declaration,
/// [superName] and [superArguments] are the name of the super constructor
/// referenced therein, and the arguments passed to the super constructor.
/// Otherwise these parameters are `null`.
InvalidConstant? _checkSuperConstructorCall({
required String? superName,
required List<Expression>? superArguments,
}) {
var superclass = definingType.superclass;
if (superclass != null && !superclass.isDartCoreObject) {
var superConstructor = superclass.lookUpConstructor(
superName,
_constructor.library,
);
if (superConstructor == null) {
return null;
}
if (superConstructor.isConst) {
var evaluationResult = _evaluationEngine.evaluateConstructorCall(
_library,
_errorNode,
superclass.typeArguments,
superArguments ?? const [],
superConstructor,
_initializerVisitor,
);
switch (evaluationResult) {
case DartObjectImpl():
_fieldMap[GenericState.SUPERCLASS_FIELD] = evaluationResult;
case InvalidConstant(isRuntimeException: false):
// Add additional information to the error in the super constructor
// call because the error is reported at the location of
// [_errorNode].
if (evaluationResult.contextMessages.isEmpty) {
evaluationResult.contextMessages.add(
DiagnosticMessageImpl(
filePath:
_constructor
.firstFragment
.libraryFragment
.source
.fullName,
length: evaluationResult.length,
message:
"The error is in the super constructor invocation "
"of '${_constructor.displayName}', and occurs here.",
offset: evaluationResult.offset,
url: null,
),
);
} else {
evaluationResult.contextMessages.add(
_stackTraceContextMessage(superConstructor, _constructor),
);
}
return InvalidConstant.copyWithEntity(
other: evaluationResult,
entity: _errorNode,
);
case InvalidConstant():
evaluationResult.contextMessages.add(
_stackTraceContextMessage(superConstructor, _constructor),
);
return evaluationResult;
}
}
}
return null;
}
/// Checks that the arguments to a call to [Symbol.new] are correct.
///
/// The [arguments] are the AST nodes of the arguments. Returns `true` if the
/// arguments are correct, `false` otherwise.
bool _checkSymbolArguments(List<Expression> arguments) {
if (arguments.length != 1) {
return false;
}
if (arguments[0] is NamedExpression) {
return false;
}
if (firstArgument!.type != typeProvider.stringType) {
return false;
}
var name = firstArgument?.toStringValue();
if (name == null) {
return false;
}
return true;
}
void _checkTypeParameters() {
var typeParameters =
_constructor.baseElement.enclosingElement.typeParameters;
var typeArguments = _typeArguments;
if (typeParameters.isNotEmpty &&
typeArguments != null &&
typeParameters.length == typeArguments.length) {
for (int i = 0; i < typeParameters.length; i++) {
var typeParameter = typeParameters[i];
var typeArgument = typeArguments[i];
_typeParameterMap[typeParameter] = typeArgument;
}
}
}
/// Returns a context message that mimics a stack trace where [superConstructor] is
/// called by [constructor]
DiagnosticMessageImpl _stackTraceContextMessage(
ConstructorElementMixin2 superConstructor,
ConstructorElementMixin2 constructor,
) {
return DiagnosticMessageImpl(
filePath: constructor.firstFragment.libraryFragment.source.fullName,
length: 1,
message:
"The evaluated constructor '${superConstructor.displayName}' "
"is called by '${constructor.displayName}' and "
"'${constructor.displayName}' is defined here.",
offset: constructor.firstFragment.offset,
url: null,
);
}
/// Evaluates [node] as an instance creation expression using [constructor].
static Constant evaluate(
ConstantEvaluationEngine evaluationEngine,
DeclaredVariables declaredVariables,
LibraryElementImpl library,
AstNode node,
ConstructorElementMixin2 constructor,
List<TypeImpl>? typeArguments,
List<Expression> arguments,
ConstantVisitor constantVisitor, {
ConstructorInvocation? invocation,
}) {
if (!constructor.isConst) {
Token? keyword;
if (node is InstanceCreationExpression) {
keyword = node.keyword;
} else if (node is DotShorthandConstructorInvocation) {
keyword = node.constKeyword;
}
return InvalidConstant.forEntity(
entity: keyword ?? node,
diagnosticCode: CompileTimeErrorCode.CONST_WITH_NON_CONST,
);
}
if (!constructor.baseElement.firstFragment.isCycleFree) {
// It's not safe to evaluate this constructor, so bail out.
//
// Instead of reporting an error at the call-sites, we will report an
// error at each constructor in
// [ConstantVerifier.visitConstructorDeclaration].
return DartObjectImpl.validWithUnknownValue(
library.typeSystem,
constructor.returnType,
);
}
var argumentValues = <DartObjectImpl>[];
var namedNodes = <String, NamedExpression>{};
var namedValues = <String, DartObjectImpl>{};
for (var i = 0; i < arguments.length; i++) {
var argument = arguments[i];
// Use the corresponding parameter type as the default value if
// an unresolved expression is evaluated. We do this to continue the
// rest of the evaluation without producing unrelated errors.
if (argument is NamedExpressionImpl) {
var parameterType = argument.element?.type ?? InvalidTypeImpl.instance;
var argumentConstant = constantVisitor._valueOf(
argument.expression,
parameterType,
);
if (argumentConstant is! DartObjectImpl) {
return argumentConstant;
}
var name = argument.name.label.name;
namedNodes[name] = argument;
namedValues[name] = argumentConstant;
} else {
var parameterType =
i < constructor.formalParameters.length
? constructor.formalParameters[i].type
: InvalidTypeImpl.instance;
var argumentConstant = constantVisitor._valueOf(
argument,
parameterType,
);
if (argumentConstant is! DartObjectImpl) {
return argumentConstant;
}
argumentValues.add(argumentConstant);
}
}
invocation ??= ConstructorInvocation(
constructor,
argumentValues,
namedValues,
);
constructor = _followConstantRedirectionChain(constructor);
var evaluator = _InstanceCreationEvaluator._(
evaluationEngine,
declaredVariables,
library,
evaluationEngine.configuration.errorNode(node),
constructor,
typeArguments,
namedNodes: namedNodes,
namedValues: namedValues,
argumentValues: argumentValues,
invocation: invocation,
);
if (constructor.isFactory) {
// We couldn't find a non-factory constructor.
// See if it's because we reached an external const factory constructor
// that we can emulate.
return evaluator.evaluateFactoryConstructorCall(arguments);
} else {
return evaluator.evaluateGenerativeConstructorCall(arguments);
}
}
/// Attempt to follow the chain of factory redirections until a constructor is
/// reached which is not a const factory constructor. Return the constant
/// constructor which terminates the chain of factory redirections, if the
/// chain terminates. If there is a problem (e.g. a redirection can't be
/// found, or a cycle is encountered), the chain will be followed as far as
/// possible and then a const factory constructor will be returned.
static ConstructorElementMixin2 _followConstantRedirectionChain(
ConstructorElementMixin2 constructor,
) {
var constructorsVisited = <ConstructorElementMixin2>{};
while (true) {
var redirectedConstructor =
ConstantEvaluationEngine.getConstRedirectedConstructor(constructor);
if (redirectedConstructor == null) {
break;
} else {
var constructorBase = constructor.baseElement;
constructorsVisited.add(constructorBase);
var redirectedConstructorBase = redirectedConstructor.baseElement;
if (constructorsVisited.contains(redirectedConstructorBase)) {
// Cycle in redirecting factory constructors--this is not allowed
// and is checked elsewhere--see
// [ErrorVerifier.checkForRecursiveFactoryRedirect()]).
break;
}
}
constructor = redirectedConstructor;
}
return constructor;
}
}
extension on NamedType {
bool get isTypeLiteralInConstantPattern {
var parent = this.parent;
return parent is TypeLiteral && parent.parent?.parent is ConstantPattern;
}
}
extension RuntimeExtensions on TypeSystemImpl {
/// Returns whether [obj] matches the [type] according to runtime
/// type-checking rules.
bool runtimeTypeMatch(DartObjectImpl obj, TypeImpl type) {
type = type.extensionTypeErasure;
var objType = obj.type;
return isSubtypeOf(objType, type);
}
}