Skip to content
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

cant define nn in usercode with -Yexplicit-nulls #7882

Closed
bishabosha opened this issue Dec 31, 2019 · 5 comments
Closed

cant define nn in usercode with -Yexplicit-nulls #7882

bishabosha opened this issue Dec 31, 2019 · 5 comments

Comments

@bishabosha
Copy link
Member

bishabosha commented Dec 31, 2019

minimized code

compile with -Yexplicit-nulls

def nonnull[T](x: T|Null): x.type & T =
    if (x == null) throw new NullPointerException("tried to cast away nullability, but value is null")
    else x.asInstanceOf[x.type & T]

errors with

-- Error: test.scala:3:24 ------------------------------------------------------
3 |    else x.asInstanceOf[x.type & T]
  |                        ^^^^^^
  |                        (x : T | Null) & T is not stable
1 error found

this makes it hard to define other APIs in user code that eliminate Null, such as Option.apply

@LPTK
Copy link
Contributor

LPTK commented Dec 31, 2019

Looks like the type is considered not stable because it's not normalized enough after flow typing. As a workaround, you can work around flow typing and write:

  def nonnull[T](x: T | Null): x.type & T =
     val isNull = x == null
     if (isNull) throw new NullPointerException("tried to cast away nullability, but value is null")
     else x.asInstanceOf[x.type & T]

@bishabosha
Copy link
Member Author

nice

@noti0na1
Copy link
Member

noti0na1 commented Jan 1, 2020

Actually, with flow typing, you don't need asInstanceOf in your code.

def nonnull[T](x: T|Null): x.type & T =
    if (x == null) throw new NullPointerException("tried to cast away nullability, but value is null")
    else x

The problem is from the isStable function in Types.scala

      /** Does this type denote a stable reference (i.e. singleton type)?
      *
      * Like in isStableMember, "stability" means idempotence.
      * Rationale: If an expression has a stable type, the expression must be idempotent, so stable types
      * must be singleton types of stable expressions. */
    final def isStable(implicit ctx: Context): Boolean = stripTypeVar match {
      case tp: TermRef => tp.symbol.isStableMember && tp.prefix.isStable || tp.info.isStable
      case _: SingletonType | NoPrefix => true
      case tp: RefinedOrRecType => tp.parent.isStable
      case tp: ExprType => tp.resultType.isStable
      case tp: AnnotatedType => tp.parent.isStable
      case tp: AndType =>
        tp.tp1.isStable && (realizability(tp.tp2) eq Realizable) ||
        tp.tp2.isStable && (realizability(tp.tp1) eq Realizable)
      case _ => false
    }

I add the AndType case in the original PR.

In this case, T is a type paramter, its realizability is unknown.

We had some discussion at #7546 (comment)

@LPTK
Copy link
Contributor

LPTK commented Jan 1, 2020

You are right.

The real problem is the way flow-typing adapts variable references x to inline casts of the form x.$asInstanceOf$[(x : T | Null) & T], which prevents them from being used in paths.

The underlying problem is that Dotty lacks proper reasoning capabilities for singleton types. I have opened an issue about this problem here: #7885

@odersky
Copy link
Contributor

odersky commented Sep 26, 2022

Let's keep discussion about this in #7885

@odersky odersky closed this as completed Sep 26, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants