Skip to content

Commit 36cef0f

Browse files
committed
Avoid name conflicts with dart:core
The primary mechanism here is a custom code_builder Allocator. Prefixing _all_ 'dart:core' elements would make the generated code quite unreadable. Code would look like: ```dart @_i3.override _i3.Future<_i3.bool> List(_i3.Map<_i3.String, _i3.int> p) { ... } ``` This PR instead gathers all members which would conflict with an element exported from 'dart:core', and only prefixes those elements. In this way, most code is unchanged. The only, very rare, cases of prefixing 'dart:core', are for members named things like 'List'. PiperOrigin-RevId: 459549619
1 parent 24f62d6 commit 36cef0f

File tree

2 files changed

+132
-20
lines changed

2 files changed

+132
-20
lines changed

pkgs/mockito/lib/src/builder.dart

Lines changed: 86 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,11 @@ class MockBuilder implements Builder {
108108
b.body.addAll(mockLibraryInfo.mockClasses);
109109
});
110110

111-
final emitter = DartEmitter.scoped(
112-
orderDirectives: true, useNullSafetySyntax: sourceLibIsNonNullable);
111+
final emitter = DartEmitter(
112+
allocator: _AvoidConflictsAllocator(
113+
coreConflicts: mockLibraryInfo.coreConflicts),
114+
orderDirectives: true,
115+
useNullSafetySyntax: sourceLibIsNonNullable);
113116
final rawOutput = mockLibrary.accept(emitter).toString();
114117
// The source lib may be pre-null-safety because of an explicit opt-out
115118
// (`// @dart=2.9`), as opposed to living in a pre-null-safety package. To
@@ -816,13 +819,22 @@ class _MockLibraryInfo {
816819
/// Asset-resolving while building the mock library.
817820
final Map<Element, String> assetUris;
818821

822+
final LibraryElement? dartCoreLibrary;
823+
824+
/// Names of overridden members which conflict with elements exported from
825+
/// 'dart:core'.
826+
///
827+
/// Each of these must be imported with a prefix to avoid the conflict.
828+
final coreConflicts = <String>{};
829+
819830
/// Build mock classes for [mockTargets].
820831
_MockLibraryInfo(
821832
Iterable<_MockTarget> mockTargets, {
822833
required this.assetUris,
823834
required LibraryElement entryLib,
824835
required InheritanceManager3 inheritanceManager,
825-
}) {
836+
}) : dartCoreLibrary = entryLib.importedLibraries
837+
.firstWhereOrNull((library) => library.isDartCore) {
826838
for (final mockTarget in mockTargets) {
827839
final fallbackGenerators = mockTarget.fallbackGenerators;
828840
mockClasses.add(_MockClassInfo(
@@ -1013,11 +1025,18 @@ class _MockClassInfo {
10131025
if (typeSystem._returnTypeIsNonNullable(method) ||
10141026
typeSystem._hasNonNullableParameter(method) ||
10151027
_needsOverrideForVoidStub(method)) {
1028+
_checkForConflictWithCore(method.name);
10161029
yield Method((mBuilder) => _buildOverridingMethod(mBuilder, method));
10171030
}
10181031
}
10191032
}
10201033

1034+
void _checkForConflictWithCore(String name) {
1035+
if (mockLibraryInfo.dartCoreLibrary?.exportNamespace.get(name) != null) {
1036+
mockLibraryInfo.coreConflicts.add(name);
1037+
}
1038+
}
1039+
10211040
/// The default behavior of mocks is to return null for unstubbed methods. To
10221041
/// use the new behavior of throwing an error, we must explicitly call
10231042
/// `throwOnMissingStub`.
@@ -1036,7 +1055,7 @@ class _MockClassInfo {
10361055
if (method.isOperator) name = 'operator$name';
10371056
builder
10381057
..name = name
1039-
..annotations.addAll([refer('override')])
1058+
..annotations.add(referImported('override', 'dart:core'))
10401059
..returns = _typeReference(method.returnType)
10411060
..types.addAll(method.typeParameters.map(_typeParameterReference));
10421061

@@ -1105,7 +1124,8 @@ class _MockClassInfo {
11051124
return;
11061125
}
11071126

1108-
final invocation = refer('Invocation').property('method').call([
1127+
final invocation =
1128+
referImported('Invocation', 'dart:core').property('method').call([
11091129
refer('#${method.displayName}'),
11101130
literalList(invocationPositionalArgs),
11111131
if (invocationNamedArgs.isNotEmpty) literalMap(invocationNamedArgs),
@@ -1202,6 +1222,7 @@ class _MockClassInfo {
12021222
return TypeReference((b) {
12031223
b
12041224
..symbol = 'Stream'
1225+
..url = 'dart:async'
12051226
..types.add(elementType);
12061227
}).property('empty').call([]);
12071228
} else if (type.isDartCoreString) {
@@ -1224,7 +1245,9 @@ class _MockClassInfo {
12241245
/// Returns a reference to [Future], optionally with a type argument for the
12251246
/// value of the Future.
12261247
TypeReference _futureReference([Reference? valueType]) => TypeReference((b) {
1227-
b.symbol = 'Future';
1248+
b
1249+
..symbol = 'Future'
1250+
..url = 'dart:async';
12281251
if (valueType != null) {
12291252
b.types.add(valueType);
12301253
}
@@ -1513,7 +1536,7 @@ class _MockClassInfo {
15131536
MethodBuilder builder, PropertyAccessorElement getter) {
15141537
builder
15151538
..name = getter.displayName
1516-
..annotations.addAll([refer('override')])
1539+
..annotations.add(referImported('override', 'dart:core'))
15171540
..type = MethodType.getter
15181541
..returns = _typeReference(getter.returnType);
15191542

@@ -1540,7 +1563,8 @@ class _MockClassInfo {
15401563
return;
15411564
}
15421565

1543-
final invocation = refer('Invocation').property('getter').call([
1566+
final invocation =
1567+
referImported('Invocation', 'dart:core').property('getter').call([
15441568
refer('#${getter.displayName}'),
15451569
]);
15461570
final namedArgs = {
@@ -1566,7 +1590,7 @@ class _MockClassInfo {
15661590
MethodBuilder builder, PropertyAccessorElement setter) {
15671591
builder
15681592
..name = setter.displayName
1569-
..annotations.addAll([refer('override')])
1593+
..annotations.add(referImported('override', 'dart:core'))
15701594
..type = MethodType.setter;
15711595

15721596
assert(setter.parameters.length == 1);
@@ -1576,7 +1600,8 @@ class _MockClassInfo {
15761600
..type = _typeReference(parameter.type, forceNullable: true)));
15771601
final invocationPositionalArg = refer(parameter.displayName);
15781602

1579-
final invocation = refer('Invocation').property('setter').call([
1603+
final invocation =
1604+
referImported('Invocation', 'dart:core').property('setter').call([
15801605
refer('#${setter.displayName}'),
15811606
invocationPositionalArg,
15821607
]);
@@ -1714,6 +1739,57 @@ class InvalidMockitoAnnotationException implements Exception {
17141739
String toString() => 'Invalid @GenerateMocks annotation: $message';
17151740
}
17161741

1742+
/// An [Allocator] that avoids conflicts with elements exported from
1743+
/// 'dart:core'.
1744+
///
1745+
/// This does not prefix _all_ 'dart:core' elements; instead it takes a set of
1746+
/// names which conflict, and if that is non-empty, generates two import
1747+
/// directives for 'dart:core':
1748+
///
1749+
/// * an unprefixed import with conflicting names enumerated in the 'hide'
1750+
/// combinator,
1751+
/// * a prefixed import which will be the way to reference the conflicting
1752+
/// names.
1753+
class _AvoidConflictsAllocator implements Allocator {
1754+
final _imports = <String, int>{};
1755+
var _keys = 1;
1756+
1757+
/// The collection of names of elements which conflict with elements exported
1758+
/// from 'dart:core'.
1759+
final Set<String> _coreConflicts;
1760+
1761+
_AvoidConflictsAllocator({required Set<String> coreConflicts})
1762+
: _coreConflicts = coreConflicts;
1763+
1764+
@override
1765+
String allocate(Reference reference) {
1766+
final symbol = reference.symbol!;
1767+
final url = reference.url;
1768+
if (url == null) {
1769+
return symbol;
1770+
}
1771+
if (url == 'dart:core' && !_coreConflicts.contains(symbol)) {
1772+
return symbol;
1773+
}
1774+
return '_i${_imports.putIfAbsent(url, _nextKey)}.$symbol';
1775+
}
1776+
1777+
int _nextKey() => _keys++;
1778+
1779+
@override
1780+
Iterable<Directive> get imports => [
1781+
if (_imports.containsKey('dart:core'))
1782+
// 'dart:core' is explicitly imported to avoid a conflict between an
1783+
// overriding member and an element exported by 'dart:core'. We must
1784+
// add another, unprefixed, import for 'dart:core' which hides the
1785+
// conflicting names.
1786+
Directive.import('dart:core', hide: _coreConflicts.toList()),
1787+
..._imports.keys.map(
1788+
(u) => Directive.import(u, as: '_i${_imports[u]}'),
1789+
),
1790+
];
1791+
}
1792+
17171793
/// A [MockBuilder] instance for use by `build.yaml`.
17181794
Builder buildMocks(BuilderOptions options) => MockBuilder();
17191795

pkgs/mockito/test/builder/auto_mocks_test.dart

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -534,8 +534,8 @@ void main() {
534534
'''),
535535
_containsAllOf(dedent2('''
536536
_i3.Future<void> m() => (super.noSuchMethod(Invocation.method(#m, []),
537-
returnValue: Future<void>.value(),
538-
returnValueForMissingStub: Future<void>.value()) as _i3.Future<void>);
537+
returnValue: _i3.Future<void>.value(),
538+
returnValueForMissingStub: _i3.Future<void>.value()) as _i3.Future<void>);
539539
''')),
540540
);
541541
});
@@ -549,7 +549,7 @@ void main() {
549549
'''),
550550
_containsAllOf(dedent2('''
551551
_i3.Stream<int> m() => (super.noSuchMethod(Invocation.method(#m, []),
552-
returnValue: Stream<int>.empty()) as _i3.Stream<int>);
552+
returnValue: _i3.Stream<int>.empty()) as _i3.Stream<int>);
553553
''')),
554554
);
555555
});
@@ -715,6 +715,25 @@ void main() {
715715
);
716716
});
717717

718+
test('overrides methods, adjusting imports for names that conflict with core',
719+
() async {
720+
await expectSingleNonNullableOutput(
721+
dedent('''
722+
import 'dart:core' as core;
723+
class Foo {
724+
void List(core.int a) {}
725+
core.List<core.String> m() => [];
726+
}
727+
'''),
728+
_containsAllOf(
729+
' void List(int? a) => super.noSuchMethod(Invocation.method(#List, [a]),\n',
730+
dedent2('''
731+
_i3.List<String> m() =>
732+
(super.noSuchMethod(Invocation.method(#m, []), returnValue: <String>[])
733+
as _i3.List<String>);'''),
734+
));
735+
});
736+
718737
test(
719738
'overrides `toString` with a correct signature if the class overrides '
720739
'it', () async {
@@ -1257,6 +1276,23 @@ void main() {
12571276
expect(mocksContent, contains('m(_i3.Bar? a)'));
12581277
});
12591278

1279+
test('imports dart:core with a prefix when members conflict with dart:core',
1280+
() async {
1281+
await expectSingleNonNullableOutput(
1282+
dedent('''
1283+
import 'dart:core' as core;
1284+
class Foo {
1285+
void List(int a) {}
1286+
core.List<String> m() => [];
1287+
}
1288+
'''),
1289+
_containsAllOf(
1290+
"import 'dart:core' hide List;",
1291+
"import 'dart:core' as _i3;",
1292+
),
1293+
);
1294+
});
1295+
12601296
test('prefixes parameter type on generic function-typed parameter', () async {
12611297
await expectSingleNonNullableOutput(
12621298
dedent(r'''
@@ -1492,7 +1528,7 @@ void main() {
14921528
'''),
14931529
_containsAllOf(
14941530
'_i3.FutureOr<R> m<R>() => (super.noSuchMethod(Invocation.method(#m, []),',
1495-
' returnValue: Future<R>.value(null)) as _i3.FutureOr<R>);'),
1531+
' returnValue: _i3.Future<R>.value(null)) as _i3.FutureOr<R>);'),
14961532
);
14971533
});
14981534

@@ -2148,7 +2184,7 @@ void main() {
21482184
'''),
21492185
_containsAllOf(dedent2('''
21502186
_i3.Future<bool> m() => (super.noSuchMethod(Invocation.method(#m, []),
2151-
returnValue: Future<bool>.value(false)) as _i3.Future<bool>);
2187+
returnValue: _i3.Future<bool>.value(false)) as _i3.Future<bool>);
21522188
''')),
21532189
);
21542190
});
@@ -2164,7 +2200,7 @@ void main() {
21642200
'''),
21652201
_containsAllOf(dedent2('''
21662202
_i3.Future<Function> m() => (super.noSuchMethod(Invocation.method(#m, []),
2167-
returnValue: Future<Function>.value(() {})) as _i3.Future<Function>);
2203+
returnValue: _i3.Future<Function>.value(() {})) as _i3.Future<Function>);
21682204
''')),
21692205
);
21702206
});
@@ -2180,7 +2216,7 @@ void main() {
21802216
'''),
21812217
_containsAllOf(dedent2('''
21822218
_i3.Future<_i2.Bar?> m() => (super.noSuchMethod(Invocation.method(#m, []),
2183-
returnValue: Future<_i2.Bar?>.value()) as _i3.Future<_i2.Bar?>);
2219+
returnValue: _i3.Future<_i2.Bar?>.value()) as _i3.Future<_i2.Bar?>);
21842220
''')),
21852221
);
21862222
});
@@ -2198,7 +2234,7 @@ void main() {
21982234
_containsAllOf(dedent2('''
21992235
_i3.Future<_i4.Uint8List> m() =>
22002236
(super.noSuchMethod(Invocation.method(#m, []),
2201-
returnValue: Future<_i4.Uint8List>.value(_i4.Uint8List(0)))
2237+
returnValue: _i3.Future<_i4.Uint8List>.value(_i4.Uint8List(0)))
22022238
as _i3.Future<_i4.Uint8List>);
22032239
''')),
22042240
);
@@ -2216,7 +2252,7 @@ void main() {
22162252
_containsAllOf(dedent2('''
22172253
_i3.Future<Iterable<bool>> m() =>
22182254
(super.noSuchMethod(Invocation.method(#m, []),
2219-
returnValue: Future<Iterable<bool>>.value(<bool>[]))
2255+
returnValue: _i3.Future<Iterable<bool>>.value(<bool>[]))
22202256
as _i3.Future<Iterable<bool>>);
22212257
''')),
22222258
);
@@ -2231,7 +2267,7 @@ void main() {
22312267
'''),
22322268
_containsAllOf(dedent2('''
22332269
_i3.Stream<int> m() => (super.noSuchMethod(Invocation.method(#m, []),
2234-
returnValue: Stream<int>.empty()) as _i3.Stream<int>);
2270+
returnValue: _i3.Stream<int>.empty()) as _i3.Stream<int>);
22352271
''')),
22362272
);
22372273
});

0 commit comments

Comments
 (0)