[analyzer] Propagate nullability in InterfaceType.allSupertypes

Fixes https://github.com/Dart-Code/Dart-Code/issues/4185.

Change-Id: Ic9fcd60ef3eae24e3921c25162ec405a56c62c53
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/261840
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
Danny Tuppeny 2022-11-17 21:59:44 +00:00 committed by Commit Queue
parent 7f1ae73fdb
commit 3f206da5e3
5 changed files with 169 additions and 25 deletions

View file

@ -492,7 +492,8 @@ class InterfaceTypeImpl extends TypeImpl implements InterfaceType {
List<InterfaceType> get allSupertypes {
var substitution = Substitution.fromInterfaceType(this);
return element.allSupertypes
.map((t) => substitution.substituteType(t) as InterfaceType)
.map((t) => (substitution.substituteType(t) as InterfaceTypeImpl)
.withNullability(nullabilitySuffix))
.toList();
}

View file

@ -734,6 +734,8 @@ class InterfaceTypeImplTest extends AbstractTypeSystemTest {
check(objectNone, []);
check(numNone, ['Comparable<num>', 'Object']);
check(intNone, ['Comparable<num>', 'Object', 'num']);
check(intQuestion, ['Comparable<num>?', 'Object?', 'num?']);
check(intStar, ['Comparable<num>*', 'Object*', 'num*']);
check(listNone(intQuestion), ['Iterable<int?>', 'Object']);
}

View file

@ -44,10 +44,17 @@ class DartEditBuilderImpl extends EditBuilderImpl implements DartEditBuilder {
/// If not `null`, [write] will copy everything into this buffer.
StringBuffer? _carbonCopyBuffer;
/// Whether the target file is non-null by default.
///
/// When `true`, question `?` suffixes will be included on nullable types.
final bool isNonNullableByDefault;
/// Initialize a newly created builder to build a source edit.
DartEditBuilderImpl(
DartFileEditBuilderImpl sourceFileEditBuilder, int offset, int length)
: super(sourceFileEditBuilder, offset, length);
: isNonNullableByDefault = sourceFileEditBuilder
.resolvedUnit.libraryElement.isNonNullableByDefault,
super(sourceFileEditBuilder, offset, length);
DartFileEditBuilderImpl get dartFileEditBuilder =>
fileEditBuilder as DartFileEditBuilderImpl;
@ -579,12 +586,11 @@ class DartEditBuilderImpl extends EditBuilderImpl implements DartEditBuilder {
Expression argument, int index, Set<String> usedNames) {
// append type name
var type = argument.staticType;
var library = dartFileEditBuilder.resolvedUnit.libraryElement;
if (type == null || type.isBottom || type.isDartCoreNull) {
type = DynamicTypeImpl.instance;
}
if (argument is NamedExpression &&
library.isNonNullableByDefault &&
isNonNullableByDefault &&
type.nullabilitySuffix == NullabilitySuffix.none) {
write('required ');
}
@ -874,8 +880,7 @@ class DartEditBuilderImpl extends EditBuilderImpl implements DartEditBuilder {
return false;
}
if (type.isBottom) {
var library = dartFileEditBuilder.resolvedUnit.libraryElement;
if (library.isNonNullableByDefault) {
if (isNonNullableByDefault) {
return true;
}
return false;
@ -1224,8 +1229,7 @@ class DartEditBuilderImpl extends EditBuilderImpl implements DartEditBuilder {
return false;
}
if (type.isBottom) {
var library = dartFileEditBuilder.resolvedUnit.libraryElement;
if (library.isNonNullableByDefault) {
if (isNonNullableByDefault) {
write('Never');
return true;
}
@ -1929,26 +1933,34 @@ class DartFileEditBuilderImpl extends FileEditBuilderImpl
class DartLinkedEditBuilderImpl extends LinkedEditBuilderImpl
implements DartLinkedEditBuilder {
/// Initialize a newly created linked edit builder.
DartLinkedEditBuilderImpl(EditBuilderImpl editBuilder) : super(editBuilder);
DartLinkedEditBuilderImpl(DartEditBuilderImpl editBuilder)
: super(editBuilder);
DartEditBuilderImpl get dartEditBuilder => editBuilder as DartEditBuilderImpl;
@override
void addSuperTypesAsSuggestions(DartType? type) {
_addSuperTypesAsSuggestions(type, <DartType>{});
if (type is InterfaceType) {
_addTypeAsSuggestions(type);
type.allSupertypes.forEach(_addTypeAsSuggestions);
}
}
/// Safely implement [addSuperTypesAsSuggestions] by using the set of
/// [alreadyAdded] types to prevent infinite loops.
void _addSuperTypesAsSuggestions(DartType? type, Set<DartType> alreadyAdded) {
if (type is InterfaceType && alreadyAdded.add(type)) {
addSuggestion(
LinkedEditSuggestionKind.TYPE,
type.getDisplayString(withNullability: false),
);
_addSuperTypesAsSuggestions(type.superclass, alreadyAdded);
for (var interfaceType in type.interfaces) {
_addSuperTypesAsSuggestions(interfaceType, alreadyAdded);
}
}
void _addTypeAsSuggestions(InterfaceType type) {
addSuggestion(
LinkedEditSuggestionKind.TYPE,
_getTypeSuggestionText(type),
);
}
String _getTypeSuggestionText(InterfaceType type) {
// Add the suffix manually, because it should only be included for '?' and
// not '*'.
var typeDisplay = type.getDisplayString(withNullability: false);
return dartEditBuilder.isNonNullableByDefault &&
type.nullabilitySuffix == NullabilitySuffix.question
? '$typeDisplay?'
: typeDisplay;
}
}

View file

@ -1990,7 +1990,8 @@ void functionAfter() {
@reflectiveTest
class DartLinkedEditBuilderImplTest extends AbstractContextTest {
Future<void> test_addSuperTypesAsSuggestions() async {
Future<void>
test_addSuperTypesAsSuggestions_legacyTargetFile_noneSuffix() async {
var path = convertPath('/home/test/lib/test.dart');
addSource(path, '''
class A {}
@ -1999,7 +2000,122 @@ class C extends B {}
''');
var unit = (await resolveFile(path)).unit;
var classC = unit.declarations[2] as ClassDeclaration;
var builder = DartLinkedEditBuilderImpl(MockEditBuilderImpl());
var builder = DartLinkedEditBuilderImpl(
MockDartEditBuilderImpl(isNonNullableByDefault: false));
builder.addSuperTypesAsSuggestions(
classC.declaredElement?.instantiate(
typeArguments: [],
nullabilitySuffix: NullabilitySuffix.none,
),
);
var suggestions = builder.suggestions;
expect(suggestions, hasLength(4));
expect(suggestions.map((s) => s.value),
unorderedEquals(['Object', 'A', 'B', 'C']));
}
Future<void>
test_addSuperTypesAsSuggestions_legacyTargetFile_questionSuffix() async {
var path = convertPath('/home/test/lib/test.dart');
addSource(path, '''
class A {}
class B extends A {}
class C extends B {}
''');
var unit = (await resolveFile(path)).unit;
var classC = unit.declarations[2] as ClassDeclaration;
var builder = DartLinkedEditBuilderImpl(
MockDartEditBuilderImpl(isNonNullableByDefault: false));
builder.addSuperTypesAsSuggestions(
classC.declaredElement?.instantiate(
typeArguments: [],
nullabilitySuffix: NullabilitySuffix.question,
),
);
var suggestions = builder.suggestions;
expect(suggestions, hasLength(4));
expect(suggestions.map((s) => s.value),
unorderedEquals(['Object', 'A', 'B', 'C']));
}
Future<void>
test_addSuperTypesAsSuggestions_legacyTargetFile_starSuffix() async {
var path = convertPath('/home/test/lib/test.dart');
addSource(path, '''
class A {}
class B extends A {}
class C extends B {}
''');
var unit = (await resolveFile(path)).unit;
var classC = unit.declarations[2] as ClassDeclaration;
var builder = DartLinkedEditBuilderImpl(
MockDartEditBuilderImpl(isNonNullableByDefault: false));
builder.addSuperTypesAsSuggestions(
classC.declaredElement?.instantiate(
typeArguments: [],
nullabilitySuffix: NullabilitySuffix.star,
),
);
var suggestions = builder.suggestions;
expect(suggestions, hasLength(4));
expect(suggestions.map((s) => s.value),
unorderedEquals(['Object', 'A', 'B', 'C']));
}
Future<void> test_addSuperTypesAsSuggestions_noneSuffix() async {
var path = convertPath('/home/test/lib/test.dart');
addSource(path, '''
class A {}
class B extends A {}
class C extends B {}
''');
var unit = (await resolveFile(path)).unit;
var classC = unit.declarations[2] as ClassDeclaration;
var builder = DartLinkedEditBuilderImpl(MockDartEditBuilderImpl());
builder.addSuperTypesAsSuggestions(
classC.declaredElement?.instantiate(
typeArguments: [],
nullabilitySuffix: NullabilitySuffix.none,
),
);
var suggestions = builder.suggestions;
expect(suggestions, hasLength(4));
expect(suggestions.map((s) => s.value),
unorderedEquals(['Object', 'A', 'B', 'C']));
}
Future<void> test_addSuperTypesAsSuggestions_questionSuffix() async {
var path = convertPath('/home/test/lib/test.dart');
addSource(path, '''
class A {}
class B extends A {}
class C extends B {}
''');
var unit = (await resolveFile(path)).unit;
var classC = unit.declarations[2] as ClassDeclaration;
var builder = DartLinkedEditBuilderImpl(MockDartEditBuilderImpl());
builder.addSuperTypesAsSuggestions(
classC.declaredElement?.instantiate(
typeArguments: [],
nullabilitySuffix: NullabilitySuffix.question,
),
);
var suggestions = builder.suggestions;
expect(suggestions, hasLength(4));
expect(suggestions.map((s) => s.value),
unorderedEquals(['Object?', 'A?', 'B?', 'C?']));
}
Future<void> test_addSuperTypesAsSuggestions_starSuffix() async {
var path = convertPath('/home/test/lib/test.dart');
addSource(path, '''
class A {}
class B extends A {}
class C extends B {}
''');
var unit = (await resolveFile(path)).unit;
var classC = unit.declarations[2] as ClassDeclaration;
var builder = DartLinkedEditBuilderImpl(MockDartEditBuilderImpl());
builder.addSuperTypesAsSuggestions(
classC.declaredElement?.instantiate(
typeArguments: [],

View file

@ -7,6 +7,7 @@ import 'package:analyzer/dart/analysis/context_root.dart';
import 'package:analyzer/dart/analysis/session.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer_plugin/src/utilities/change_builder/change_builder_core.dart';
import 'package:analyzer_plugin/src/utilities/change_builder/change_builder_dart.dart';
class MockAnalysisContext implements AnalysisContext {
@override
@ -48,6 +49,18 @@ class MockContextRoot implements ContextRoot {
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}
class MockDartEditBuilderImpl implements DartEditBuilderImpl {
@override
final bool isNonNullableByDefault;
MockDartEditBuilderImpl({this.isNonNullableByDefault = true});
@override
dynamic noSuchMethod(Invocation invocation) {
return super.noSuchMethod(invocation);
}
}
class MockEditBuilderImpl implements EditBuilderImpl {
@override
dynamic noSuchMethod(Invocation invocation) {