// Copyright (c) 2020, 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:_fe_analyzer_shared/src/types/shared_type.dart'; import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/type.dart'; import 'package:analyzer/src/dart/ast/ast.dart'; import 'package:analyzer/src/dart/ast/extensions.dart'; import 'package:analyzer/src/dart/element/element.dart'; import 'package:analyzer/src/summary2/function_type_builder.dart'; import 'package:analyzer/src/summary2/link.dart'; import 'package:analyzer/src/summary2/named_type_builder.dart'; import 'package:analyzer/src/summary2/record_type_builder.dart'; class VarianceBuilder { final Linker _linker; final Set _pending = Set.identity(); final Set _visit = Set.identity(); VarianceBuilder(this._linker); void perform() { for (var builder in _linker.builders.values) { for (var linkingUnit in builder.units) { for (var node in linkingUnit.node.declarations) { if (node is FunctionTypeAliasImpl) { _pending.add(node); } else if (node is GenericTypeAliasImpl) { _pending.add(node); } } } } for (var builder in _linker.builders.values) { for (var linkingUnit in builder.units) { for (var node in linkingUnit.node.declarations) { if (node is ClassTypeAliasImpl) { _typeParameters(node.typeParameters); } else if (node is ClassDeclarationImpl) { _typeParameters(node.typeParameters); } else if (node is EnumDeclarationImpl) { _typeParameters(node.typeParameters); } else if (node is FunctionTypeAliasImpl) { _functionTypeAlias(node); } else if (node is GenericTypeAliasImpl) { _genericTypeAlias(node); } else if (node is MixinDeclarationImpl) { _typeParameters(node.typeParameters); } } } } } Variance _compute(TypeParameterElement variable, DartType? type) { if (type is TypeParameterType) { if (type.element == variable) { return Variance.covariant; } else { return Variance.unrelated; } } else if (type is NamedTypeBuilder) { var element = type.element; var arguments = type.arguments; if (element is InterfaceElementImpl) { var result = Variance.unrelated; if (arguments.isNotEmpty) { var typeParameters = element.typeParameters; for ( var i = 0; i < arguments.length && i < typeParameters.length; i++ ) { var typeParameter = typeParameters[i]; result = result.meet( typeParameter.variance.combine(_compute(variable, arguments[i])), ); } } return result; } else if (element is TypeAliasElementImpl) { _typeAliasElement(element); var result = Variance.unrelated; if (arguments.isNotEmpty) { var typeParameters = element.typeParameters; for ( var i = 0; i < arguments.length && i < typeParameters.length; i++ ) { var typeParameter = typeParameters[i]; var typeParameterVariance = typeParameter.variance; result = result.meet( typeParameterVariance.combine(_compute(variable, arguments[i])), ); } } return result; } } else if (type is FunctionTypeBuilder) { return _computeFunctionType( variable, returnType: type.returnType, typeParameters: type.typeParameters, formalParameters: type.formalParameters, ); } else if (type is RecordTypeBuilder) { var result = Variance.unrelated; for (var field in type.node.fields) { result = result.meet(_compute(variable, field.type.typeOrThrow)); } return result; } return Variance.unrelated; } Variance _computeFunctionType( TypeParameterElement variable, { required DartType? returnType, required List? typeParameters, required List formalParameters, }) { var result = Variance.unrelated; result = result.meet(_compute(variable, returnType)); // If [variable] is referenced in a bound at all, it makes the // variance of [variable] in the entire type invariant. if (typeParameters != null) { for (var typeParameter in typeParameters) { var bound = typeParameter.bound; if (bound != null && _compute(variable, bound) != Variance.unrelated) { result = Variance.invariant; } } } for (var formalParameter in formalParameters) { result = result.meet( Variance.contravariant.combine( _compute(variable, formalParameter.type), ), ); } return result; } void _functionTypeAlias(FunctionTypeAliasImpl node) { var parameterList = node.typeParameters; if (parameterList == null) { return; } // Recursion detected, recover. if (_visit.contains(node)) { for (var typeParameter in parameterList.typeParameters) { _setVariance(typeParameter, Variance.covariant); } return; } // Not being linked, or already linked. if (!_pending.remove(node)) { return; } _visit.add(node); try { for (var typeParameter in parameterList.typeParameters) { var variance = _computeFunctionType( typeParameter.declaredFragment!.element, returnType: node.returnType?.type, typeParameters: null, formalParameters: FunctionTypeBuilder.getParameters(node.parameters), ); _setVariance(typeParameter, variance); } } finally { _visit.remove(node); } } void _genericTypeAlias(GenericTypeAlias node) { var parameterList = node.typeParameters; if (parameterList == null) { return; } // Recursion detected, recover. if (_visit.contains(node)) { for (var typeParameter in parameterList.typeParameters) { _setVariance(typeParameter, Variance.covariant); } return; } // Not being linked, or already linked. if (!_pending.remove(node)) { return; } var type = node.type.type; // Not a function type, recover. if (type == null) { for (var typeParameter in parameterList.typeParameters) { _setVariance(typeParameter, Variance.covariant); } } _visit.add(node); try { for (var typeParameter in parameterList.typeParameters) { var variance = _compute(typeParameter.declaredFragment!.element, type); _setVariance(typeParameter, variance); } } finally { _visit.remove(node); } } void _typeAliasElement(TypeAliasElementImpl element) { if (!_linker.isLinkingElement(element)) { return; } var node = _linker.getLinkingNode2(element.firstFragment); if (node == null) { // Not linking. } else if (node is GenericTypeAliasImpl) { _genericTypeAlias(node); } else if (node is FunctionTypeAliasImpl) { _functionTypeAlias(node); } else { throw UnimplementedError('(${node.runtimeType}) $node'); } } void _typeParameters(TypeParameterListImpl? parameterList) { if (parameterList == null) { return; } for (var typeParameter in parameterList.typeParameters) { var varianceKeyword = typeParameter.varianceKeyword; if (varianceKeyword != null) { var variance = Variance.fromKeywordString(varianceKeyword.lexeme); _setVariance(typeParameter, variance); } } } static void _setVariance(TypeParameter node, Variance variance) { var element = node.declaredFragment!.element as TypeParameterElementImpl; element.variance = variance; } }