-
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
Should Object methods be callable on nullable types? #175
Comments
Note that if not, you're not able to call Also, I'm pretty sure we want to allow string interpolation to interpolate null values. So we'd have: int? i = null;
print(i.toString()); // Error
print("$i"); // Ok |
There's an "under the radar rule" that a receiver whose static type is a union type supports access to members that are shared by all summands of that union type. With that, we'd say "yes". |
If we answer "no" to this, then I believe the answer should be "yes". It's useful in practice, makes interpolation consistent with It ain't broke now, so let's not "fix" it. |
Drive-by comment: Making class V {
final String? name;
const V(this.name);
int get hashCode => name.hashCode;
bool operator==(Object o) => o is V && name == o.name;
} Both |
Decided: yes, they can. @leafpetersen to write this up. |
So, playing devil's advocate here, if you have the following code in Flutter: String _initalValueForTextFormField() { //Notice return type is String not String? so the return value should not be nullable
int? initialvalue = SomeService.getInitialValue();
return initialvalue.toString(); //issue in question on this line
} Shouldn't the return statement above give an error so that the end user doesn't see null in the TextFormField? The error message may be something like:
I used Kotlin's not-null assertion operator (!!) there, not saying you guys will decide on the same thing. So valid code would end up being this: String _initalValueForTextFormField() {
int? initialvalue = SomeService.getInitialValue();
return initialvalue!!.toString(); // added the !! operator
} Or this: String _initalValueForTextFormField() {
int? initialvalue = SomeService.getInitialValue();
return initialvalue?.toString() ?? "It was null!"; //added null check
} |
@eernstg Doesn't this go against what we are trying to achieve with nullable types? Say we have a custom class C? item = C(); And say that item.methodOnC() Then what is the point of having the nullable types at all? Perhaps I'm not interpreting your statement correctly so please let me know if I'm not. |
If we assume the return type of
That's not what he's saying. "shared by all summands" means every branch of the union has to have a member before you can access it, including the Null branch. Since Null has very few members (just |
Right, I understand it returns the four letter word "null". Which is what I am arguing should not be allowed. If you look at the valid code blocks I posted, the first one throws an NPE because of the So while in theory, sure, letting null have the same functions as What I'm suggesting is that at the return statement the programmer is given some sort of warning, error, etc. saying "Hey, this value could be null. Either mark it as a possible NPE or do a null check."
Ah yes. Thank you for the explanation. Haven't had discrete math in almost a decade so I need reminders from time to time. |
There are definitely examples of places where one might prefer not to silently be allowed to call class A<T> {
T x;
String toString() {
String xAsString = x?.toString() ?? "null";
return "A{x = $xAsString}";
} Overall, this felt like very awkward for fairly limited benefit. Yes, we'd help you catch the cases where you really didn't want to accidentally print out null, but this doesn't seem, to me at least, to justify the cost. FWIW, Kotlin seems to make the same choice - it allows calling Object methods on nullable types. |
I see your point, and I'm probably not thinking through all the cases where my suggestion would be a problem (I'm definitely not). Two language teams, probably the Swift team too, have come to the same conclusion so it must be the way to go. |
Interestingly, as best I can tell Swift objects have no common base class with a common set of methods, and |
@gbaldeck wrote:
@munificent already mentioned why this only allows for using members of Let e be an expression whose type is a union of some types T1 | .. | Tk. Let's just consider class types (and ignore function types and oddballs like If we go directly to a shared superinterface of all those class interfaces then we'll find an interface where every member is inherited by each Tj, and they'll be associated with the same dartdoc comment etc.: They mean the same thing. In contrast, if we allow invoking |
@leafpetersen That's very interesting that they don't have a shared IMO, despite the union rules between types I feel that Dart should make nulls a special case and not allow the |
At this point I suspect that we can make a decision: Yes, methods declared on the top type ( Note that one case which is affected by this (cf. this comment) is receivers of type Presumably, the breakage would be far too massive if we were to decide that a type variable with no explicit bound has the bound "non-null object" (spelled |
Agree. We have to allow If we had designed Dart from the start so that We might have to introduce a synthetic and un-namable top interface type containing the |
We did so a month ago... :) |
I've been keeping the decided issues open as reminders, but maybe that's too confusing: they're linked anyway from the header of the main issue. I'll go ahead and close out the decided issues. |
I know you all have decided on this, and I'm not trying to change your mind. But I've thought of a different solution to the problem I mentioned in this thread a month or two back. Refresher on the problem: Dart is evolving as a multipurpose language with an emphasis on the front end. With the addition of non-null by default, Dart provides a great tool to catch null values before they can cause a problem. In Flutter the By allowing Here is an example: class PaymentPage extends StatefulWidget {
double? paymentAmount; //nullable
PaymentPage(Service service) {
paymentAmount = service.serverCallToRetrieveAmountDue(); //call to server, could return null
}
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: TextFormField(
initialValue: paymentAmount.toString(), //null check not required, the user sees null if paymentAmount is null
decoration: const InputDecoration(
labelText: 'Amount to Pay',
),
keyboardType: TextInputType.number,
validator: (value) {
//validator logic
},
onSaved: (value) => paymentAmount = double.parse(value),
),
);
}
} In the example above, if paymentAmount is null, the user will see null. Rather than disallowing the Object methods to be called on nullable types (which is what I was arguing for before), I propose that an analyzer option be added under the "errors" category that will notify the programmer of analyzer:
errors:
tostring_on_nullable: error Since the programmer is reminded to do a null check when using class PaymentPage extends StatefulWidget {
double? paymentAmount; //nullable
PaymentPage(Service service) {
paymentAmount = service.serverCallToRetrieveAmountDue(); //call to server, could return null
}
//This build method should only be run once on app startup
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: TextFormField(
initialValue: paymentAmount?.toString() ?? "0.00", //null check now required because of analyzer option
decoration: const InputDecoration(
labelText: 'Amount to Pay',
),
keyboardType: TextInputType.number,
validator: (value) {
//validator logic
},
onSaved: (value) => paymentAmount = double.parse(value),
),
);
}
} This will help a lot in catching a null value before it is seen by the user, which goes along with the benefits of NNBD. Also, the analyzer option could just as easily be changed to warning so that |
There's a pretty strong and rich set of lints available from the analyzer (and Flutter makes good use of the linting ability), and this seems to me to be a good candidate for a lint. |
@leafpetersen A linter option sounds good, just something to remind the programmer that their |
In NNBD code, is it valid to call methods from
Object
on nullable types?The text was updated successfully, but these errors were encountered: