[analysis_server] Swap from dynamic to Object? in LSP generated code

Change-Id: Ib8ce2c722806b53b727d256c73859becbc1dbd11
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/206120
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
Danny Tuppeny 2021-07-07 17:14:34 +00:00 committed by commit-bot@chromium.org
parent 77487355b7
commit c2f5625fa7
17 changed files with 6326 additions and 4843 deletions

File diff suppressed because it is too large Load diff

View file

@ -132,8 +132,9 @@ class LspClientCapabilities {
raw.workspace?.symbol?.symbolKind?.valueSet,
defaults: defaultSupportedSymbolKinds),
experimentalSnippetTextEdit =
raw.experimental is Map<String, dynamic> &&
raw.experimental['snippetTextEdit'] == true;
raw.experimental is Map<String, Object?> &&
(raw.experimental as Map<String, Object?>)['snippetTextEdit'] ==
true;
static Set<MarkupKind>? _completionDocumentationFormats(
ClientCapabilities raw) {

View file

@ -78,8 +78,8 @@ abstract class SimpleEditCommandHandler
// sent - and may have failed to apply - was related to this command
// execution).
// We need to fromJson to convert the JSON map to the real types.
final editResponseResult =
ApplyWorkspaceEditResponse.fromJson(editResponse.result);
final editResponseResult = ApplyWorkspaceEditResponse.fromJson(
editResponse.result as Map<String, Object?>);
if (editResponseResult.applied) {
return success(null);
} else {

View file

@ -51,9 +51,6 @@ class ExecuteCommandHandler
: server.clientCapabilities?.workDoneProgress ?? false
? ProgressReporter.serverCreated(server)
: ProgressReporter.noop;
// TODO(dantup): Remove this cast and update codegen to use `Object?` in
// place of the `dynamics`.
return handler.handle(
params.arguments?.cast<Object?>(), progress, cancellationToken);
return handler.handle(params.arguments, progress, cancellationToken);
}
}

View file

@ -614,7 +614,7 @@ class LspAnalysisServer extends AbstractAnalysisServer {
ShowMessageRequestParams(type: type, message: message, actions: actions),
);
return MessageActionItem.fromJson(response.result);
return MessageActionItem.fromJson(response.result as Map<String, Object?>);
}
@override

View file

@ -38,7 +38,7 @@ abstract class AbstractLspAnalysisServerIntegrationTest
? null as T
: throw 'Expected Null response but got ${resp.result}';
} else {
return fromJson(resp.result);
return fromJson(resp.result as R);
}
}

View file

