Allow setters to declare a return type of `Never`
This may be only useful on some few edge cases, so I am not sure if this is viable or not, but it may not be so hard to do and isn't breaking.
Currently, the specification explicitly states:
It is a compile-time error if a setter declares a return type other than void.
With null-safety and the Never type introduced, there are cases where one might want to change their setter return type to indicate that an error is always thrown.
For instance, UnmodifiableListView.operator []= always throw.
It could be useful to be able to declare the return type of those kinds of setters as Never.
The return type of a setter doesn't matter, because the type isn't used anywhere in the specification. Any returned value is lost, so it's type doesn't matter.
If we allow Never as return type, as a signal that the setter invocation never returns any value, then we'd want the spec to recognize that.
That would mean something like:
- If an assignment expression invokes a setter (it's not an assignment to a local variable), then let R be the return type of the signature of the setter being invoked, and let S be the static type of the assigned expression. If R is a bottom type (any subtype of
Never), then the static type of the assignment expression isNever. Otherwise the static type of the assignment expression is S. - It is be a compile time error if the declared return type of a setter is anything except
voidorNever. A setter declaration with no return type defaults tovoid(even if we can see that the cost definitely throws). An instance setter with no declared return type inherits a return type from any superinterface member signature as normal, and defaults tovoidif there is no superinterface setter. - It's a compile-time error if a concrete setter declaration has a bottom type as return type, and control flow can exit the setter body (reach the end, reach any return statement, or even contain any return statement, but this is just the same rules as for any method body with a bottom return type).
operator []=counts as a setter.
Could work. Not sure if it's worth the work, just to document that a setter throws. For a static setter, it just means that you should never call it, so it might as well not be there. This only makes sense for instance setters, but those also gave to be valid overrides, so any interface having a throwing setter implies that all subtypes must also throw. That again only makes sense for a sub-interface that disables supertype APIs, which isn't great OO design (breaks subtype substitutability). Can be useful occasionally, but not great.
Yes, it would only be useful for instance setters, and only for a few cases. I feel like there are no downsides (except, obviously, for the resources spent to implement it), but maybe the benefits are not worthy enough.
I was thinking operator []= was already a setter (because it already doesn't allow anything but void). Does it have a different special case for the return type?
Hi @mateusfccp
I have a quick idea to share! Currently, setters can only return void, but with null-safety and the Never type, it might be cool to allow setters to return Never in certain cases.
For example, UnmodifiableListView.operator []= always throws, so having a Never return type could clarify intent and enhance type safety.
What do you think?
Cheers, Sowmithri Sriram. sriramsowmithri9807 --> github
I was thinking
operator []=was already a setter
I think it is, just wanted to be certain that it was included since "setter" sometimes means "something declared using set" and sometimes means "something behaving as an assignment". Here we want the latter.
One workaround is to mark such setter with a @Deprecated('do not use'), like:
@Deprecated('The list is immutable, do not use');
@override
void operator[]=(int index);
One workaround is to mark such setter with a
@Deprecated('do not use'), like:@Deprecated('The list is immutable, do not use'); @override void operator[]=(int index);
This is an interesting workaround and may work, although the semantics conveyed are not correct.
I agree that allowing setters to use Never as a return type would enhance clarity in cases like UnmodifiableListView.operator []=. It makes the intent explicit that such setters always throw and aligns well with null-safety principles. This seems like a reasonable and non-breaking improvement."
class UnmodifiableListView<T> {
List<T> _items;
UnmodifiableListView(this._items);
Never operator []=(int index, T value) {
throw UnsupportedError('Cannot modify an unmodifiable list.');
}
}
void main() {
var list = UnmodifiableListView<int>([1, 2, 3]);
// Trying to assign a value will always throw an error
list[0] = 10; // Throws UnsupportedError
}