Skip to content

Null <:< "foo" and other problems with Null subtyping (also in -Yexplicit-nulls) #17467

Closed
@sjrd

Description

@sjrd

Compiler version

3.3.0-RC5

Problem at a glance

Null is considered to be a subtype of anything that widenDealiases to a nullable type.

Spec context

The spec actually says that Null <:< x.type if the underlying type of x is a nullable. But that is supposed to only apply to singleton types of the form path.type. Currently in dotty, we even have absurd things like Null <:< "foo" because "foo" widens to "String" which is nullable.

See https://scala-lang.org/files/archive/spec/2.13/03-types.html#singleton-types

Minimized code

REPL session without -Yexplicit-nulls

$ cs launch scala:3.3.0-RC5
Welcome to Scala 3.3.0-RC5 (1.8.0_362, Java OpenJDK 64-Bit Server VM).
Type in expressions for evaluation. Or try :help.
                                                                                                    
scala> val x: "foo" = null
val x: "foo" = null
                                                                                                    
scala> val y: 5 = null // Type error, as expected
-- [E007] Type Mismatch Error: -------------------------------------------------
1 |val y: 5 = null // Type error, as expected
  |           ^^^^
  |Found:    Null
  |Required: (5 : Int)
  |Note that implicit conversions were not tried because the result of an implicit conversion
  |must be more specific than (5 : Int)
  |
  | longer explanation available when compiling with `-explain`
1 error found
                                                                                                    
scala> val a: "foo" = "foo"
val a: "foo" = foo
                                                                                                    
scala> val b: a.type = null
val b: a.type = null
                                                                                                    
scala> val c: Any = new AnyRef
val c: Any = java.lang.Object@78d73b1b
                                                                                                    
scala> val d: c.type = null
val d: c.type = null
                                                                                                    
scala> val e: AnyVal = 5
val e: AnyVal = 5
                                                                                                    
scala> val f: e.type = null // Type error as expected
-- [E007] Type Mismatch Error: -------------------------------------------------
1 |val f: e.type = null // Type error as expected
  |                ^^^^
  |Found:    Null
  |Required: (e : AnyVal)
  |Note that implicit conversions were not tried because the result of an implicit conversion
  |must be more specific than (e : AnyVal)
  |
  | longer explanation available when compiling with `-explain`
1 error found
                                                                                                    
scala> summon[Null <:< "foo"]
val res0: Null =:= Null = generalized constraint
                                                                                                    
scala> summon[Null <:< c.type]
val res1: Null =:= Null = generalized constraint

scala> class Foo { def bar(): this.type = null }
// defined class Foo

Even under -Yexplicit-nulls, we have issues:

$ cs launch scala:3.3.0-RC5 -- -Yexplicit-nulls
Welcome to Scala 3.3.0-RC5 (1.8.0_362, Java OpenJDK 64-Bit Server VM).
Type in expressions for evaluation. Or try :help.
                                                                                                    
scala> val x: "foo" = null // hey, progress!
-- [E007] Type Mismatch Error: -------------------------------------------------
1 |val x: "foo" = null // hey, progress!
  |               ^^^^
  |Found:    Null
  |Required: ("foo" : String)
  |Note that implicit conversions were not tried because the result of an implicit conversion
  |must be more specific than ("foo" : String)
  |
  | longer explanation available when compiling with `-explain`
1 error found
                                                                                                    
scala> val x: Any = "foo"
val x: Any = foo
                                                                                                    
scala> val y: x.type = null // oops, still broken
val y: x.type = null
                                                                                                    
scala> val a: String | Null = "foo"
val a: String | Null = foo
                                                                                                    
scala> val b: a.type = null // this is broken too
val b: a.type = null
                                                                                                    
scala> summon[Null <:< a.type]
val res0: Null =:= Null = generalized constraint
                                                                                                    
scala> type Foo = a.type
// defined alias type Foo = a.type
                                                                                                    
scala> summon[Null <:< Foo]
val res1: Null =:= Null = generalized constraint

Expectation

Not having Null <:< T for arbitrary Ts that widenDealias to a nullable type. Only allow it for singleton types (in dotc terminology, TermRefs) whose direct underlying type is nullable, as the spec says.

Further, under -Yexplicit-nulls, IMO we should take the opportunity to tighten the spec to remove the weird clause about the underlying type of singleton types.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions