Skip to content

Commit b48643f

Browse files
committed
remove NonNullTermRef
1 parent bc972b2 commit b48643f

File tree

3 files changed

+27
-252
lines changed

3 files changed

+27
-252
lines changed

compiler/src/dotty/tools/dotc/core/Types.scala

Lines changed: 17 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1924,19 +1924,19 @@ object Types {
19241924
else computeDenot
19251925
}
19261926

1927-
protected def finish(d: Denotation)(implicit ctx: Context): Denotation = {
1928-
if (d.exists)
1929-
// Avoid storing NoDenotations in the cache - we will not be able to recover from
1930-
// them. The situation might arise that a type has NoDenotation in some later
1931-
// phase but a defined denotation earlier (e.g. a TypeRef to an abstract type
1932-
// is undefined after erasure.) We need to be able to do time travel back and
1933-
// forth also in these cases.
1934-
setDenot(d)
1935-
d
1936-
}
1937-
19381927
private def computeDenot(implicit ctx: Context): Denotation = {
19391928

1929+
def finish(d: Denotation) = {
1930+
if (d.exists)
1931+
// Avoid storing NoDenotations in the cache - we will not be able to recover from
1932+
// them. The situation might arise that a type has NoDenotation in some later
1933+
// phase but a defined denotation earlier (e.g. a TypeRef to an abstract type
1934+
// is undefined after erasure.) We need to be able to do time travel back and
1935+
// forth also in these cases.
1936+
setDenot(d)
1937+
d
1938+
}
1939+
19401940
def fromDesignator = designator match {
19411941
case name: Name =>
19421942
val sym = lastSymbol
@@ -2235,7 +2235,7 @@ object Types {
22352235

22362236
/** A reference like this one, but with the given symbol, if it exists */
22372237
final def withSym(sym: Symbol)(implicit ctx: Context): ThisType =
2238-
if ((designator ne sym) && sym.exists) newLikeThis(prefix, sym).asInstanceOf[ThisType]
2238+
if ((designator ne sym) && sym.exists) NamedType(prefix, sym).asInstanceOf[ThisType]
22392239
else this
22402240

22412241
/** A reference like this one, but with the given denotation, if it exists.
@@ -2282,10 +2282,10 @@ object Types {
22822282
d = disambiguate(d,
22832283
if (lastSymbol.signature == Signature.NotAMethod) Signature.NotAMethod
22842284
else lastSymbol.asSeenFrom(prefix).signature)
2285-
newLikeThis(prefix, name, d)
2285+
NamedType(prefix, name, d)
22862286
}
22872287
if (prefix eq this.prefix) this
2288-
else if (lastDenotation == null) newLikeThis(prefix, designator)
2288+
else if (lastDenotation == null) NamedType(prefix, designator)
22892289
else designator match {
22902290
case sym: Symbol =>
22912291
if (infoDependsOnPrefix(sym, prefix) && !prefix.isArgPrefixOf(sym)) {
@@ -2294,10 +2294,10 @@ object Types {
22942294
// A false override happens if we rebind an inner class to another type with the same name
22952295
// in an outer subclass. This is wrong, since classes do not override. We need to
22962296
// return a type with the existing class info as seen from the new prefix instead.
2297-
if (falseOverride) newLikeThis(prefix, sym.name, denot.asSeenFrom(prefix))
2297+
if (falseOverride) NamedType(prefix, sym.name, denot.asSeenFrom(prefix))
22982298
else candidate
22992299
}
2300-
else newLikeThis(prefix, sym)
2300+
else NamedType(prefix, sym)
23012301
case name: Name => reload()
23022302
}
23032303
}
@@ -2320,12 +2320,6 @@ object Types {
23202320
}
23212321

23222322
override def eql(that: Type): Boolean = this eq that // safe because named types are hash-consed separately
2323-
2324-
protected def newLikeThis(prefix: Type, designator: Designator)(implicit ctx: Context): NamedType =
2325-
NamedType(prefix, designator)
2326-
2327-
protected def newLikeThis(prefix: Type, designator: Name, denot: Denotation)(implicit ctx: Context): NamedType =
2328-
NamedType(prefix, designator, denot)
23292323
}
23302324

23312325
/** A reference to an implicit definition. This can be either a TermRef or a
@@ -2342,7 +2336,7 @@ object Types {
23422336
private var myDesignator: Designator)
23432337
extends NamedType with SingletonType with ImplicitRef {
23442338

2345-
type ThisType >: this.type <: TermRef
2339+
type ThisType = TermRef
23462340
type ThisName = TermName
23472341

23482342
override def designator: Designator = myDesignator
@@ -2397,34 +2391,6 @@ object Types {
23972391
myHash = hc
23982392
}
23992393

2400-
/** A `TermRef` that, through flow-sensitive type inference, we know is non-null.
2401-
* Accordingly, the `info` in its denotation won't be of the form `T|Null`.
2402-
*
2403-
* This class is cached differently from regular `TermRef`s. Regular `TermRef`s use the
2404-
* `uniqueNameTypes` map in the context, while these non-null `TermRef`s use
2405-
* the generic `uniques` map. This is so that regular `TermRef`s can continue to use
2406-
* a "fast path", since non-null `TermRef`s are not very common.
2407-
*/
2408-
final class NonNullTermRef(prefix: Type, designator: Designator) extends TermRef(prefix, designator) {
2409-
override type ThisType = NonNullTermRef
2410-
2411-
override protected def finish(d: Denotation)(implicit ctx: Context): Denotation =
2412-
// If the denotation is computed for the first time, or if it's ever updated, make sure
2413-
// that the `info` is non-null.
2414-
super.finish(d.mapInfo(_.stripNull))
2415-
2416-
override protected def newLikeThis(prefix: Type, designator: Designator)(implicit ctx: Context): NamedType =
2417-
NonNullTermRef(prefix, designator)
2418-
2419-
override protected def newLikeThis(prefix: Type, designator: Name, denot: Denotation)(implicit ctx: Context): NamedType =
2420-
NonNullTermRef(prefix, designator.asTermName, denot)
2421-
2422-
override def eql(that: Type): Boolean = that match {
2423-
case that: NonNullTermRef => (this.prefix eq that.prefix) && (this.designator eq that.designator)
2424-
case _ => false
2425-
}
2426-
}
2427-
24282394
final class CachedTypeRef(prefix: Type, designator: Designator, hc: Int) extends TypeRef(prefix, designator) {
24292395
assert((prefix ne NoPrefix) || designator.isInstanceOf[Symbol])
24302396
myHash = hc
@@ -2456,11 +2422,9 @@ object Types {
24562422
case sym: Symbol => sym.isType
24572423
case name: Name => name.isTypeName
24582424
}
2459-
24602425
def apply(prefix: Type, designator: Designator)(implicit ctx: Context): NamedType =
24612426
if (isType(designator)) TypeRef.apply(prefix, designator)
24622427
else TermRef.apply(prefix, designator)
2463-
24642428
def apply(prefix: Type, designator: Name, denot: Denotation)(implicit ctx: Context): NamedType =
24652429
if (designator.isTermName) TermRef.apply(prefix, designator.asTermName, denot)
24662430
else TypeRef.apply(prefix, designator.asTypeName, denot)
@@ -2479,23 +2443,6 @@ object Types {
24792443
apply(prefix, designatorFor(prefix, name, denot)).withDenot(denot)
24802444
}
24812445

2482-
object NonNullTermRef {
2483-
// Notice these TermRefs are cached in a different map than the one used for
2484-
// regular TermRefs. The non-null TermRefs use the "slow" map, since they're less common.
2485-
// If we used the same map, then we'd end up replacing a regular TermRef by a non-null
2486-
// one with a different denotation.
2487-
2488-
/** Create a non-null term ref with the given designator. */
2489-
def apply(prefix: Type, desig: Designator)(implicit ctx: Context): NonNullTermRef =
2490-
unique(new NonNullTermRef(prefix, desig))
2491-
2492-
/** Create a non-null term ref with given initial denotation. The name of the reference is taken
2493-
* from the denotation's symbol if the latter exists, or else it is the given name.
2494-
*/
2495-
def apply(prefix: Type, name: TermName, denot: Denotation)(implicit ctx: Context): NonNullTermRef =
2496-
apply(prefix, designatorFor(prefix, name, denot)).withDenot(denot)
2497-
}
2498-
24992446
object TypeRef {
25002447

25012448
/** Create a type ref with given prefix and name */

docs/docs/internals/explicit-nulls.md

Lines changed: 1 addition & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ The implementation of the feature in dotty can be conceptually divided in severa
1212
2. a "translation layer" for Java interop that exposes the nullability in Java APIs
1313
3. a "magic" `JavaNull` type (an alias for `Null`) that is recognized by the compiler and
1414
allows unsound member selections (trading soundness for usability)
15-
4. a module for "flow typing", so we can work more naturally with nullable values
1615

1716
Feature Flag
1817
------------
@@ -29,7 +28,7 @@ We change the type hierarchy so that `Null` is only a subtype of `Any` by:
2928

3029
Java Interop
3130
------------
32-
TODO(abeln): add support for recognizing nullability annotations a la
31+
TODO(abeln): add support for recognizing nullability annotations a la
3332
https://kotlinlang.org/docs/reference/java-interop.html#nullability-annotations
3433

3534
The problem we're trying to solve here is: if we see a Java method `String foo(String)`,
@@ -99,88 +98,3 @@ are methods of the `Type` class, so call them with `this` as a receiver:
9998
we'll get `Array[String|Null]` (only the outermost nullable union was removed).
10099
- `stripAllJavaNull` is like `stripNull` but removes _all_ nullable unions in the type (and only works
101100
for `JavaNull`). This is needed when we want to "revert" the Java nullification function.
102-
103-
Flow Typing
104-
-----------
105-
Flow typing is needed so we can work with nullable unions in a more natural way.
106-
The following is a common idiom that should work without additional casts:
107-
```scala
108-
val x: String|Null = ???
109-
if (x != null && x.length < 10)
110-
```
111-
This is implemented as a "must be null in the current scope" analysis on stable paths:
112-
- we add additional state to the `Context` in `Contexts.scala`.
113-
Specifically, we add a set of `FlowFacts` (right now just a set of `TermRef`s), which
114-
are the paths known to be non-nullable in the current scope.
115-
- the bulk of the flow typing logic lives in a new `FlowTyper.scala` file.
116-
117-
There are four entry points to `FlowTyper`:
118-
1. `inferFromCond(cond: Tree): Inferred`: given a tree representing a condition such as
119-
`x != null && x.length < 10`, return the `Inferred` facts.
120-
121-
In turn, `Inferred` is defined as `case class Inferred(ifTrue: FlowFacts, ifFalse: FlowFacts)`.
122-
That is, `Inferred` contains the paths that _must_ be non-null if the condition is true and,
123-
separately, the paths that must be non-null if the condition is false.
124-
125-
e.g. for `x != null` we'd get `Inferred({x}, {})`, but only if `x` is stable.
126-
However, if we had `x == null` we'd get `Inferred({}, {x})`.
127-
128-
2. `inferWithinCond(cond: Tree): FlowFacts`: given a condition of the form `lhs && rhs` or
129-
`lhs || rhs`, calculate the paths that must be non-null for the rhs to execute (given
130-
that these operations) are short-circuiting.
131-
132-
3. `inferWithinBlock(stat: Tree): FlowFacts`: if `stat` is a statement with a block, calculate
133-
which paths must be non-null when the statement that _follows_ `stat` in the block executes.
134-
This is so we can handle things like
135-
```scala
136-
val x: String|Null = ???
137-
if (x == null) return
138-
val y = x.length
139-
```
140-
Here, `inferWithinBlock(if (x == null) return)` gives back `{x}`, because we can tell that
141-
the next statement will execute only if `x` is non-null.
142-
143-
4. `refineType(tpe: Type): Type`: given a type, refine it if possible using flow-sensitive type
144-
information. This uses a `NonNullTermRef` (see below).
145-
146-
- Each of the public APIs in `FlowTyper` is used to do flow typing in a different scenario
147-
(but all the use sites of `FlowTyper` are in `Typer.scala`):
148-
* `refineType` is used in `typedIdent` and `typedSelect`
149-
* `inferFromCond` is used for typing if statements
150-
* `inferWithinCond` is used when typing "applications" (which is how "&&" and "||" are encoded)
151-
* `inferWithinBlock` is used when typing blocks
152-
153-
For example, to do FlowTyping on if expressions:
154-
* we type the condition
155-
* we give the typed condition to the FlowTyper and obtain a pair of sets of paths `(ifTrue, ifFalse)`.
156-
We type the `then` branch with the `ifTrue` facts, and the else branch with the `ifFalse` facts.
157-
* profit
158-
159-
Flow typing also introduces two new abstractions: `NonNullTermRef` and `ValDefInBlockCompleter`.
160-
161-
#### NonNullTermRef
162-
This is a new type of `TermRef` (path-dependent type) that, whenever its denotation is updated, makes sure
163-
that the underlying widened type is non-null. It's defined in `Types.scala`. A `NonNullTermRef` is identified by `computeDenot` whenever the denotation is updated, and then we call `stripNull` on the widened type.
164-
165-
To use the flow-typing information, whenever we see a path that we know must be non-null (in `typedIdent` or
166-
`typedSelect`), we replace its `TermRef` by a `NonNullTermRef`.
167-
168-
#### ValDefInBlockCompleter
169-
This a new type of completer defined in `Namer.scala` that completes itself using the completion context, asopposed to the creation context.
170-
171-
The problem we're trying to solve here is the following:
172-
```scala
173-
val x: String|Null = ???
174-
if (x == null) return
175-
val y = x.length
176-
```
177-
The block is usually typed as follows:
178-
1. first, we scan the block to create symbols for the new definitions (`val x`, `val y`)
179-
2. then, we type statement by statement
180-
3. the completers for the symbols created in 1. are _all_ invoked in step 2. However,
181-
regular completers use the _creation_ context, so that means that `val y` is completed
182-
with a context that doesn't contain the new flow fact "x != null".
183-
184-
To fix this, whenever we're inside a block and we create completers for `val`s, we use a
185-
`ValDefInBlockCompleter` instead of a regular completer. This new completer uses the completion context,
186-
which is aware of the new flow fact "x != null".

0 commit comments

Comments
 (0)