blob: 7c05e9df0bb3cbb98b8df3fe5fcc210b968aa337 [file] [log] [blame]
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element2.dart';
import 'package:analyzer/dart/element/type.dart';
// ignore: implementation_imports
import 'package:analyzer/src/dart/element/element.dart';
import '../analyzer.dart';
const _desc = r"Avoid using 'FutureOr<void>' as the type of a result.";
const _in = Variance._in;
const _inout = Variance._inout;
const _out = Variance._out;
class AvoidFutureOrVoid extends LintRule {
AvoidFutureOrVoid()
: super(
name: LintNames.avoid_futureor_void,
description: _desc,
state: State.experimental());
@override
LintCode get lintCode => LinterLintCode.avoid_futureor_void;
@override
void registerNodeProcessors(
NodeLintRegistry registry, LinterContext context) {
var visitor = _Visitor(this, context);
registry.addAsExpression(this, visitor);
registry.addCastPattern(this, visitor);
registry.addExtendsClause(this, visitor);
registry.addExtensionOnClause(this, visitor);
registry.addFunctionDeclaration(this, visitor);
registry.addImplementsClause(this, visitor);
registry.addIsExpression(this, visitor);
registry.addMethodDeclaration(this, visitor);
registry.addMixinOnClause(this, visitor);
registry.addObjectPattern(this, visitor);
registry.addRepresentationDeclaration(this, visitor);
registry.addTypeParameter(this, visitor);
registry.addVariableDeclarationList(this, visitor);
registry.addWithClause(this, visitor);
}
}
/* 'type_analyzer_operations.dart' does support variance in some ways, but
* this may not be supported for use in a lint. So we roll our own
* minimal level of support for variance.
*/
enum Variance {
_out,
_in,
_inout;
Variance get inverse => switch (this) {
_out => _in,
_in => _out,
_inout => _inout,
};
}
class _Visitor extends SimpleAstVisitor<void> {
final LintRule rule;
final LinterContext context;
_Visitor(this.rule, this.context);
@override
void visitAsExpression(AsExpression node) => _checkOut(node.type);
@override
void visitCastPattern(CastPattern node) => _checkOut(node.type);
@override
void visitExtendsClause(ExtendsClause node) => _checkOut(node.superclass);
@override
void visitExtensionOnClause(ExtensionOnClause node) =>
_checkOut(node.extendedType);
@override
void visitFunctionDeclaration(FunctionDeclaration node) {
_checkOut(node.returnType);
var functionExpression = node.functionExpression;
functionExpression.typeParameters?.typeParameters.forEach(_checkBound);
functionExpression.parameters?.parameters.forEach(_checkFormalParameterIn);
}
@override
void visitImplementsClause(ImplementsClause node) =>
node.interfaces.forEach(_checkOut);
@override
void visitIsExpression(IsExpression node) => _checkOut(node.type);
@override
void visitMethodDeclaration(MethodDeclaration node) {
_checkOut(node.returnType);
node.typeParameters?.typeParameters.forEach(_checkBound);
node.parameters?.parameters.forEach(_checkFormalParameterIn);
}
@override
void visitMixinOnClause(MixinOnClause node) =>
node.superclassConstraints.forEach(_checkOut);
@override
void visitObjectPattern(ObjectPattern node) => _checkOut(node.type);
@override
void visitRepresentationDeclaration(RepresentationDeclaration node) =>
_checkOut(node.fieldType);
@override
void visitTypeParameter(TypeParameter node) => _checkInOut(node.bound);
@override
void visitVariableDeclarationList(VariableDeclarationList node) =>
_checkOut(node.type);
@override
void visitWithClause(WithClause node) => node.mixinTypes.forEach(_checkOut);
void _check(Variance variance, TypeAnnotation? typeAnnotation) {
if (typeAnnotation == null) return;
// Do not lint implicit program elements.
if (typeAnnotation.isSynthetic) return;
switch (typeAnnotation) {
case NamedType():
var arguments = typeAnnotation.typeArguments?.arguments;
if (arguments != null) {
var element = typeAnnotation.element2?.baseElement;
List<TypeParameterElement2>? typeParameterList;
if (element != null) {
switch (element) {
case ClassElement2(:var typeParameters2):
case MixinElement2(:var typeParameters2):
case EnumElement2(:var typeParameters2):
case TypeAliasElement2(:var typeParameters2):
typeParameterList = typeParameters2;
default:
typeParameterList = null;
}
} else {
typeParameterList = null;
}
if (typeParameterList == null ||
typeParameterList.length != arguments.length) {
// Fallback: Assume every type parameter is covariant.
for (var argument in arguments) {
_check(variance, argument);
}
} else {
var length = arguments.length;
for (var i = 0; i < length; ++i) {
var parameter = typeParameterList[i];
var argument = arguments[i];
Variance parameterVariance;
var parameterFragment =
parameter.firstFragment as TypeParameterElementImpl;
if (parameterFragment.isLegacyCovariant ||
parameterFragment.variance.isCovariant) {
parameterVariance = variance;
} else if (parameterFragment.variance.isContravariant) {
parameterVariance = variance.inverse;
} else {
parameterVariance = _inout;
}
_check(parameterVariance, argument);
}
}
}
var staticType = typeAnnotation.type;
if (staticType == null) return;
if (staticType is ParameterizedType) {
if (variance == _in) return;
if (!staticType.isDartAsyncFutureOr) return;
var typeArguments = staticType.typeArguments;
if (typeArguments.length != 1) return; // Just to be safe.
if (typeArguments.first is VoidType) {
rule.reportLint(typeAnnotation);
}
}
case GenericFunctionType():
_check(variance, typeAnnotation.returnType);
for (var parameter in typeAnnotation.parameters.parameters) {
_checkFormalParameter(variance.inverse, parameter);
}
case RecordTypeAnnotation():
var positionalFields = typeAnnotation.positionalFields;
for (var field in positionalFields) {
_check(variance, field.type);
}
var namedFields = typeAnnotation.namedFields?.fields;
if (namedFields != null) {
for (var field in namedFields) {
_check(variance, field.type);
}
}
}
}
void _checkBound(TypeParameter typeParameter) =>
_checkInOut(typeParameter.bound);
void _checkFormalParameter(
Variance variance, FormalParameter formalParameter) {
if (!formalParameter.isExplicitlyTyped) return;
switch (formalParameter) {
case SuperFormalParameter(:var type):
case FieldFormalParameter(:var type):
case SimpleFormalParameter(:var type):
_check(variance, type);
case FunctionTypedFormalParameter(
:var returnType,
:var parameters,
:var typeParameters
):
_check(variance, returnType);
typeParameters?.typeParameters.forEach(_checkBound);
for (var parameter in parameters.parameters) {
_checkFormalParameter(variance.inverse, parameter);
}
case DefaultFormalParameter():
_checkFormalParameter(variance, formalParameter.parameter);
}
}
void _checkFormalParameterIn(FormalParameter formalParameter) =>
_checkFormalParameter(_in, formalParameter);
void _checkInOut(TypeAnnotation? typeAnnotation) =>
_check(_inout, typeAnnotation);
void _checkOut(TypeAnnotation? typeAnnotation) =>
_check(_out, typeAnnotation);
}