-
Notifications
You must be signed in to change notification settings - Fork 1.7k
[Breaking change request] String.fromEnvironment and int.fromEnvironment const constructors will have a non-null defaultValue default value. #40678
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
Comments
cc @Hixie @matanlurey @dgrove @vsmenon for review and approval. |
This seems like an undesireable change. Could these factory methods be defined as |
The reason to have factory methods in Dart is to be able to switch between generative constructors and factory constructors without breaking clients instantiating the class*. As such, providing a feature (nullable return type) which only works on one of them is not desirable. That is, this change is not made to improve the API, but to make it possible at all in a Null Safe setting. It's not something we can choose not to change. Either we need to add more features to the language (with a questionable cost/value ratio) or change the constructors in some other way, but we can't return The other option we have found is to make If you want to distinguish an absent value from the default value, you still can do so precisely as described above(even if it's cumbersome and not something you can abstract over), or you can use a slightly less precise check like: const String flag = String.fromEnvironment("flag key", defaultValue: "<NOT PRESENT>");
if (flag == "<NOT PRESENT>") { ... assume it wasn't there ... }
const int level = int.fromEnvironment("level key", defaultValue: -999999);
if (level == -999999) { ... assume it wasn't there ... } These tests can be foiled, but in practice that will only happen with malicious intent, and the only effect the user gets is to act as if no value was passed, which the code should be ready to handle anyway. So, there are workarounds. The code linked in the Flutter The code would just need to change the absent-check for I believe that in most cases, an empty value can be treated the same as an absent key without any actual loss of user power because an empty value is already a questionable input. (A boolean declaration should use About allowing nullable return values from factories: Adding return types to constructors in general is a personal wish of mine, but not something we can do in the scope of the NNBD change. That would, if it ever happens, likely be part of a major revamp of constructors in general. Not sure whether it would actually allow you to return *: It may still break classes extending the class, so the abstraction isn't pure. It would be better if we could define generative constructors which could not be used for super-class constructor invocations. |
Ever since we got rid of
That seems like a pretty terrible place to be in terms of API design. We shouldn't need to hope there's no malicious intent for our APIs to work. (What if the environment variable comes from the network, e.g. HTTP_xxx in a CGI context? Then it really could be malicious.)
Why is it unclear? It seems pretty clear that that should be treated as an explicit local engine whose value is the empty string. Sure, it's unlikely to be useful, but there's plenty of strings that are unlikely to be useful. To be honest, it seems a bit weird that we're saying factory methods can't return null any more. I guess in general we want people to switch to real static methods if they have that use case, and the only reason that doesn't work here is that we want to encourage people to use In any case, these constructors are already highly magical (they're const factory constructors with external bodies, which is not a thing a normal Dart program can express!). I recommend we just make them more magical for now, allowing them to continue to return null, until the day we return to environment variables in Dart in general to address them more rationally. |
LGTM in terms of changing this particular API (and making factory methods return non-null). |
One option we have for making migration easier is to add a |
lgtm assuming we can land this without breaking. The
Is this actually true for const constructors? |
Yes. A class can have a const generative constructor, then change it to a const factory constructor which forwards to a subclass const constructor. As long as noone uses it as a superclass constructor, that still works. And if someone uses it as a superclass constructor, you're stuck with it, which is a hole in the entire theory. (I prefer to only expose factory constructors where possible, to prevent people from extending classes not intended for extending). |
Where are we with this? I am fairly strongly opposed to adding an ad hoc "nullable" factory constructor at this point. If we want this feature, I think it should be part of a broader language feature around constructors with explicit return types. If we want to special case the constructors for now, what I might suggest is that we specify that the expression If we take that approach, should we consider having a non-nullable version as well, so that the 99% case that doesn't want to get null from |
cc @johnniwinther @stereotype441 @scheglov for any concerns about the syntactic sugar approach I mentioned above. |
@leafpetersen I don't have concerns about this approach and analyzer. |
Somewhat tangential to this issue, but...
The other reason for factory constructors is that they are a more natural way to create instances of generic classes. Compare: Dictionary<String, int>.fromMap(map); // Factory constructor.
Dictionary.fromMap<String, int>(map); // Generic static method. Back on topic... I'm not crazy about nullable factory constructors. The only motivation I've seen for them is For the kinds of strings and ints used with The syntactic sugar feels a little like digging ourselves further into the weird hole that is |
I agree that "that API is almost pathologically weird". That is why I think we should just leave it as-is until we find a more long-term sane API to deal with environment variables (whatever that is). I have no problem with making all other factory constructors unable to return null. If we cannot make this already not-actually-expressible-in-normal-Dart magical API just that little bit more magical for some reason, then I could live with |
Ok, so there seem to be three proposals on the table:
I'm not super enthusiastic about 1. The main reason is that I actually think this ends up being a worse experience for most users. I just checked our internal code (code search for Either of 2 or 3 seem reasonable to me. Most code already seems to pass Option 2 still feels like the best option to me overall. Option 3 is appealing mostly in that it forces users who are currently not passing a defaultValue to decide what they want to do. Empirically, that's a minority of users though, and of the ones that aren't passing a @Hixie any further thoughts in light of the above? |
The "required defaultValue parameter" for I do think that a default I also don't expect any change to the API in the near future. It's not high enough on our list of priorities because the feature isn't very widely used to begin with. It's still used enough that it must work, and should not suck too much. An API which is a consistent and usable now is better than maintaining a hack for a significant time. |
Ok, I'd like to propose that we move forward with the original proposal from @lrhn with the addition of a |
@Hixie is out this week. Given that his last comment and @leafpetersen 's last comment align, I think we can go forward. lgtm |
Approved. |
Breaking change #40678 mandates new default values for String.fromEnvironment, this CL adjusts the expectations of a test accordingly. Change-Id: Ib8a26005673d07c67f65708fc9c4ae7b17421670 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/139811 Reviewed-by: Johnni Winther <[email protected]> Commit-Queue: Erik Ernst <[email protected]>
The new version 0.16.1 does not use `fromEnvironment` constructors, whereas the old version 0.15.7 does use it, and 0.15.7 uses it in a way which requires updates in order to keep working when the breaking change #40678 is landed. Change-Id: I10cabc2d7799c448f7b42d88e24bb8406fcf0672 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/140604 Reviewed-by: Alexander Thomas <[email protected]> Commit-Queue: Erik Ernst <[email protected]>
Change landed |
Summary
The
String.fromEnvironment
andint.fromEnvironment
const constructors will have a non-null
defaultValue
default value.What is changing:
The
String.fromEnvironment
andint.fromEnvironment
constructors both have adefaultValue
optional parameter which has a default value ofnull
.This is changed to the empty string and the integer zero respectively.
Why is this changing?
These are factory constructors, and in Null Safe code, constructors cannot return
null
.As such, having a default value of
null
will not work. We picked the most neutral value for the types as the new default value.The
bool.fromEnvironment
constructor is unaffected because it already hadfalse
as defaultdefaultValue
.Expected impact
Code which uses either constructor with no specified default value will get a different result if there is no declaration for the requested key.
If code checks for the presence of a key by checking whether
String.fromEnvironment(key)
evaluates tonull
, it will now fail.Code like
const int.fromEnvironment("port") ?? computedPort()
will no longer work. It can't just usecomputerPort()
as default value because the default value for a constant constructor invocation must be constant.The impact can be large for code which uses the current feature, but it is not expected that the feature is used that much.
Checking whether a key is present can be done by something like:
which is not as convenient.
Allowing a factory constructor to return
null
is not a viable choice in Null Safe code.Changing the constructors to be something other than const constructors, but keep them being constant, would require adding such a feature because the language currently doesn't have any.
The text was updated successfully, but these errors were encountered: