mirror of
https://github.com/dart-lang/sdk
synced 2024-09-16 04:37:12 +00:00
[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:
parent
7f1ae73fdb
commit
3f206da5e3
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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']);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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: [],
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in a new issue