// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. import 'package:analyzer/dart/ast/token.dart'; import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/diagnostic/diagnostic.dart'; import 'package:analyzer/error/error.dart'; import 'package:analyzer/error/listener.dart'; import 'package:analyzer/src/dart/ast/ast.dart'; import 'package:analyzer/src/dart/ast/token.dart'; import 'package:analyzer/src/dart/constant/compute.dart'; import 'package:analyzer/src/dart/constant/constant_verifier.dart'; import 'package:analyzer/src/dart/constant/evaluation.dart'; import 'package:analyzer/src/dart/constant/potentially_constant.dart'; import 'package:analyzer/src/dart/constant/utilities.dart'; import 'package:analyzer/src/diagnostic/diagnostic.dart' as diag; /// A diagnostic listener that only records whether any constant-related /// diagnostics have been reported. class _ConstantDiagnosticListener extends DiagnosticListener { /// A flag indicating whether any constant-related diagnostics have been /// reported to this listener. bool hasConstError = false; @override void onDiagnostic(Diagnostic diagnostic) { DiagnosticCode diagnosticCode = diagnostic.diagnosticCode; switch (diagnosticCode) { case diag.constConstructorConstantFromDeferredLibrary: case diag.constConstructorWithFieldInitializedByNonConst: case diag.constEvalExtensionMethod: case diag.constEvalExtensionTypeMethod: case diag.constEvalMethodInvocation: case diag.constEvalPropertyAccess: case diag.constEvalTypeBool: case diag.constEvalTypeBoolInt: case diag.constEvalTypeBoolNumString: case diag.constEvalTypeInt: case diag.constEvalTypeNum: case diag.constEvalTypeNumString: case diag.constEvalTypeString: case diag.constEvalThrowsException: case diag.constEvalThrowsIdbze: case diag.constEvalForElement: case diag.constMapKeyNotPrimitiveEquality: case diag.constSetElementNotPrimitiveEquality: case diag.constTypeParameter: case diag.constWithNonConst: case diag.constWithNonConstantArgument: case diag.constWithTypeParameters: case diag.constWithTypeParametersConstructorTearoff: case diag.invalidConstant: case diag.missingConstInListLiteral: case diag.missingConstInMapLiteral: case diag.missingConstInSetLiteral: case diag.nonBoolCondition: case diag.nonConstantListElement: case diag.nonConstantMapElement: case diag.nonConstantMapKey: case diag.nonConstantMapValue: case diag.nonConstantRecordField: case diag.nonConstantSetElement: hasConstError = true; } } } extension AstNodeExtension on AstNode { /// Whether [ConstantVerifier] reports an error when computing the value of /// `this` as a constant. bool get hasConstantVerifierError { var unitNode = thisOrAncestorOfType(); var unitFragment = unitNode?.declaredFragment; if (unitFragment == null) return false; var libraryElement = unitFragment.element; var declaredVariables = libraryElement.session.declaredVariables; var dependenciesFinder = ConstantExpressionsDependenciesFinder(); accept(dependenciesFinder); computeConstants( declaredVariables: declaredVariables, constants: dependenciesFinder.dependencies.toList(), featureSet: libraryElement.featureSet, configuration: ConstantEvaluationConfiguration(), ); var listener = _ConstantDiagnosticListener(); var errorReporter = DiagnosticReporter(listener, unitFragment.source); accept(ConstantVerifier(errorReporter, libraryElement, declaredVariables)); return listener.hasConstError; } } extension ConstructorDeclarationExtension on ConstructorDeclaration { bool get canBeConst { var element = declaredFragment!.element; var classElement = element.enclosingElement; if (classElement is ClassElement && classElement.hasNonFinalField) { return false; } var oldKeyword = constKeyword; var self = this as ConstructorDeclarationImpl; try { temporaryConstConstructorElements[element] = true; self.constKeyword = KeywordToken(Keyword.CONST, offset); return !hasConstantVerifierError; } finally { temporaryConstConstructorElements[element] = null; self.constKeyword = oldKeyword; } } }