Skip to content
This repository has been archived by the owner on Sep 27, 2021. It is now read-only.

enable null-safety #45

Closed
wants to merge 24 commits into from
Closed

enable null-safety #45

wants to merge 24 commits into from

Conversation

2ZeroSix
Copy link
Contributor

@2ZeroSix 2ZeroSix commented Mar 6, 2021

Closes #43

  • enable nullsafety
  • add generic parameter in then clauses without return: then<Null>
    fixes Null is not a subtype of Future<Never>
  • replace custom priority queue with PriorityQueue from package:collection to get rid of nullable _LoadBalancerEntry everywhere

@google-cla google-cla bot added the cla: yes label Mar 6, 2021
@kevmoo kevmoo requested a review from lrhn March 6, 2021 21:26
Copy link
Contributor Author

@2ZeroSix 2ZeroSix left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I enabled strong-mode for implicit casts to find all the places with them

it seems that only 3 places with potentially unsafe casts left (all highlighted in next comments)

  1. _handler!(message)
  2. SendPort.send
  3. singleCompletePort with dynamic generic parameters

@@ -49,7 +49,7 @@ class _MultiplexRawReceivePort implements RawReceivePort {
SendPort get sendPort => _multiplexer._createSendPort(_id);

void _invokeHandler(message) {
_handler(message);
_handler!(message);
Copy link
Contributor Author

@2ZeroSix 2ZeroSix Mar 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it seems to be breaking change to call handler conditionally, but leaving it like this seems to be a mistake too

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the only place with force unwrap besides 2 valid operator[] on maps in registry.dart

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the current behavior is wrong. Also, it's not considered a breaking change to change the behavior of something which throws an Error, so changing it is fine.

I think _handle?.call(message) would be correct. The alternative is to have a default handler of (_){}.

Comment on lines -42 to -44
test('Value', () {
var completer = Completer.sync();
var p = singleCallbackPort(completer.complete);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a removal of duplicated test

Comment on lines +335 to +339
test('FutureValueWithoutTimeout invalid null', () {
return expectLater(singleResponseFutureWithoutTimeout<int>((SendPort p) {
p.send(null);
}), throwsA(isA<TypeError>()));
});
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is impossible to check statically since SendPort.send expects Object?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, ports are inherently untyped. Primarily because they are able to send values between isolates that don't necessarily share types.

We have to get the value to the other side of the port connection in order to check it against the original type argument.

Comment on lines +295 to +307
test('TimeoutFirst with invalid null', () {
final completer = Completer<int>.sync();

/// example of incomplete generic parameters promotion.
/// same code with [singleCompletePort<int, dynamic>] is a compile time error
final p = singleCompletePort(
completer,
timeout: _ms * 100,
onTimeout: () => null,
);
Timer(_ms * 500, () => p.send(42));
return expectLater(completer.future, throwsA(isA<TypeError>()));
});
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems to be related to generic types promotion with incomplete types
callback arg is required to promote second generic parameter, so both parameters are handled as dynamic

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems likely, yes.

@ericmartineau
Copy link

Not sure if this will ever be merged. I released your branch as a project isolates. I'm happy to give you control, or git access. Thx for doing this

@bsutton
Copy link

bsutton commented Mar 18, 2021

@2ZeroSix
@ericmartineau is the isolate project dead hence it won't be merged or is there some other reason it won't be?

I've found a bug in the isolates version of the code is there a github repo for it that I can push a patch to?

../../../.pub-cache/hosted/pub.dartlang.org/isolates-3.0.3+7/lib/isolate_runner.dart:85:36: Warning: Operand of null-aware operation '??' has type 'bool' which excludes null.
isolate.setErrorsFatal(builder.failOnError ?? true);

Copy link
Contributor

@lrhn lrhn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks very thorough. There are a few subtle potentially-bugs, but otherwise very solid work.

I'm half torn towards rewriting the API to avoid the "you can omit the value if it's null" places where it'll now be an error to pass null with a non-nullable type argument.
It's probably better to launch this as null safe first, then make a 3.0.0 version afterwards.

@@ -53,7 +55,7 @@ class EchoHttpListener implements HttpListener {
static final _id = Isolate.current.hashCode;
final SendPort _counter;

StreamSubscription _subscription;
late StreamSubscription _subscription;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer to not use late unless necessary. I'd just use StreamSubscription? here.

int port = args[0];
HttpListener listener = args[1];
final port = args[0] as int;
final listener = args[1] as HttpListener;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code doesn't use final, so I wouldn't change that.
I'd change the argument to List<Object?> instead of (implicit) List<dynamic> while here.

@@ -61,8 +61,8 @@ class IsolateRunner implements Runner {
isolate.setErrorsFatal(false);
var pingChannel = SingleResponseChannel();
isolate.ping(pingChannel.port);
var commandPort = await channel.result;
var result = IsolateRunner(isolate, commandPort);
final commandPort = await channel.result as SendPort;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, the style here is not to use final. Just use var.

@@ -150,7 +151,7 @@ class IsolateRunner implements Runner {
/// Calling pause more than once with the same `resumeCapability`
/// has no further effect. Only a single call to [resume] is needed
/// to resume the isolate.
void pause([Capability resumeCapability]) {
void pause([Capability? resumeCapability]) {
resumeCapability ??= isolate.pauseCapability;
isolate.pause(resumeCapability);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably throw here if resumeCapability is still null. That, or return the result of isolate.pause. Or have a default resumeCapability it uses if necessary.

As written, it pauses in a way which cannot be resumed if isolate.pauseCapability is null. I'll look into this.

resumeCapability ??= isolate.pauseCapability;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just do:

if (resumeCapability == null) return;

@@ -22,259 +22,335 @@ void main() {

void testSingleCallbackPort() {
test('Value', () {
var completer = Completer.sync();
var p = singleCallbackPort(completer.complete);
final completer = Completer.sync();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to change var to final.

Comment on lines +295 to +307
test('TimeoutFirst with invalid null', () {
final completer = Completer<int>.sync();

/// example of incomplete generic parameters promotion.
/// same code with [singleCompletePort<int, dynamic>] is a compile time error
final p = singleCompletePort(
completer,
timeout: _ms * 100,
onTimeout: () => null,
);
Timer(_ms * 500, () => p.send(42));
return expectLater(completer.future, throwsA(isA<TypeError>()));
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems likely, yes.

test('TimeoutFirst with invalid null', () {
final completer = Completer<int>.sync();

/// example of incomplete generic parameters promotion.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Format comments as sentences, so capital first letter and trailing ..

Comment on lines +335 to +339
test('FutureValueWithoutTimeout invalid null', () {
return expectLater(singleResponseFutureWithoutTimeout<int>((SendPort p) {
p.send(null);
}), throwsA(isA<TypeError>()));
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, ports are inherently untyped. Primarily because they are able to send values between isolates that don't necessarily share types.

We have to get the value to the other side of the port connection in order to check it against the original type argument.

@override
bool operator ==(Object other) => other is Element && id == other.id;
bool operator ==(Object? other) => other is Element && id == other.id;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be (Object other). A null value can't reach operator==.

@2ZeroSix
Copy link
Contributor Author

FYI
I haven't abandoned this pull request, I will have time to fix everything from the review comments in a few days

@bsutton
Copy link

bsutton commented Apr 19, 2021

@2ZeroSix any eta on getting this merged?

@gaetschwartz
Copy link

Hi, any updates in this PR ? Would be great if this could get merged or reviewed !

@allanwolski
Copy link

Thanks @2ZeroSix

I'm waiting for this too.

@AlexV525
Copy link
Contributor

@2ZeroSix Can you add me as a collaborator in your repo? I can push this forward as far as it can. We're currently waiting it for too long. :)

@AlexV525
Copy link
Contributor

Also I saw @irhn said is investigating something like #45 (comment) , any result?

@@ -122,7 +122,7 @@ class Registry<T> {
_timeout = timeout;

_RegistryCache get _cache {
_RegistryCache cache = _caches[this];
var cache = _caches[this] as _RegistryCache?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This entire getter could be

_RegistryCache get _cache => _caches[this] ??= _RegistryCache();

if _caches is declared as Expando<_RegistryCache>.

..[0] = v1
..[1] = v2
..[2] = v3;

/// Create a four-element fixed-length list.
List<Object> list4(Object v1, Object v2, Object v3, Object v4) =>
List<Object?> list4(Object? v1, Object? v2, Object? v3, Object? v4) =>
List.filled(4, null)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could possibly optimize these a little by doing:

  List.filled(4, v1)
    ..[2] = v2
    ..[3] = v3
    ..[4] = v4;

and avoid one assignment. (Not a big save, but a save!)./

@AlexV525
Copy link
Contributor

AlexV525 commented Jun 1, 2021

I've addressed all suggestions in this PR to #51 . FYI 😃

@2ZeroSix 2ZeroSix closed this Jun 4, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Development

Successfully merging this pull request may close these issues.

nnbd version
7 participants