@ -37,7 +37,8 @@ class ExtractMethodRefactorCodeActionsTest extends AbstractCodeActionsTest {
requestsFromServer
.where((r) => r.method == Method.window_workDoneProgress_create)
.listen((request) async {
final params = WorkDoneProgressCreateParams.fromJson(request.params);
final params = WorkDoneProgressCreateParams.fromJson(
request.params as Map<String, Object?>);
if (params.token != analyzingProgressToken) {
controller.add('CREATE');
}
@ -45,7 +46,8 @@ class ExtractMethodRefactorCodeActionsTest extends AbstractCodeActionsTest {
notificationsFromServer
.where((n) => n.method == Method.progress)
.listen((notification) {
final params = ProgressParams.fromJson(notification.params);
final params =
ProgressParams.fromJson(notification.params as Map<String, Object?>);
if (params.token != analyzingProgressToken) {
if (WorkDoneProgressBegin.canParse(params.value, nullLspJsonReporter)) {
controller.add('BEGIN');
@ -118,7 +120,8 @@ void newMethod() {
late WorkspaceEdit edit;
requestsFromServer.listen((request) async {
if (request.method == Method.workspace_applyEdit) {
final params = ApplyWorkspaceEditParams.fromJson(request.params);
final params = ApplyWorkspaceEditParams.fromJson(
request.params as Map<String, Object?>);
edit = params.edit;
respondTo(request, ApplyWorkspaceEditResponse(applied: true));
}

View file

@ -154,7 +154,8 @@ void f() {
// By default, there should be no commit characters.
var reg = registration(Method.textDocument_completion);
var options = CompletionRegistrationOptions.fromJson(reg.registerOptions);
var options = CompletionRegistrationOptions.fromJson(
reg.registerOptions as Map<String, Object?>);
expect(options.allCommitCharacters, isNull);
// When we change config, we should get a re-registration (unregister then
@ -162,7 +163,8 @@ void f() {
await monitorDynamicReregistration(
registrations, () => updateConfig({'previewCommitCharacters': true}));
reg = registration(Method.textDocument_completion);
options = CompletionRegistrationOptions.fromJson(reg.registerOptions);
options = CompletionRegistrationOptions.fromJson(
reg.registerOptions as Map<String, Object?>);
expect(options.allCommitCharacters, equals(dartCompletionCommitCharacters));
}

View file

@ -26,7 +26,8 @@ class InitializationTest extends AbstractLspAnalysisServerTest {
Method method,
) {
return TextDocumentRegistrationOptions.fromJson(
registrationFor(registrations, method)?.registerOptions);
registrationFor(registrations, method)?.registerOptions
as Map<String, Object?>);
}
Future<void> test_bazelWorkspace() async {
@ -64,14 +65,15 @@ class InitializationTest extends AbstractLspAnalysisServerTest {
),
);
final initResult = InitializeResult.fromJson(initResponse.result);
final initResult =
InitializeResult.fromJson(initResponse.result as Map<String, Object?>);
expect(initResult.capabilities, isNotNull);
// Check Dart-only registration.
final dartRegistration =
registrationForDart(registrations, Method.textDocument_completion);
final dartOptions = CompletionRegistrationOptions.fromJson(
dartRegistration.registerOptions);
dartRegistration.registerOptions as Map<String, Object?>);
expect(dartOptions.documentSelector, hasLength(1));
expect(dartOptions.documentSelector![0].language, dartLanguageId);
expect(dartOptions.triggerCharacters, isNotEmpty);
@ -81,7 +83,7 @@ class InitializationTest extends AbstractLspAnalysisServerTest {
r.method == Method.textDocument_completion.toJson() &&
r != dartRegistration);
final nonDartOptions = CompletionRegistrationOptions.fromJson(
nonDartRegistration.registerOptions);
nonDartRegistration.registerOptions as Map<String, Object?>);
final otherLanguages = nonDartOptions.documentSelector!
.map((selector) => selector.language)
.toList();
@ -109,7 +111,8 @@ class InitializationTest extends AbstractLspAnalysisServerTest {
// Because we support dynamic registration for synchronization, we won't send
// static registrations for them.
// https://github.com/dart-lang/sdk/issues/38490
final initResult = InitializeResult.fromJson(initResponse.result);
final initResult =
InitializeResult.fromJson(initResponse.result as Map<String, Object?>);
expect(initResult.serverInfo!.name, 'Dart SDK LSP Analysis Server');
expect(initResult.serverInfo!.version, isNotNull);
expect(initResult.capabilities, isNotNull);
@ -123,7 +126,7 @@ class InitializationTest extends AbstractLspAnalysisServerTest {
registrationOptionsFor(registrations, Method.textDocument_didChange);
final rename = FileOperationRegistrationOptions.fromJson(
registrationFor(registrations, Method.workspace_willRenameFiles)
?.registerOptions);
?.registerOptions as Map<String, Object?>);
expect(registrationOptionsFor(registrations, Method.textDocument_didOpen),
isNotNull);
expect(registrationOptionsFor(registrations, Method.textDocument_didClose),
@ -164,7 +167,8 @@ class InitializationTest extends AbstractLspAnalysisServerTest {
final initResponse = await initialize();
await pumpEventQueue();
final initResult = InitializeResult.fromJson(initResponse.result);
final initResult =
InitializeResult.fromJson(initResponse.result as Map<String, Object?>);
expect(initResult.capabilities, isNotNull);
// When dynamic registration is not supported, we will always statically
// request text document open/close and incremental updates.
@ -229,7 +233,8 @@ class InitializationTest extends AbstractLspAnalysisServerTest {
),
);
final initResult = InitializeResult.fromJson(initResponse.result);
final initResult =
InitializeResult.fromJson(initResponse.result as Map<String, Object?>);
expect(initResult.capabilities, isNotNull);
// Ensure no static registrations. This list should include all server equivilents
@ -289,8 +294,8 @@ class InitializationTest extends AbstractLspAnalysisServerTest {
..interestingFiles = ['*.foo'];
pluginManager.pluginsChangedController.add(null);
});
final unregistrations =
UnregistrationParams.fromJson(unregisterRequest.params)
final unregistrations = UnregistrationParams.fromJson(
unregisterRequest.params as Map<String, Object?>)
.unregisterations;
// folding method should have been unregistered as the server now supports
@ -321,7 +326,9 @@ class InitializationTest extends AbstractLspAnalysisServerTest {
.firstWhere((r) => r.method == Method.client_unregisterCapability)
.then((request) {
respondTo(request, null);
return UnregistrationParams.fromJson(request.params).unregisterations;
return UnregistrationParams.fromJson(
request.params as Map<String, Object?>)
.unregisterations;
});
final request = await expectRequest(Method.client_registerCapability, () {
@ -332,7 +339,8 @@ class InitializationTest extends AbstractLspAnalysisServerTest {
});
final registrations =
RegistrationParams.fromJson(request.params).registrations;
RegistrationParams.fromJson(request.params as Map<String, Object?>)
.registrations;
final documentFilterSql =
DocumentFilter(scheme: 'file', pattern: '**/*.sql');
@ -343,7 +351,8 @@ class InitializationTest extends AbstractLspAnalysisServerTest {
contains(isA<Registration>()
.having((r) => r.method, 'method', 'textDocument/foldingRange')
.having(
(r) => TextDocumentRegistrationOptions.fromJson(r.registerOptions)
(r) => TextDocumentRegistrationOptions.fromJson(
r.registerOptions as Map<String, Object?>)
.documentSelector,
'registerOptions.documentSelector',
containsAll([documentFilterSql, documentFilterDart]),
@ -483,7 +492,8 @@ class InitializationTest extends AbstractLspAnalysisServerTest {
expect(response.result, isNotNull);
expect(InitializeResult.canParse(response.result, nullLspJsonReporter),
isTrue);
final result = InitializeResult.fromJson(response.result);
final result =
InitializeResult.fromJson(response.result as Map<String, Object?>);
expect(result.capabilities, isNotNull);
// Check some basic capabilities that are unlikely to change.
expect(result.capabilities.textDocumentSync, isNotNull);

View file

@ -176,7 +176,8 @@ class RenameTest extends AbstractLspAnalysisServerTest {
throw error;
}
final result = WorkspaceEdit.fromJson(response.result);
final result =
WorkspaceEdit.fromJson(response.result as Map<String, Object?>);
// Ensure applying the changes will give us the expected content.
final contents = {
@ -205,7 +206,7 @@ class RenameTest extends AbstractLspAnalysisServerTest {
// Expect a successful empty response if cancelled.
expect(response.error, isNull);
expect(
WorkspaceEdit.fromJson(response.result),
WorkspaceEdit.fromJson(response.result as Map<String, Object?>),
equals(emptyWorkspaceEdit),
);
}

View file

@ -102,7 +102,7 @@ abstract class AbstractLspAnalysisServerTest
throw error;
} else {
// resp.result should only be null when error != null if T allows null.
return resp.result == null ? null as T : fromJson(resp.result);
return resp.result == null ? null as T : fromJson(resp.result as R);
}
}
@ -122,7 +122,8 @@ abstract class AbstractLspAnalysisServerTest
) {
return registrations.singleWhere((r) =>
r.method == method.toJson() &&
(TextDocumentRegistrationOptions.fromJson(r.registerOptions)
(TextDocumentRegistrationOptions.fromJson(
r.registerOptions as Map<String, Object?>)
.documentSelector
?.any((selector) => selector.language == dartLanguageId) ??
false));
@ -875,7 +876,8 @@ mixin LspAnalysisServerTestMixin implements ClientCapabilitiesHelperMixin {
final notificationFromServer = await firstError.timeout(timeout);
expect(notificationFromServer, isNotNull);
return ShowMessageParams.fromJson(notificationFromServer.params);
return ShowMessageParams.fromJson(
notificationFromServer.params as Map<String, Object?>);
}
Future<T> expectNotification<T>(
@ -1253,7 +1255,8 @@ mixin LspAnalysisServerTestMixin implements ClientCapabilitiesHelperMixin {
});
// Handle the request from the server and send the response back.
final clientsResponse = await handler(fromJson(incomingRequest.params));
final clientsResponse =
await handler(fromJson(incomingRequest.params as Map<String, Object?>));
respondTo(incomingRequest, clientsResponse);
// Return a future that completes when the response to the original request
@ -1649,7 +1652,8 @@ mixin LspAnalysisServerTestMixin implements ClientCapabilitiesHelperMixin {
'but client supports workDoneProgress');
}
final params = AnalyzerStatusParams.fromJson(message.params);
final params = AnalyzerStatusParams.fromJson(
message.params as Map<String, Object?>);
return params.isAnalyzing == analyzing;
} else if (message.method == Method.progress) {
if (_clientCapabilities!.window?.workDoneProgress != true) {
@ -1658,7 +1662,8 @@ mixin LspAnalysisServerTestMixin implements ClientCapabilitiesHelperMixin {
'but client supports workDoneProgress');
}
final params = ProgressParams.fromJson(message.params);
final params =
ProgressParams.fromJson(message.params as Map<String, Object?>);
// Skip unrelated progress notifications.
if (params.token != analyzingProgressToken) {
@ -1688,8 +1693,8 @@ mixin LspAnalysisServerTestMixin implements ClientCapabilitiesHelperMixin {
await serverToClient.firstWhere((message) {
if (message is NotificationMessage &&
message.method == CustomMethods.publishClosingLabels) {
closingLabelsParams =
PublishClosingLabelsParams.fromJson(message.params);
closingLabelsParams = PublishClosingLabelsParams.fromJson(
message.params as Map<String, Object?>);
return closingLabelsParams.uri == uri.toString();
}
@ -1704,7 +1709,8 @@ mixin LspAnalysisServerTestMixin implements ClientCapabilitiesHelperMixin {
(message) {
if (message is NotificationMessage &&
message.method == Method.textDocument_publishDiagnostics) {
diagnosticParams = PublishDiagnosticsParams.fromJson(message.params);
diagnosticParams = PublishDiagnosticsParams.fromJson(
message.params as Map<String, Object?>);
return diagnosticParams!.uri == uri.toString();
}
return false;
@ -1717,7 +1723,8 @@ mixin LspAnalysisServerTestMixin implements ClientCapabilitiesHelperMixin {
await serverToClient.firstWhere((message) {
if (message is NotificationMessage &&
message.method == CustomMethods.publishFlutterOutline) {
outlineParams = PublishFlutterOutlineParams.fromJson(message.params);
outlineParams = PublishFlutterOutlineParams.fromJson(
message.params as Map<String, Object?>);
return outlineParams.uri == uri.toString();
}
@ -1731,7 +1738,8 @@ mixin LspAnalysisServerTestMixin implements ClientCapabilitiesHelperMixin {
await serverToClient.firstWhere((message) {
if (message is NotificationMessage &&
message.method == CustomMethods.publishOutline) {
outlineParams = PublishOutlineParams.fromJson(message.params);
outlineParams = PublishOutlineParams.fromJson(
message.params as Map<String, Object?>);
return outlineParams.uri == uri.toString();
}
@ -1760,7 +1768,8 @@ mixin LspAnalysisServerTestMixin implements ClientCapabilitiesHelperMixin {
(input) => input.cast<Map<String, dynamic>>().map(fromJson).toList();
Future<void> _handleProgress(NotificationMessage request) async {
final params = ProgressParams.fromJson(request.params);
final params =
ProgressParams.fromJson(request.params as Map<String, Object?>);
if (params.token != clientProvidedTestWorkDoneToken &&
!validProgressTokens.contains(params.token)) {
throw Exception('Server sent a progress notification for a token '
@ -1777,7 +1786,8 @@ mixin LspAnalysisServerTestMixin implements ClientCapabilitiesHelperMixin {
throw Exception('Server sent ${Method.window_workDoneProgress_create} '
'but client capabilities do not allow');
}
final params = WorkDoneProgressCreateParams.fromJson(request.params);
final params = WorkDoneProgressCreateParams.fromJson(
request.params as Map<String, Object?>);
if (validProgressTokens.contains(params.token)) {
throw Exception('Server tried to create already-active progress token');
}

View file

@ -40,12 +40,14 @@ class MockLspServerChannel implements LspServerCommunicationChannel {
// `window/showMessage`.
_serverToClient.stream.listen((message) {
if (message is lsp.NotificationMessage &&
message.method == Method.window_showMessage &&
message.params is lsp.ShowMessageParams) {
if (message.params?.type == MessageType.Error) {
shownErrors.add(message.params);
} else if (message.params?.type == MessageType.Warning) {
shownWarnings.add(message.params);
message.method == Method.window_showMessage) {
final params = message.params;
if (params is lsp.ShowMessageParams) {
if (params.type == MessageType.Error) {
shownErrors.add(params);
} else if (params.type == MessageType.Warning) {
shownWarnings.add(params);
}
}
}
});
@ -170,7 +172,9 @@ class MockLspServerChannel implements LspServerCommunicationChannel {
(throwOnError &&
message is lsp.NotificationMessage &&
message.method == Method.window_showMessage &&
lsp.ShowMessageParams.fromJson(message.params).type ==
lsp.ShowMessageParams.fromJson(
message.params as Map<String, Object?>)
.type ==
MessageType.Error));
if (response is lsp.ResponseMessage) {

View file

@ -11,8 +11,8 @@ void main() {
test('handles basic types', () {
expect(_simple('string').dartType, equals('String'));
expect(_simple('boolean').dartType, equals('bool'));
expect(_simple('any').dartType, equals('dynamic'));
expect(_simple('object').dartType, equals('dynamic'));
expect(_simple('any').dartType, equals('Object?'));
expect(_simple('object').dartType, equals('Object?'));
expect(_simple('int').dartType, equals('int'));
expect(_simple('num').dartType, equals('num'));
});

View file

@ -177,6 +177,20 @@ String _rewriteCommentReference(String comment) {
});
}
/// Returns a String representing the underlying Dart type for the provided
/// spec [type].
///
/// This is `Map<String, Object?>` for complex types but can be a simple type
/// for enums.
String _specJsonType(TypeBase type) {
if (type is Type && _namespaces.containsKey(type.name)) {
final valueType = _namespaces[type.name]!.members.cast<Const>().first.type;
return resolveTypeAlias(valueType, resolveEnumClasses: true)
.dartTypeWithTypeArgs;
}
return 'Map<String, Object?>';
}
Iterable<String> _wrapLines(List<String> lines, int maxLength) sync* {
lines = lines.map((l) => l.trimRight()).toList();
for (var line in lines) {
@ -204,7 +218,7 @@ void _writeCanParseMethod(IndentableStringBuffer buffer, Interface interface) {
..writeIndentedln(
'static bool canParse(Object? obj, LspJsonReporter reporter) {')
..indent()
..writeIndentedln('if (obj is Map<String, dynamic>) {')
..writeIndentedln('if (obj is Map<String, Object?>) {')
..indent();
// In order to consider this valid for parsing, all fields that must not be
// undefined must be present and also type check for the correct type.
@ -227,9 +241,12 @@ void _writeCanParseMethod(IndentableStringBuffer buffer, Interface interface) {
..outdent()
..writeIndentedln('}');
}
// Add a local variable to allow type promotion (and avoid multiple lookups).
final localName = _makeValidIdentifier(field.name);
buffer.writeIndentedln("final $localName = obj['${field.name}'];");
if (!field.allowsNull && !field.allowsUndefined) {
buffer
..writeIndentedln("if (obj['${field.name}'] == null) {")
..writeIndentedln('if ($localName == null) {')
..indent()
..writeIndentedln("reporter.reportError('must not be null');")
..writeIndentedln('return false;')
@ -238,11 +255,11 @@ void _writeCanParseMethod(IndentableStringBuffer buffer, Interface interface) {
}
buffer.writeIndented('if (');
if (field.allowsNull || field.allowsUndefined) {
buffer.write("obj['${field.name}'] != null && ");
buffer.write('$localName != null && ');
}
buffer.write('!(');
_writeTypeCheckCondition(
buffer, interface, "obj['${field.name}']", field.type, 'reporter');
buffer, interface, localName, field.type, 'reporter');
buffer
..write(')) {')
..indent()
@ -469,47 +486,56 @@ void _writeField(IndentableStringBuffer buffer, Field field) {
}
void _writeFromJsonCode(
IndentableStringBuffer buffer, TypeBase type, String valueCode,
{required bool allowsNull, bool requiresBracesInInterpolation = false}) {
IndentableStringBuffer buffer,
TypeBase type,
String valueCode, {
required bool allowsNull,
bool requiresCast = true,
}) {
type = resolveTypeAlias(type);
final nullOperator = allowsNull ? '?' : '';
final cast = requiresCast && type.dartTypeWithTypeArgs != 'Object?'
? ' as ${type.dartTypeWithTypeArgs}$nullOperator'
: '';
if (_isSimpleType(type)) {
buffer.write('$valueCode');
buffer.write('$valueCode$cast');
} else if (_isSpecType(type)) {
// Our own types have fromJson() constructors we can call.
if (allowsNull) {
buffer.write('$valueCode != null ? ');
}
buffer.write('${type.dartType}.fromJson${type.typeArgsString}($valueCode)');
buffer
..write('${type.dartType}.fromJson${type.typeArgsString}')
..write('($valueCode as ${_specJsonType(type)})');
if (allowsNull) {
buffer.write(': null');
}
} else if (type is ArrayType) {
// Lists need to be map()'d so we can recursively call writeFromJsonCode
// as they may need fromJson on each element.
buffer.write('$valueCode?.map((item) => ');
_writeFromJsonCode(buffer, type.elementType, 'item',
allowsNull: allowsNull);
buffer
.write(')?.cast<${type.elementType.dartTypeWithTypeArgs}>()?.toList()');
final listCast = requiresCast ? ' as List<Object?>$nullOperator' : '';
buffer.write('($valueCode$listCast)$nullOperator.map((item) => ');
_writeFromJsonCode(buffer, type.elementType, 'item', allowsNull: false);
buffer.write(').toList()');
} else if (type is MapType) {
// Maps need to be map()'d so we can recursively call writeFromJsonCode as
// they may need fromJson on each key or value.
buffer.write('$valueCode?.map((key, value) => MapEntry(');
_writeFromJsonCode(buffer, type.indexType, 'key', allowsNull: allowsNull);
final mapCast = requiresCast ? ' as Map<Object, Object?>$nullOperator' : '';
buffer
..write('($valueCode$mapCast)$nullOperator.map(')
..write('(key, value) => MapEntry(');
_writeFromJsonCode(buffer, type.indexType, 'key', allowsNull: false);
buffer.write(', ');
_writeFromJsonCode(buffer, type.valueType, 'value', allowsNull: allowsNull);
buffer.write(
'))?.cast<${type.indexType.dartTypeWithTypeArgs}, ${type.valueType.dartTypeWithTypeArgs}>()');
_writeFromJsonCode(buffer, type.valueType, 'value', allowsNull: false);
buffer.write('))');
} else if (type is LiteralUnionType) {
_writeFromJsonCodeForLiteralUnion(buffer, type, valueCode,
allowsNull: allowsNull);
} else if (type is UnionType) {
_writeFromJsonCodeForUnion(buffer, type, valueCode,
allowsNull: allowsNull,
requiresBracesInInterpolation: requiresBracesInInterpolation);
_writeFromJsonCodeForUnion(buffer, type, valueCode, allowsNull: allowsNull);
} else {
buffer.write('$valueCode');
buffer.write('$valueCode$cast');
}
}
@ -520,14 +546,16 @@ void _writeFromJsonCodeForLiteralUnion(
if (allowsNull) null,
...union.literalTypes.map((t) => t.literal)
];
final valueType = union.literalTypes.first.dartTypeWithTypeArgs;
final cast = ' as $valueType${allowsNull ? '?' : ''}';
buffer.write(
"const {${allowedValues.join(', ')}}.contains($valueCode) ? $valueCode : "
"throw '''\${$valueCode} was not one of (${allowedValues.join(', ')})'''");
"const {${allowedValues.join(', ')}}.contains($valueCode) ? $valueCode$cast : "
"throw '''\$$valueCode was not one of (${allowedValues.join(', ')})'''");
}
void _writeFromJsonCodeForUnion(
IndentableStringBuffer buffer, UnionType union, String valueCode,
{required bool allowsNull, required bool requiresBracesInInterpolation}) {
{required bool allowsNull}) {
// Write a check against each type, eg.:
// x is y ? new Either.tx(x) : (...)
var hasIncompleteCondition = false;
@ -543,7 +571,7 @@ void _writeFromJsonCodeForUnion(
final type = union.types[i];
final isAny = isAnyType(type);
// Dynamic matches all type checks, so only emit it if required.
// "any" matches all type checks, so only emit it if required.
if (!isAny) {
_writeTypeCheckCondition(
buffer, null, valueCode, type, 'nullLspJsonReporter');
@ -552,10 +580,13 @@ void _writeFromJsonCodeForUnion(
// The code to construct a value with this "side" of the union.
buffer.write('${union.dartTypeWithTypeArgs}.t${i + 1}(');
_writeFromJsonCode(buffer, type, valueCode,
allowsNull: false, // null is already handled above this loop
requiresBracesInInterpolation:
requiresBracesInInterpolation); // Call recursively!
// Call recursively as unions may be nested.
_writeFromJsonCode(
buffer, type, valueCode,
// null + type checks are already handled above this loop
allowsNull: false,
requiresCast: false,
);
buffer.write(')');
// If we output the type condition at the top, prepare for the next condition.
@ -570,8 +601,7 @@ void _writeFromJsonCodeForUnion(
// Fill the final parens with a throw because if we fell through all of the
// cases then the value we had didn't match any of the types in the union.
if (hasIncompleteCondition) {
var interpolation =
requiresBracesInInterpolation ? '\${$valueCode}' : '\$$valueCode';
var interpolation = '\$$valueCode';
buffer.write(
"throw '''$interpolation was not one of (${union.types.map((t) => t.dartTypeWithTypeArgs).join(', ')})'''");
}
@ -583,7 +613,7 @@ void _writeFromJsonConstructor(
final allFields = _getAllFields(interface);
buffer
..writeIndentedln('static ${interface.nameWithTypeArgs} '
'fromJson${interface.typeArgsString}(Map<String, dynamic> json) {')
'fromJson${interface.typeArgsString}(Map<String, Object?> json) {')
..indent();
// First check whether any of our subclasses can deserialise this.
for (final subclassName in _subtypes[interface.name] ?? const <String>[]) {
@ -597,10 +627,13 @@ void _writeFromJsonConstructor(
..writeIndentedln('}');
}
for (final field in allFields) {
buffer.writeIndented('final ${field.name} = ');
_writeFromJsonCode(buffer, field.type, "json['${field.name}']",
allowsNull: field.allowsNull || field.allowsUndefined,
requiresBracesInInterpolation: true);
// Add a local variable to allow type promotion (and avoid multiple lookups).
final localName = _makeValidIdentifier(field.name);
final localNameJson = '${localName}Json';
buffer.writeIndented("final $localNameJson = json['${field.name}'];");
buffer.writeIndented('final $localName = ');
_writeFromJsonCode(buffer, field.type, localNameJson,
allowsNull: field.allowsNull || field.allowsUndefined);
buffer.writeln(';');
}
buffer
@ -760,9 +793,9 @@ void _writeToJsonMethod(IndentableStringBuffer buffer, Interface interface) {
// It's important the name we use for the map here isn't in use in the object
// already. 'result' was, so we prefix it with some underscores.
buffer
..writeIndentedln('Map<String, dynamic> toJson() {')
..writeIndentedln('Map<String, Object?> toJson() {')
..indent()
..writeIndentedln('var __result = <String, dynamic>{};');
..writeIndentedln('var __result = <String, Object?>{};');
// ResponseMessage must confirm to JSON-RPC which says only one of
// result/error can be included. Since this isn't encoded in the types we
// need to special-case it's toJson generation.
@ -806,7 +839,7 @@ void _writeTypeCheckCondition(IndentableStringBuffer buffer,
final dartType = type.dartType;
final fullDartType = type.dartTypeWithTypeArgs;
if (fullDartType == 'dynamic') {
if (fullDartType == 'Object?') {
buffer.write('true');
} else if (_isSimpleType(type)) {
buffer.write('$valueCode is $fullDartType');
@ -816,7 +849,7 @@ void _writeTypeCheckCondition(IndentableStringBuffer buffer,
buffer.write('$dartType.canParse($valueCode, $reporter)');
} else if (type is ArrayType) {
buffer.write('($valueCode is List');
if (fullDartType != 'dynamic') {
if (fullDartType != 'Object?') {
// TODO(dantup): If we're happy to assume we never have two lists in a union
// we could skip this bit.
buffer.write(' && ($valueCode.every((item) => ');
@ -827,7 +860,7 @@ void _writeTypeCheckCondition(IndentableStringBuffer buffer,
buffer.write(')');
} else if (type is MapType) {
buffer.write('($valueCode is Map');
if (fullDartType != 'dynamic') {
if (fullDartType != 'Object?') {
buffer..write(' && (')..write('$valueCode.keys.every((item) => ');
_writeTypeCheckCondition(
buffer, interface, 'item', type.indexType, reporter);

View file

@ -98,7 +98,7 @@ String? getImprovedType(String interfaceName, String? fieldName) {
},
'ResponseError': {
'code': 'ErrorCodes',
// This is dynamic normally, but since this class can be serialised
// This is Object? normally, but since this class can be serialised
// we will crash if it data is set to something that can't be converted to
// JSON (for ex. Uri) so this forces anyone setting this to convert to a
// String.

View file

@ -507,17 +507,17 @@ class Parser {
/// Returns the next token without advancing.
Token _peek() => _tokenAt(_current);
/// Remove any duplicate types (for ex. if we map multiple types into dynamic)
/// we don't want to end up with `dynamic | dynamic`. Key on dartType to
/// Remove any duplicate types (for ex. if we map multiple types into Object?)
/// we don't want to end up with `Object? | Object?`. Key on dartType to
/// ensure we different types that will map down to the same type.
TypeBase _simplifyUnionTypes(List<TypeBase> types) {
final uniqueTypes = Map.fromEntries(
types.map((t) => MapEntry(t.uniqueTypeIdentifier, t)),
).values.toList();
// If our list includes something that maps to dynamic as well as other
// types, we should just treat the whole thing as dynamic as we get no value
// typing Either4<bool, String, num, dynamic> but it becomes much more
// If our list includes something that maps to Object? as well as other
// types, we should just treat the whole thing as Object? as we get no value
// typing Either4<bool, String, num, Object?> but it becomes much more
// difficult to use.
if (uniqueTypes.any(isAnyType)) {
return uniqueTypes.firstWhere(isAnyType);
@ -954,8 +954,8 @@ class Type extends TypeBase {
'number': 'num',
'integer': 'int',
'uinteger': 'int',
'any': 'dynamic',
'object': 'dynamic',
'any': 'Object?',
'object': 'Object?',
// Simplify MarkedString from
// string | { language: string; value: string }
// to just String
@ -1001,7 +1001,7 @@ abstract class TypeBase {
String get typeArgsString;
/// A unique identifier for this type. Used for folding types together
/// (for example two types that resolve to "dynamic" in Dart).
/// (for example two types that resolve to "Object?" in Dart).
String get uniqueTypeIdentifier => dartTypeWithTypeArgs;
}