Skip to content

Add "base", "final" and "interface" modifiers #920

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
May 9, 2023

Conversation

brianquinlan
Copy link
Collaborator

@brianquinlan brianquinlan commented May 3, 2023

Fixes #918

@github-actions github-actions bot added the type-infra A repository infrastructure change or enhancement label May 3, 2023
@brianquinlan brianquinlan changed the title zAdd Dart 3.0 class modifiers Add "base", "final" and "interface" modifiers May 3, 2023
@@ -14,7 +14,7 @@ import 'http.dart';
/// NOTE: [RetryClient] makes a copy of the request data in order to support
/// resending it. This can cause a lot of memory usage when sending a large
/// [StreamedRequest].
class RetryClient extends BaseClient {
final class RetryClient extends BaseClient {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Interface is identical to client. There is no reason to implement or extend this.

@@ -31,7 +31,7 @@ BaseClient createClient() {
/// [BaseRequest.followRedirects], and [BaseRequest.maxRedirects] fields. It is
/// also unable to stream requests or responses; a request will only be sent and
/// a response will only be returned once all the data is available.
class BrowserClient extends BaseClient {
base class BrowserClient extends BaseClient {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Interface is identical to Client

Copy link
Member

Choose a reason for hiding this comment

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

I wonder if this should be final?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I could imagine people wanting to add methods to Client implementations. Like:

class DownloadingBrowserClient extends BrowserClient {
  Stream<UInt8List> downloadFile(Url) {
     ...
  }

}```

Copy link
Member

Choose a reason for hiding this comment

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

If this is usable as a skeleton/base class for people who might want to extend the interface, then I'd just make it open (no modifier).
The example you give shows exactly that: You forgot to put base on DownloadingBrowserClient, and the author of it might want to allow other implementations (for testing/mocking or genuinely considers it an interface worth implementing).

So, final or open, only use base for very particular cases where extending is fine, but inheriting implementation is essential (mainly existing code which calls private members on the type).

I expect very few base modifiers, because of the transitive "must be base" requirement. It can't be used for just "don't implement this interface, because it doesn't make sense". We don't have that, sadly. (Otherwise Object would have that modifier, but it definitely cannot be base.)

@@ -43,7 +43,7 @@ class _ClientSocketException extends ClientException
}

/// A `dart:io`-based HTTP client.
class IOClient extends BaseClient {
base class IOClient extends BaseClient {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Interface is identical to Client

Copy link
Member

Choose a reason for hiding this comment

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

I'd make it final or open.

There is no need to require extending subclasses to be base.

@@ -14,14 +14,14 @@ import 'package:test/test.dart';

import '../utils.dart';

class TestClient extends http.BaseClient {
base class TestClient extends http.BaseClient {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Interface is identical to Client

Copy link
Member

Choose a reason for hiding this comment

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

This class is in the test directory, so no need to give it a modifier unless one is required.

@override
Future<http.StreamedResponse> send(http.BaseRequest request) {
throw UnimplementedError();
}
}

class TestClient2 extends http.BaseClient {
base class TestClient2 extends http.BaseClient {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Interface is identical to Client

Copy link
Member

Choose a reason for hiding this comment

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

Ditto. Just no modifier. It's not a public class, so we don't care what people can do with it.

@@ -10,7 +10,7 @@ import 'package:test/test.dart';

import '../utils.dart';

class TestClient extends http.BaseClient {
base class TestClient extends http.BaseClient {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Interface is identical to Client

Copy link
Member

Choose a reason for hiding this comment

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

Also just a test class, so no modifier needed.

@brianquinlan
Copy link
Collaborator Author

I didn't add base to BaseRequest, BaseClient and BaseResponse but someone might want to implement (e.g. for mocking) an implementor of those classes.

Copy link
Member

@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.

The BaseClient class is documented as "This is a mixin-style class" and it can be used as a mixin today, so I'd recommend making it a mixin class.

LGTM without the bases.

(The package could generally use some clean-up. A lot of the code seems overly complicated, and could use more modern features or just be optimized better.)

@@ -31,7 +31,7 @@ BaseClient createClient() {
/// [BaseRequest.followRedirects], and [BaseRequest.maxRedirects] fields. It is
/// also unable to stream requests or responses; a request will only be sent and
/// a response will only be returned once all the data is available.
class BrowserClient extends BaseClient {
base class BrowserClient extends BaseClient {
Copy link
Member

Choose a reason for hiding this comment

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

If this is usable as a skeleton/base class for people who might want to extend the interface, then I'd just make it open (no modifier).
The example you give shows exactly that: You forgot to put base on DownloadingBrowserClient, and the author of it might want to allow other implementations (for testing/mocking or genuinely considers it an interface worth implementing).

So, final or open, only use base for very particular cases where extending is fine, but inheriting implementation is essential (mainly existing code which calls private members on the type).

I expect very few base modifiers, because of the transitive "must be base" requirement. It can't be used for just "don't implement this interface, because it doesn't make sense". We don't have that, sadly. (Otherwise Object would have that modifier, but it definitely cannot be base.)

@@ -43,7 +43,7 @@ class _ClientSocketException extends ClientException
}

/// A `dart:io`-based HTTP client.
class IOClient extends BaseClient {
base class IOClient extends BaseClient {
Copy link
Member

Choose a reason for hiding this comment

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

I'd make it final or open.

There is no need to require extending subclasses to be base.

@@ -14,14 +14,14 @@ import 'package:test/test.dart';

import '../utils.dart';

class TestClient extends http.BaseClient {
base class TestClient extends http.BaseClient {
Copy link
Member

Choose a reason for hiding this comment

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

This class is in the test directory, so no need to give it a modifier unless one is required.

@@ -10,7 +10,7 @@ import 'package:test/test.dart';

import '../utils.dart';

class TestClient extends http.BaseClient {
base class TestClient extends http.BaseClient {
Copy link
Member

Choose a reason for hiding this comment

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

Also just a test class, so no modifier needed.

@override
Future<http.StreamedResponse> send(http.BaseRequest request) {
throw UnimplementedError();
}
}

class TestClient2 extends http.BaseClient {
base class TestClient2 extends http.BaseClient {
Copy link
Member

Choose a reason for hiding this comment

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

Ditto. Just no modifier. It's not a public class, so we don't care what people can do with it.

@@ -7,7 +7,7 @@ import 'dart:convert';
import 'dart:typed_data';

/// A stream of chunks of bytes representing a single piece of data.
class ByteStream extends StreamView<List<int>> {
final class ByteStream extends StreamView<List<int>> {
Copy link
Member

Choose a reason for hiding this comment

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

(This class should not need to exist. It should just be extension methods on Stream<List<int>>.)

@brianquinlan
Copy link
Collaborator Author

brianquinlan commented May 9, 2023

The BaseClient class is documented as "This is a mixin-style class" and it can be used as a mixin today, so I'd recommend making it a mixin class.

EDIT I wrote a lot of wrong this below. TL;DR I made BaseClient mixin.

BaseClient requires that you implement a send method so I don't think that it is currently useable as a mixin. We could restructure this a bit to have a new Sendable class like:

interface Sendable {
  Future<SendResponse> send(SendRequest request);
}

mixin BaseClient on Sendable {
...
}

But I think that should be a separate change.

Please correct me if I'm wrong - I've never actually used a mixin!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
package:http type-infra A repository infrastructure change or enhancement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add Dart 3 class modifiers
3 participants