-
Notifications
You must be signed in to change notification settings - Fork 205
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
[breaking change] Remove implicit tearoff of call
methods
#2399
Comments
@natebosch will investigate the feasibility of this. |
IIRC this would be a potential (big?) win for dart2js (@sigmundch @rakudrama ?) |
I don't think it will do much for dart2js since it just makes an implicit inferred The front-end inference might get slightly faster. (I'd be happy to be wrong, though!) |
Does this issue relate to Dart2js currently can't use the CFE lowering to encode getter calls as an explicit class A {
Function get f;
}
main() => A().f(); The CFE lowering changes main to be equivalent to |
Towards dart-lang/language#2399 Change-Id: I4e31bec7703504711a5827e282b2b52d14ae6b05 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/254465 Auto-Submit: Nate Bosch <[email protected]> Reviewed-by: Konstantin Shcheglov <[email protected]> Reviewed-by: Brian Wilkerson <[email protected]> Commit-Queue: Brian Wilkerson <[email protected]> Commit-Queue: Konstantin Shcheglov <[email protected]>
No. This issue only relates to how non-Function types are implicitly coerced to a Function type by tearing off |
I have an implementation of a fix for this usage which unconditionally wraps the expression in parenthesis to make it safe to tear off @bwilkerson - I am trying to figure out if there might be a way to avoid introducing these parens when they are not necessary. Do you have any suggestions for reliable ways to detect this? Would it make sense to try to enumerate the types of expressions that do or don't need wrapping? |
@natebosch any updates about this? |
The lint is working. I need to take a look at the unnecessary_parenthesis implementation to see if it is simple enough to reproduce in the fix. |
I'm having a bit of trouble understanding the intent of this ticket, could somebody help me understand if the following would still be possible: class MyClass {
void call({required String name}) {
...
}
}
void main() {
final myClass = MyClass();
myClass(name: 'Brandon');
} I'm hoping so, since this is one of my favorite language features coming from Kotlin. Edit: Additionally, if that would still be possible, I'm wondering what this proposal's intent is and how it would impact the usage of a class as written above. |
Calling the callable object's void Function({required String name}) f = MyClass(); You would have to do (the equivalent): void Function({required String name}) f = MyClass().call; You won't get an implicit tear-off of the In Dart 1, the It's actually the context-dependency that worries me, more than the tear-off readability. If you care about the readability, you can always write Because it sometimes get tricky, especially around union types like |
That was extremely helpful @lrhn, thank you for that explanation!
Totally agree that predictability/reasonableness in this case is more valuable than readability/minimizing code written. Thanks again! |
Note that the ground is being prepared for this change with a lint: avoid-implicit-call-tearoffs. |
We will want to include the lint in our core lint rules so that the widest audience sees it surfaced. |
Hi @natebosch any news about the |
Fix is implemented and waiting for review. https://dart-review.git.corp.google.com/c/sdk/+/256163 The crbot failures in that CL are related to the |
Towards dart-lang/language#2399 Add `AddExplicitCall` correction producer and configure it to run for `implicit_call_tearoffs` diagnostics. Add parenthesis for any expressions with lower precedence than `.call`. Add tests which include patterns returning and passing an implicit tearoff. Change-Id: I482d2571b183bd0dfdf6c77f0cf530117add9eca Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/256163 Reviewed-by: Brian Wilkerson <[email protected]> Reviewed-by: Konstantin Shcheglov <[email protected]> Commit-Queue: Nate Bosch <[email protected]>
call
methodscall
methods
We have classes like: class Function1Mock<T0, T1> extends Mock {
T0 call([T1? arg1]) => ...
} that are used in tests like: late Function2Mock<void, String> onNameChanged;
setUp(() {
onNameChanged = Function2Mock();
});
// code being tested
// void sut(Function<void, String> onNameChanged) => ...
test("...", () {
sut(onNameChanged);
...
verify(onNameChanged("expected name")).called(1);
}); Would |
@spkersten Yes. That's exactly the situation where the change applies, an implicit tear-off of the You could also just change the function name to, say, test("...", () {
sut(onNameChanged.function);
...
verify(onNameChanged.function("expected name")).called(1);
}); That's being very explicit that you have a mock that you only use the one function of. |
It's so great that a lot was done in 2.19 for this change, with a new lint rule and a fix. Is this change planned for Dart 3.0? |
No, we are going to put the lint in our core set now, and then put this change behind a language version. There are still some open questions about the UX after this change. Once the language doesn't have implicit tearoffs, we will either need to
|
I am going to remove this breaking change from the Dart 3 project as it is not planned for Dart 3. |
I know I'm late for this discussion but for my package flutter_command this change would make all code look much uglier as the beauty of a callable class is that it can be assigned to event handlers which is a central use case for my command package. |
I have yet to find a case where the associated lint was helpful to me. On the flip side, having to add an extra I don't care that But I do use callable classes quite often for spying on event-handlers: class OnChangeListener<T> extends Mock {
void cal(T value);
}
test('example', () {
final listener = OnChangeListener<int>();
someObservableObject.listen(listener);
verify(listener(0));
}); |
Food for thought: Couldn't we instead remove the implicit tear-off and rather have callable classes implement So we could still do I assume that would remove the original concern? |
@rrousselGit As it is, every object which implements But that also means that So, making callable classes implement For @escamoteur, if you call your function onRetry: feedSource.updateDataCommand.run doesn't look as bad. It looks weird because |
It might look better but I don't know why this change is necessary at all. I never heard about anyone having problems with the way it is now and the idea that a command object can be used like a function just make sense as you can call it directly too.
I really hope you don't plan to abandon callable classes as a whole. My other package get_it lives from them too.
Am 9. Jan. 2024, 18:41 +0100 schrieb Lasse R.H. Nielsen ***@***.***>:
… @rrousselGit As it is, every object which implements Function is a function value, which are a separate kind of objects from class instances. Which is not a big optimization, because the only thing you can do with Function-typed values is to do dynamic invocations on them. You can get the same effect by casting to dynamic before doing the dynamic invocation.
But that also means that Function is not very useful. (You can just use dynamic.)
Most likely, just implementing Function is not enough, and people will want the object to implement the actual function type of the call method. Which is just not compatible with Dart's current type system, when generics are covariant, but call method arguments would be contravariant. There's a reason we stopped doing that when moving to the sounder Dart 2 type system.
So, making callable classes implement Function is likely too little to be useful, yet too invasive for the type system to be practical.
For @escamoteur, if you call your function run instead of call, then:
onRetry: feedSource.updateDataCommand.run
doesn't look as bad. It looks weird because call isn't a good name for that function, it's only there to not be visible when calling.
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you were mentioned.Message ID: ***@***.***>
|
Currently an assignment from a "callable class type" (an interface type where the interface has a
call
method) to a function type or the typeFunction
will perform an implicitcall
method tearoff.That is:
means that
Function f = MyClass();
will be allowed, even ifMyClass
is not a subtype ofFunction
, and will be implicitly converted to/treated asFunction f = MyClass.call;
.Whether to do the implicit tear-off depends on the context type where the expression occurs, which means that we have an expression which changes its type, invisibly, depending on an, also invisible, context type. It's hard to read, and if something goes wrong, it's very hard to see where.
This breaking change removes this implicit tear-off behavior, and requires you to write the
.call
explicitly to get the current behavior.Historically, prior to Dart 2.0, such callable class types were subtypes of the function type of their
call
method. The Dart 1 type system was unsound in many ways, and this particular subtyping could not be included soundly in the Dart 2 type system. Instead an implicit coercion was introduced, which made some of the old code keep working, but not all of it. Code that relied on the actual callable class object being stored in the variable would no longer work. The torn-offcall
method was stored instead.The current code still looks like it stores the object, which is deceptive and potentially error-prone.
And it does so based on a potentially invisible context type, which makes it intractable to verify the code by visual inspection.
There is no syntax suggesting that something special is going on in this assignment. There is no warning if it stops happening for some reason, perhaps because the context type changes to
dynamic
.Because of that, it's considered better for readability and safety to force the tear-off to be explicit.
Also, with the patterns proposal, there will be more ways to do assignment, with more distance between the variable and the initializing expression, which makes it even easier to make mistakes and overlook the implicit tear-off.
There are no plans to change the behavior when calling such an object. You can still do:
without having to write
myFunctionLike.call()
.There is syntax here suggesting exactly what's happening (something is getting called), and no expression which changes type because of an invisible coercion, and almost no dependency on the context type (unless the
call
method is generic, then type inference may use the context type to infer type arguments as usual).The text was updated successfully, but these errors were encountered: