| // Copyright (c) 2019, 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/ast.dart'; |
| import 'package:analyzer/dart/ast/token.dart'; |
| import 'package:analyzer/dart/element/element.dart'; |
| import 'package:analyzer/dart/element/type.dart'; |
| import 'package:analyzer/src/dart/element/type.dart'; |
| |
| /// Some [ConstructorElement]s can be temporary marked as "const" to check |
| /// if doing this is valid. |
| final temporaryConstConstructorElements = Expando<bool>(); |
| |
| /// Check if the [node] and all its sub-nodes are potentially constant. |
| /// |
| /// Return the list of nodes that are not potentially constant. |
| List<AstNode> getNotPotentiallyConstants( |
| AstNode node, { |
| required bool isNonNullableByDefault, |
| }) { |
| var collector = _Collector( |
| isNonNullableByDefault: isNonNullableByDefault, |
| ); |
| collector.collect(node); |
| return collector.nodes; |
| } |
| |
| /// Return `true` if the [node] is a constant type expression. |
| bool isConstantTypeExpression(TypeAnnotation node) { |
| return _ConstantTypeChecker(potentially: false).check(node); |
| } |
| |
| /// Return `true` if the [node] is a potentially constant type expression. |
| bool isPotentiallyConstantTypeExpression(TypeAnnotation node) { |
| return _ConstantTypeChecker(potentially: true).check(node); |
| } |
| |
| bool _isConstantTypeName(Identifier name) { |
| var element = name.staticElement; |
| if (element is ClassElement || element is TypeAliasElement) { |
| if (name is PrefixedIdentifier) { |
| if (name.isDeferred) { |
| return false; |
| } |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| class _Collector { |
| final bool isNonNullableByDefault; |
| final List<AstNode> nodes = []; |
| |
| _Collector({required this.isNonNullableByDefault}); |
| |
| void collect(AstNode node) { |
| if (node is BooleanLiteral || |
| node is DoubleLiteral || |
| node is IntegerLiteral || |
| node is NullLiteral || |
| node is SimpleStringLiteral || |
| node is SymbolLiteral) { |
| return; |
| } |
| |
| if (node is AdjacentStrings) { |
| for (var string in node.strings) { |
| collect(string); |
| } |
| return; |
| } |
| |
| if (node is StringInterpolation) { |
| for (var component in node.elements) { |
| if (component is InterpolationExpression) { |
| collect(component.expression); |
| } |
| } |
| return; |
| } |
| |
| if (node is Identifier) { |
| return _identifier(node); |
| } |
| |
| if (node is InstanceCreationExpression) { |
| if (!node.isConst) { |
| nodes.add(node); |
| } |
| return; |
| } |
| |
| if (node is TypedLiteral) { |
| return _typeLiteral(node); |
| } |
| |
| if (node is ParenthesizedExpression) { |
| collect(node.expression); |
| return; |
| } |
| |
| if (node is MethodInvocation) { |
| return _methodInvocation(node); |
| } |
| |
| if (node is NamedExpression) { |
| return collect(node.expression); |
| } |
| |
| if (node is BinaryExpression) { |
| collect(node.leftOperand); |
| collect(node.rightOperand); |
| return; |
| } |
| |
| if (node is PrefixExpression) { |
| var operator = node.operator.type; |
| if (operator == TokenType.BANG || |
| operator == TokenType.MINUS || |
| operator == TokenType.TILDE) { |
| collect(node.operand); |
| return; |
| } |
| nodes.add(node); |
| return; |
| } |
| |
| if (node is ConditionalExpression) { |
| collect(node.condition); |
| collect(node.thenExpression); |
| collect(node.elseExpression); |
| return; |
| } |
| |
| if (node is PropertyAccess) { |
| return _propertyAccess(node); |
| } |
| |
| if (node is AsExpression) { |
| if (isNonNullableByDefault) { |
| if (!isPotentiallyConstantTypeExpression(node.type)) { |
| nodes.add(node.type); |
| } |
| } else { |
| if (!isConstantTypeExpression(node.type)) { |
| nodes.add(node.type); |
| } |
| } |
| collect(node.expression); |
| return; |
| } |
| |
| if (node is IsExpression) { |
| if (isNonNullableByDefault) { |
| if (!isPotentiallyConstantTypeExpression(node.type)) { |
| nodes.add(node.type); |
| } |
| } else { |
| if (!isConstantTypeExpression(node.type)) { |
| nodes.add(node.type); |
| } |
| } |
| collect(node.expression); |
| return; |
| } |
| |
| if (node is MapLiteralEntry) { |
| collect(node.key); |
| collect(node.value); |
| return; |
| } |
| |
| if (node is SpreadElement) { |
| collect(node.expression); |
| return; |
| } |
| |
| if (node is IfElement) { |
| collect(node.condition); |
| collect(node.thenElement); |
| if (node.elseElement != null) { |
| collect(node.elseElement!); |
| } |
| return; |
| } |
| |
| nodes.add(node); |
| } |
| |
| void _identifier(Identifier node) { |
| var element = node.staticElement; |
| |
| if (node is PrefixedIdentifier) { |
| if (node.isDeferred) { |
| nodes.add(node); |
| return; |
| } |
| if (node.identifier.name == 'length') { |
| collect(node.prefix); |
| return; |
| } |
| if (element is MethodElement && element.isStatic) { |
| if (!_isConstantTypeName(node.prefix)) { |
| nodes.add(node); |
| } |
| return; |
| } |
| } |
| |
| if (element is ParameterElement) { |
| var enclosing = element.enclosingElement; |
| if (enclosing is ConstructorElement && |
| isConstConstructorElement(enclosing)) { |
| if (node.thisOrAncestorOfType<ConstructorInitializer>() != null) { |
| return; |
| } |
| } |
| nodes.add(node); |
| return; |
| } |
| |
| if (element is VariableElement) { |
| if (!element.isConst) { |
| nodes.add(node); |
| } |
| return; |
| } |
| if (element is PropertyAccessorElement && element.isGetter) { |
| var variable = element.variable; |
| if (!variable.isConst) { |
| nodes.add(node); |
| } |
| return; |
| } |
| if (_isConstantTypeName(node)) { |
| return; |
| } |
| if (element is FunctionElement) { |
| return; |
| } |
| if (element is MethodElement && element.isStatic) { |
| return; |
| } |
| nodes.add(node); |
| } |
| |
| void _methodInvocation(MethodInvocation node) { |
| var arguments = node.argumentList.arguments; |
| if (arguments.length == 2 && node.methodName.name == 'identical') { |
| var library = node.methodName.staticElement?.library; |
| if (library?.isDartCore == true) { |
| collect(arguments[0]); |
| collect(arguments[1]); |
| return; |
| } |
| } |
| nodes.add(node); |
| } |
| |
| void _propertyAccess(PropertyAccess node) { |
| // CascadeExpression is not a constant, so the target is never null. |
| var target = node.target!; |
| |
| if (node.propertyName.name == 'length') { |
| collect(target); |
| return; |
| } |
| |
| if (target is PrefixedIdentifier) { |
| if (target.isDeferred) { |
| nodes.add(node); |
| return; |
| } |
| |
| var element = node.propertyName.staticElement; |
| if (element is PropertyAccessorElement && element.isGetter) { |
| var variable = element.variable; |
| if (!variable.isConst) { |
| nodes.add(node.propertyName); |
| } |
| return; |
| } |
| } |
| |
| nodes.add(node); |
| } |
| |
| void _typeLiteral(TypedLiteral node) { |
| if (!node.isConst) { |
| nodes.add(node); |
| return; |
| } |
| |
| if (node is ListLiteral) { |
| var typeArguments = node.typeArguments?.arguments; |
| if (typeArguments != null && typeArguments.length == 1) { |
| var elementType = typeArguments[0]; |
| if (!isConstantTypeExpression(elementType)) { |
| nodes.add(elementType); |
| } |
| } |
| |
| for (var element in node.elements) { |
| collect(element); |
| } |
| return; |
| } |
| |
| if (node is SetOrMapLiteral) { |
| var typeArguments = node.typeArguments?.arguments; |
| if (typeArguments != null && typeArguments.length == 1) { |
| var elementType = typeArguments[0]; |
| if (!isConstantTypeExpression(elementType)) { |
| nodes.add(elementType); |
| } |
| } |
| |
| if (typeArguments != null && typeArguments.length == 2) { |
| var keyType = typeArguments[0]; |
| var valueType = typeArguments[1]; |
| if (!isConstantTypeExpression(keyType)) { |
| nodes.add(keyType); |
| } |
| if (!isConstantTypeExpression(valueType)) { |
| nodes.add(valueType); |
| } |
| } |
| |
| for (var element in node.elements) { |
| collect(element); |
| } |
| } |
| } |
| |
| static bool isConstConstructorElement(ConstructorElement element) { |
| if (element.isConst) return true; |
| return temporaryConstConstructorElements[element] ?? false; |
| } |
| } |
| |
| class _ConstantTypeChecker { |
| final bool potentially; |
| |
| _ConstantTypeChecker({required this.potentially}); |
| |
| /// Return `true` if the [node] is a constant type expression. |
| bool check(TypeAnnotation? node) { |
| if (potentially) { |
| if (node is TypeName) { |
| var element = node.name.staticElement; |
| if (element is TypeParameterElement) { |
| return true; |
| } |
| } |
| } |
| |
| if (node is TypeName) { |
| if (_isConstantTypeName(node.name)) { |
| var arguments = node.typeArguments?.arguments; |
| if (arguments != null) { |
| for (var argument in arguments) { |
| if (!check(argument)) { |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| if (node.type is DynamicTypeImpl) { |
| return true; |
| } |
| if (node.type is VoidType) { |
| return true; |
| } |
| return false; |
| } |
| |
| if (node is GenericFunctionType) { |
| var returnType = node.returnType; |
| if (returnType != null) { |
| if (!check(returnType)) { |
| return false; |
| } |
| } |
| |
| var typeParameters = node.typeParameters?.typeParameters; |
| if (typeParameters != null) { |
| for (var parameter in typeParameters) { |
| var bound = parameter.bound; |
| if (bound != null && !check(bound)) { |
| return false; |
| } |
| } |
| } |
| |
| var formalParameters = node.parameters.parameters; |
| for (var parameter in formalParameters) { |
| if (parameter is SimpleFormalParameter) { |
| if (!check(parameter.type)) { |
| return false; |
| } |
| } |
| } |
| |
| return true; |
| } |
| |
| return false; |
| } |
| } |