-
Notifications
You must be signed in to change notification settings - Fork 1.1k
implicit conversion is not implicitly available to types of UncheckedNull #7828
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
I had a look at this and found several fundamental problems with the way we do explicit nulls. val x: Array[String | UncheckedNull] = ???
x.map(_.length) Without -Yexplicit-nulls, the last line compiles to refArrayOps[String](x).map(_.length) With -Yexplicit-nulls the compiler tries this instead: genericArrayOps[String | Null](x).map(_.length) and then it fails since
@inline implicit def refArrayOps[T <: AnyRef](xs: Array[T]): ArrayOps[T] = new ArrayOps(xs)
@inline implicit def genericArrayOps[T](xs: Array[T]): ArrayOps[T] = new ArrayOps(xs) 1st Problem:
|
/cc @olhotak |
Another question: What is the least upper bound of So, maybe it's better to make |
When we started this project, we considered making Since an I would like to get rid of
On the 3rd problem, we have ported the Dotty community build (except stdlib) to explicit nulls and are writing up the experience. That writeup and the diffs would inform this discussion, but at the moment, everyone is away for the holidays so the writeup won't be ready until the new year. |
That's a good observation. The example in the concrete issue is misleading here, since it does imply that the distance in |
One thing I noted.: If we keep refNullArrayOps[T <: AnyRef](xs: Array[T | UncheckedNull]): ArrayOps[T | UncheckedNull] That will fix just a single class of uses, albeit an important one. |
What if we reinterpret references to |
There are two issues mixed together in this example. The return type of It's the outside Even with both of these solutions, the inside |
I think the "proper" way to fix the specific error reported here is to use Java annotations. With annotations, all the
Regarding the more general question of "how unsound" Like Ondrej said, we just finished migrating most community build libraries to explicit nulls. The results can be found in the evaluation chapter of my master thesis (evaluation.pdf), but the gist of it is below: We created multiple "run configurations", where certain explicit nulls features were enabled/disabled: For example, We then ran dotty on every (library, configuration) pair, and counted how many compiler errors were produced. The results are reported below in # errors / 1000 LOC. The idea is to use "# of compilation errors" as a proxy for how much effort it would be to migrate a specific library (there are many caveats to that statement, but it's as good of a proxy as I can think of). By comparing the |
I think this would be an appropriate thing to do, at least for conversions invoked when trying to resolve selections. Without that, the following doesn't work either: val x: String = ...
val y = x.substring(3).length // ok
val y = x.substring(3).stripSuffix(".txt") // doesn't compile How can we possibly explain to a newcomer that the former works but the latter doesn't? This and the original example (including the performance aspect) if, when searching for extension methods on an |
I think we should try that. I.e.
The scheme will also work for arrays. I.e if we see Does anyone have an argument why this cannot be enough? If not I think we should go ahead and try it out. |
IIUC, with that scheme, the following compiles: val len = str.substring(3).length but the following doesn't: val sub = str.substring(3)
val len = sub.length // sub is String | Null and is not a Java-defined symbol |
@sjrd Correct. The question is: Is that acceptable? Or do we need to refine the scheme? A candidate refinement would be:
type UncheckedNull = Null @uncheckedNull and making
If I call
This scheme would handle your example, since the type of |
Compared to the current version of
|
This works if we get a parameterized type directly from Java. But if I call |
@smarter Good point. But maybe we can also always widen |
Seems tricky. If we do that in general, then we lose the UncheckedNull as soon as we wrap a Java value in |
Yes, but that is unavoidable and, I think, also acceptable. |
This is unfortunately not correct: (String | Null) | UncheckedNull = String | Null |
@noti0na1 proposed to not widen nested Java types with |
Do we consider it unacceptable to just give return values from Java methods a non-null type (at the outer level only)? So |
That would be unacceptable because it definitely means that you can receive a value that does not belong to its declared type, and you don't get a chance to "fix" it before you use it. |
If you know that a particular Java method can return null, you can fix it with an ascription: |
Even so, there is this tiny "time" frame in which the result of |
I see. In that case, how about the following rule. For a call
By default, Java-defined calls would get |
If I have some Scala code that does: someJavaInstance.doSideEffects() and |
Could we insert an intrinsic variant of I'd like to explain the deeper reason guiding my suggestions. From our study of porting the community build programs, I drew the conclusion that in programs that use Java libraries, there are so many calls to Java code that trying to model Java soundly requires manual handling (e.g. casts) at many, many calls, even with something like UncheckedNull. Therefore I'm aiming for a design that allows two modes: 1) in code where you don't care much about soundness of Java calls, we don't bother trying to make them sound; this is the status quo before explicit nulls; 2) in other code you care enough about soundness that you're willing to do a lot of work to put in all the checks. (Switching between modes is open for discussion; it could be a compiler flag, language import, scoping construct, etc.) Rather than trying to design an in-between mode that is not sound but also still requires many casts, I would prefer to support both modes well. |
The problem of The trade-off is we have to add the following special rules to A | Null <: A | UncheckedNull
M[A | UncheckedNull] <: M[A | Null]
M[A | Null] <: M[A | UncheckedNull] Another problem mentioned by @olhotak is if it is possible to create a space for users where we remove all
|
To summarize what we want to achieve (correct me if I am wrong):
I'd like to propose this scheme to replace
For example: import scala.language.RemoveUncheckedOrNull
// which enables the following implicit conversion
// defined in compiler
given removeUncheckedOrNull[A, B] as Conversion[A, B]
where A != B && A.stripAllUncheckedOrNull =:= B.stripAllUncheckedOrNull =
_.asInstanceOf[B] I am still reading the source code of |
This has been fixed using unsafe-nulls in #9884. |
minimized code
expectation
Extension methods/implicit conversions should be avaliable to unions of
UncheckedNull
The text was updated successfully, but these errors were encountered: