-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
useless The argument type 'String?' can't be assigned to the parameter type 'String'.
#59820
Comments
Summary: Null-safe |
here is the fixed code without using class _SmartExposureAnalysisWidget extends StatelessWidget {
const _SmartExposureAnalysisWidget({
required String? pathImgSelectedLut,
required String? imagePath,
required this.previewGeneratorChangeNotifier,
}) : _pathImgSelectedLut = pathImgSelectedLut,
_imagePath = imagePath;
final String? _pathImgSelectedLut;
final String? _imagePath;
final PreviewGeneratorChangeNotifier previewGeneratorChangeNotifier;
@override
Widget build(BuildContext context) {
var imagePath = _imagePath;
if (imagePath == null) {
return const SizedBox.shrink();
}
var pathImgSelectedLut = _pathImgSelectedLut;
if (pathImgSelectedLut == null) {
return ExposureAnalysisWidget(imagePath, previewGeneratorChangeNotifier);
}
return ExposureAnalysisWidget(
pathImgSelectedLut, previewGeneratorChangeNotifier);
}
}
|
The usual reason applies: The compiler won't promote anything that would make it a breaking change to change the implementation, or that isn't absolutely guaranteed to be stable. (The one exception is a library private final variable that is not overridden inside the same library. And even then, it's only allowed to be promoted inside that library, because then the library author will immediately see if they break it.) |
thank you @lrhn for your explanation but sadly I don't understand why a local variable can be promoted while a member variable can't (inside the function scope) Let's take this simple example: class _MyWidget {
const _MyWidget({
required String? imagePath,
}) : _imagePath = imagePath;
final String? _imagePath;
String? get hello { // the getter can be null witch is normal
return _imagePath;
}
// _imagePath can't be change because of final
// set world (String a) {
// _imagePath = a;
// }
int build() {
if (_imagePath == null) {
// I don't see why this can not be promoted like a local variable in the build scope
// I don't see why a member function can't be treated like local variables inside a function scope
return 1;
}
_bar(_imagePath);
}
void _bar(String a){
print(a);
}
} adding at the beginning of your function this line for a null check of your member variable feels wrong: var imagePath= _imagePath; |
@stephane-archer see: class A {
const A(this.v);
final int? v;
}
class B extends A {
B(super.v);
int? v2 = 0;
@override
int? get v => v2;
void foo() {
if (v == null) {
return;
}
v2 = null;
// Now v is null
}
} |
@FMorschel in let me rewrite your code without "syntax sugar" class A {
const A(this.v);
final int? v;
}
class B extends A {
B(super.v);
int? v2 = 0;
void foo() {
if (v2 != null) {
return;
}
v2 = null;
// Now v2 is null <- yes an assignment that can be null after a null check can't promote the member variable but how is this different than a local variable?
}
} I don't see how an alias changes something. I think the compiler can make a difference between |
This is because Say Then in When you pass an instance of Edit@stephane-archer, see this updated version void main() {
final a1 = A(1);
print(a1.foo());
final a2 = B(1);
print(a2.foo());
}
class A {
const A(this.v);
final int? v;
int foo() {
if (v == null) {
return 0;
}
bar();
return v;
}
void bar() {
print('bar');
}
}
class B extends A {
B(super.v);
int? v2 = 0;
@override
int? get v => v2;
@override
void bar() {
v2 = null;
print('bar2');
}
} |
A new comment to say that I've updated the comment above with the repro. Also, about it. This can happen for example if you extend a class from a package and make one of its final fields a getter as the above. Now if the author had made a similar thing to what you asked, it would generate a runtime error. That's what I was talking about, the safest thing to do is to create a local variable with that value and work with it instead. |
@FMorschel is strange that to What led you to make this decision? Right now every member variable has to be put in a local variable because of this and this added flexibility doesn't make sense for me right now. Can you name a typical use case so I can better understand? |
That's because the API signature of a final variable is the getter signature. That's why you can freely switch between a variable and a getter - they have the same signature. |
A So for instances of a class with But for a subclass, that could change. We could even add a setter to that value, it won't have any change (in signature) if being used where the original/super class was expected. But where you know this more specific type you can set the value if you wish to. |
@lrhn When using class A, the fact that that means we have the flexibility to remove this I'm not so sure what the value of the Do you see any obvious drawbacks of putting |
You are asking for a warning/lint/error to extend a property by giving it a setter on this or sub-classes? I think this is a valid request, I can see that making some sense. Maybe an annotation? Than we could " |
@FMorschel what are your thoughts on adding an annotation: promoting would be then possible on member variable |
I'm unsure using any language keyword is a good option. And about the Just be aware that even with this request of discouraging adding a setter specifically (but probably not impossible, because of how OOP works), it would still be possible to override it as a getter of some sort. Maybe having yet another annotation could be then possible to discourage overrides for that getter. CC: @srawlins @bwilkerson |
We could, theoretically, add an annotation that would cause a warning to be produced if a setter is ever introduced for a final field, but we couldn't use that annotation to allow promotion because such a warning isn't binding (it can be ignored or suppressed) and as a result using it to change the semantics of promotion would make the language unsound. Given that it can't be used to change the semantics of promotion, I doubt that it carries enough value to make it worth adding. The same line of reasoning would apply to an annotation to discourage overriding a getter. There has been some discussion in the past of a similar idea encoded as a language feature, which would allow us to make it an error rather than a warning. See dart-lang/language#1518. |
It makes it a breaking change to change a final variable to a getter. People who have written final variables so far have not asked for that, and you would then need a new syntax to write a variable with no setter that is not that kind of final, to not make it a guaranteed final variable that you cannot convert to a getter. Being convertible to a getter becomes opt-in, you'd have to choose to use that new syntax. If you forget, or don't know, you'll be locking yourself into a final variable that you cannot change to a get without breaking other people. Style guides would, rightly, suggest that you make all instance variables the new kind of final, and only use the promotable final variable when you really want the promotability. There might be annotations and lints to ensure that you don't make a variable So, just like being (This is not a new idea, it's been said many times before here and in the language issue tracker. The arguments against it haven't changed. That's why the "stable getters" proposal exists.) |
@bwilkerson just make it an error. I think this proposal is an elegant way of solving the issue. Do you have any problem with it? dart-lang/language#1518 void main() {
final a1 = A(1);
print(a1.foo());
final a2 = B(1); // This line will not compile
print(a2.foo());
}
class A {
// Private constructor
const A._(this.v);
// Factory constructor for controlled instantiation
factory A(int value) {
return A._(value);
}
final int? v;
int foo() {
if (v == null) {
return 0;
}
bar();
return v!;
}
void bar() {
print('bar');
}
}
// Attempting to extend A
class B extends A {
// Error: Cannot call the private constructor of A
B(super.v); // This line will not work because A._() is private
int? v2 = 0;
@override
int? get v => v2;
@override
void bar() {
v2 = null;
print('bar2');
}
}
If we have a private constructor we can promote member variables, do you see any issue with this? |
I'd very much like to introduce support for an interface level commitment to immutability in Dart, that is, something along the lines of dart-lang/language#1518. The main reason is that I consider immutability to be a relevant affordance which may be provided by a property of an object: You can trust this property to remain unchanged. It is then safe to cache it in a local variable and/or promote it, but those things are just examples of the benefits that clients may obtain when they have a guarantee that a property of a given object is immutable. The point is that this property is easier to reason about in client code, which is the reason why it is not just taking something away (mutability) but also offering something in return (predictability). @FMorschel already gave a detailed example to demonstrate that it isn't safe to promote a final instance variable. Here is an example that serves the same purpose. It's shaped in a slightly different way in order to illustrate the core point even more directly: class A {
final String? x;
A(this.x);
void test() {
if (x != null) print(x!.length); // <--- Look here!
}
}
class B extends A {
bool _yieldNull = true;
B(super.x);
String? get x => (_yieldNull = !_yieldNull) ? null : super.x;
}
void main() {
B('Hello').test();
} So the claim is that "we can promote The crucial point is that evaluation of the expression dart-lang/language#1518 allows us to declare that the getter must be stable. You can override or implement it using a |
I think dart-lang/language#1518 is a reasonable solution if we want to allow public fields to be promoted. (It's likely the only reasonable solution, up to syntax differences. Anything that doesn't require an explicit opt-in to enable the promotion is a no-go.) It's also a complication to the class model and a restriction on what subclasses can do, and it's not clear it's worth its own complexity when you can just do That is, it's not clear that introducing complexity in the class and member model with the only benefit being that you can now (guaranteed safely) cache and promote an instance variable, is worth having every class author choose between a stable variable and just a final variable. It's not that "stable properties" an unreasonable feature, or that it doesn't match object oriented modelling as such, it's just that it's a minor increment in power with a (IMO) larger increment in user-facing complexity. There are other things I'd rather spend resources on. |
@stephane-archer wrote:
We could have that feature, no problem. But it won't make its way to the top of the TODO list unless it gets more support. I can recommend voting for it. ;-) |
We can prevent B from existing with a private constructor. Then the immutability is guaranteed and we can promote the member variables. Do you see any issues with this? (Your proposal is elegant and would be useful too.) |
Voting is putting I thumb up? If I could I would put two ✌️ How many vote to do you need? It seems quite popular already. |
Or by adding final class A {...}
It could still be overridden I think* by *See #59855. |
True, we can use privacy and final types to detect that an override cannot exist, and we could also use a notion of non-virtual getters to enforce that an override cannot exist. However, even though those things are sufficient for the purpose of soundness, they don't indicate an active and conscious commitment to stability by the maintainers of this getter. That's a conceptual matter, and a matter of software engineering and modeling. It's visible in the documentation, and clients can rely on it. I think this serves as an important motivation for having stability as an explicitly specified property, rather than as a property that may have arisen by accident (because the developers needed to make something private or final for other reasons). For example, if you rely on finality to get stability then it is a breaking change to remove the modifier |
There are no mechanical rules, but more is better. ;-) |
That's really what #59855 is about. In order for something to be an error it has to be part of the language specification. We won't define errors that are based on an annotation, so there would need to be some other mechanism, such as a new keyword, but that's just a detail. The analyzer can report warnings that the compilers don't, but those warnings aren't part of the language and hence can't change the semantics of the language. |
The approach of #59855 is great, it would be great to have a keyword for that. I'm 300% with this. But the promotion system right now can be improved without changing the language. Promoting member variable that can not be extended is possible to add today without breaking any existing code and adding additional keyword. It about having the analyser helping rather than been on the way by forcing me to introduce local variable while I would never extend that class to start with |
Promotion is part of the language specification. You literally cannot change promotion without changing the language. The analyser cannot do anything about promotion by itself, the compilers decide what the program means, and they only follow the language specification. It's (likely) correct that the language could be changed to promote some instance variables with no new syntax, but likely fewer than you think (it still has to be sound, and without a global analysis). |
Could you clarify why it is bad design to promote member variable in do you mean you prefer dart-lang/language#1518 solution or is it something else? |
If you can promote a final variable of Which is another way of saying that The only place where promoting based on implementation, not signature, is acceptable is inside the same library. It may be possible to do the same thing with non-private instance variables if the class, and all subclasses, are either It should be possible to promote static and top level final variables in the same library too, but you rarely need to test the type of those. It's so restrictive (same library only, all subclasses |
I see your point |
The argument type 'String?' can't be assigned to the parameter type 'String'.
Because
imagePath
isfinal
and is checked for null in the build function, I don't see why I should have a!
or a local variable to avoid this error message.Am I missing something?
The text was updated successfully, but these errors were encountered: