blob: de5923e9ab166d1b3d9a24dfda694cf6b5e8e661 [file] [log] [blame]
// Copyright (c) 2021, 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:scrape/scrape.dart';
enum ArgumentMatch {
noArguments,
none,
all,
some,
prefix,
suffix,
middle,
noncontiguous
}
extension on ArgumentMatch {
String get description {
switch (this) {
case ArgumentMatch.noArguments:
return 'No arguments to match';
case ArgumentMatch.none:
return 'Matched none';
case ArgumentMatch.all:
return 'Matched all';
case ArgumentMatch.some:
return 'Matched some';
case ArgumentMatch.prefix:
return 'Matched prefix';
case ArgumentMatch.suffix:
return 'Matched suffix';
case ArgumentMatch.middle:
return 'Matched middle';
case ArgumentMatch.noncontiguous:
return 'Matched noncontiguous';
}
}
}
void main(List<String> arguments) {
Scrape()
..addHistogram('Potential use')
..addHistogram('Individual arguments')
..addHistogram('Named arguments')
..addHistogram('Positional arguments')
..addHistogram('Argument pattern')
..addHistogram('Append super args')
..addHistogram('Prepend super args')
..addHistogram('Insert super args')
..addHistogram('Do not merge super args')
..addHistogram('No explicit super(), call unnamed')
..addHistogram('No explicit super(), call same name')
..addVisitor(() => SuperclassParameterVisitor())
..runCommandLine(arguments);
}
class SuperclassParameterVisitor extends ScrapeVisitor {
@override
void visitConstructorDeclaration(ConstructorDeclaration node) {
// Whether the constructor might benefit from the feature at all.
var initializer = _findSuper(node);
if (initializer == null) {
record('Potential use', 'No: No initializer');
return;
}
if (initializer.argumentList.arguments.isEmpty) {
record('Potential use', 'No: Empty super() argument list');
return;
}
record('Potential use', 'Yes');
// If we get here, we have a superclass constructor call with arguments.
// See if any of them could use the feature.
var positionalParamNames = node.parameters.parameters
.where((param) => param.isPositional)
.map((param) => param.identifier!.name)
.toList();
var namedParamNames = node.parameters.parameters
.where((param) => param.isNamed)
.map((param) => param.identifier!.name)
.toSet();
var matchedNamedArguments = 0;
var unmatchedNamedArguments = 0;
var lastPositionalParam = -1;
var matchedIndexes = <int>[];
var unmatchedPositionalArguments = 0;
var positionalArgCount = 0;
for (var i = 0; i < initializer.argumentList.arguments.length; i++) {
var argument = initializer.argumentList.arguments[i];
if (argument is NamedExpression) {
var expression = argument.expression;
if (expression is! SimpleIdentifier) {
record('Individual arguments',
'Named argument expression is not identifier');
unmatchedNamedArguments++;
} else if (argument.name.label.name != expression.name) {
record('Individual arguments',
'Named argument name does not match expression name');
unmatchedNamedArguments++;
} else if (!namedParamNames.contains(expression.name)) {
record('Individual arguments',
'Named argument does not match a parameter');
unmatchedNamedArguments++;
} else {
record('Individual arguments', 'Argument matches a parameter');
matchedNamedArguments++;
}
} else {
positionalArgCount++;
if (argument is! SimpleIdentifier) {
record('Individual arguments',
'Positional argument expression is not identifier');
unmatchedPositionalArguments++;
} else {
// Start searching after the last matched positional parameter because
// we don't allow reordering them. If two arguments are out of order,
// that doesn't mean we can't use "super." at all, just that we can
// only use it for *one* of those arguments.
var index =
positionalParamNames.indexOf(argument.name, lastPositionalParam);
if (index == -1) {
record('Individual arguments',
'Positional argument does not match a parameter');
} else {
record('Individual arguments', 'Argument matches a parameter');
lastPositionalParam = index;
matchedIndexes.add(i);
}
}
}
}
// Characterize the positional argument list.
ArgumentMatch positionalMatch;
if (unmatchedPositionalArguments == 0) {
if (matchedIndexes.isEmpty) {
positionalMatch = ArgumentMatch.noArguments;
} else {
positionalMatch = ArgumentMatch.all;
}
} else if (matchedIndexes.isEmpty) {
positionalMatch = ArgumentMatch.none;
} else {
// If there is any unmatched argument before a matched one, then the
// matched arguments are not all at the beginning.
var matchedArePrefix = true;
for (var i = 1; i < positionalArgCount; i++) {
if (!matchedIndexes.contains(i - 1) && matchedIndexes.contains(i)) {
matchedArePrefix = false;
break;
}
}
// If there is any unmatched argument after a matched one, then the
// matched arguments are not all at the end.
var matchedAreSuffix = true;
for (var i = 0; i < positionalArgCount - 1; i++) {
if (!matchedIndexes.contains(i + 1) && matchedIndexes.contains(i)) {
matchedAreSuffix = false;
break;
}
}
// If any index between the first and last matched arg is not matched,
// then the arguments are not contiguous.
var matchedAreContiguous = true;
if (matchedIndexes.isNotEmpty) {
for (var i = matchedIndexes.first; i <= matchedIndexes.last; i++) {
if (!matchedIndexes.contains(i)) {
matchedAreContiguous = false;
break;
}
}
}
if (!matchedAreContiguous) {
positionalMatch = ArgumentMatch.noncontiguous;
} else if (matchedArePrefix) {
positionalMatch = ArgumentMatch.prefix;
} else if (matchedAreSuffix) {
positionalMatch = ArgumentMatch.suffix;
} else {
positionalMatch = ArgumentMatch.middle;
}
}
record('Positional arguments', positionalMatch.description);
// Characterize the named argument list.
ArgumentMatch namedMatch;
if (matchedNamedArguments == 0) {
if (unmatchedNamedArguments == 0) {
namedMatch = ArgumentMatch.noArguments;
} else {
namedMatch = ArgumentMatch.none;
}
} else {
if (unmatchedNamedArguments == 0) {
namedMatch = ArgumentMatch.all;
} else {
namedMatch = ArgumentMatch.some;
}
}
record('Named arguments', namedMatch.description);
var pattern = [
for (var i = 0; i < positionalArgCount; i++)
matchedIndexes.contains(i) ? 's' : '_',
for (var i = 0; i < matchedNamedArguments; i++) ':s',
for (var i = 0; i < unmatchedNamedArguments; i++) ':_',
].join(',');
record('Argument pattern', '($pattern)');
// If none of the arguments could be 'super.', then none of the proposals
// apply.
if (matchedIndexes.isEmpty && matchedNamedArguments == 0) return;
var append = true;
var prepend = true;
var insert = true;
var noMerge = true;
var allParams = true;
switch (positionalMatch) {
case ArgumentMatch.noArguments:
case ArgumentMatch.all:
// OK.
break;
case ArgumentMatch.none:
allParams = false;
break;
case ArgumentMatch.some:
throw Exception('Should not get some for positional args.');
case ArgumentMatch.prefix:
append = false;
noMerge = false;
allParams = false;
break;
case ArgumentMatch.suffix:
prepend = false;
noMerge = false;
allParams = false;
break;
case ArgumentMatch.middle:
append = false;
prepend = false;
noMerge = false;
allParams = false;
break;
case ArgumentMatch.noncontiguous:
append = false;
prepend = false;
insert = false;
noMerge = false;
allParams = false;
break;
}
switch (namedMatch) {
case ArgumentMatch.noArguments:
case ArgumentMatch.all:
// OK.
break;
case ArgumentMatch.none:
case ArgumentMatch.some:
allParams = false;
break;
default:
throw Exception('Unexpected match.');
}
record('Append super args', append ? 'Yes' : 'No');
record('Prepend super args', prepend ? 'Yes' : 'No');
record('Insert super args', insert ? 'Yes' : 'No');
record('Do not merge super args', noMerge ? 'Yes' : 'No');
var subName = _constructorName(node.name);
var superName = _constructorName(initializer.constructorName);
record('No explicit super(), call same name',
(allParams && superName == subName) ? 'Yes' : 'No');
record('No explicit super(), call unnamed',
(allParams && superName == '(unnamed)') ? 'Yes' : 'No');
}
String _constructorName(SimpleIdentifier? name) {
if (name == null) return '(unnamed)';
return name.name;
}
SuperConstructorInvocation? _findSuper(ConstructorDeclaration node) {
for (var initializer in node.initializers) {
if (initializer is SuperConstructorInvocation) {
return initializer;
}
}
return null;
}
}