Skip to content

Commit b49aca9

Browse files
committed
add null check for paths
1 parent d12a9f1 commit b49aca9

File tree

5 files changed

+81
-50
lines changed

5 files changed

+81
-50
lines changed

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,10 @@ class Definitions {
320320

321321
/** Methods in Object and Any that do not have a side effect */
322322
@tu lazy val pureMethods: List[TermSymbol] = List(Any_==, Any_!=, Any_equals, Any_hashCode,
323-
Any_toString, Any_##, Any_getClass, Any_isInstanceOf, Any_typeTest, Object_eq, Object_ne)
323+
Any_toString, Any_##, Any_getClass, Any_isInstanceOf, Any_typeTest, Object_eq, Object_ne,
324+
// Even through Compiletime_notNull is not pure (contains asInstanceOf), as it is removed later,
325+
// we can consider it as pure.
326+
Compiletime_notNull.asInstanceOf[TermSymbol])
324327

325328
@tu lazy val AnyKindClass: ClassSymbol = {
326329
val cls = ctx.newCompleteClassSymbol(ScalaPackageClass, tpnme.AnyKind, AbstractFinal | Permanent, Nil)

compiler/src/dotty/tools/dotc/typer/ConstFold.scala

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import Names._
1212
import StdNames._
1313
import Contexts._
1414
import Nullables.{CompareNull, TrackedRef}
15+
import NullOpsDecorator._
1516

1617
object ConstFold {
1718

@@ -20,10 +21,16 @@ object ConstFold {
2021
/** If tree is a constant operation, replace with result. */
2122
def apply[T <: Tree](tree: T)(implicit ctx: Context): T = finish(tree) {
2223
tree match {
23-
case CompareNull(TrackedRef(ref), testEqual)
24-
if ctx.settings.YexplicitNulls.value && ctx.notNullInfos.impliesNotNull(ref) =>
25-
// TODO maybe drop once we have general Nullability?
26-
Constant(!testEqual)
24+
// TODO: fix it later
25+
// case CompareNull(operand, testEqual)
26+
// if ctx.explicitNulls && !operand.typeOpt.isNullableUnion =>
27+
// println(tree.show)
28+
// Constant(!testEqual)
29+
// case CompareNull(TrackedRef(ref), testEqual)
30+
// if ctx.settings.YexplicitNulls.value && ctx.notNullInfos.impliesNotNull(ref) =>
31+
// //throw new NullPointerException()
32+
// // TODO maybe drop once we have general Nullability?
33+
// Constant(!testEqual)
2734
case Apply(Select(xt, op), yt :: Nil) =>
2835
xt.tpe.widenTermRefExpr.normalized match
2936
case ConstantType(x) =>

compiler/src/dotty/tools/dotc/typer/Nullables.scala

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -95,26 +95,36 @@ object Nullables with
9595

9696
/** An extractor for null-trackable references */
9797
object TrackedRef
98-
def unapply(tree: Tree)(given Context): Option[TermRef] = tree.typeOpt match
99-
case ref: TermRef if isTracked(ref) => Some(ref)
100-
case _ => None
98+
def unapply(tree: Tree)(given Context): Option[TermRef] = isTracked(tree)
10199
end TrackedRef
102100

103101
/** Is given reference tracked for nullability?
104102
* This is the case if the reference is a path to an immutable val, or if it refers
105103
* to a local mutable variable where all assignments to the variable are _reachable_
106104
* (in the sense of how it is defined in assignmentSpans).
107105
*/
108-
def isTracked(ref: TermRef)(given Context) =
109-
ref.isStable
106+
def isTracked(tree: Tree)(given Context): Option[TermRef] = tree.typeOpt match
107+
case ref: TermRef
108+
if ref.isStable
109+
|| isTrackedNotNull(tree)
110110
|| { val sym = ref.symbol
111-
sym.is(Mutable)
112-
&& sym.owner.isTerm
113-
&& sym.owner.enclosingMethod == curCtx.owner.enclosingMethod
114-
&& sym.span.exists
115-
&& curCtx.compilationUnit != null // could be null under -Ytest-pickler
116-
&& curCtx.compilationUnit.assignmentSpans.contains(sym.span.start)
117-
}
111+
sym.is(Mutable)
112+
&& sym.owner.isTerm
113+
&& sym.owner.enclosingMethod == curCtx.owner.enclosingMethod
114+
&& sym.span.exists
115+
&& curCtx.compilationUnit != null // could be null under -Ytest-pickler
116+
&& curCtx.compilationUnit.assignmentSpans.contains(sym.span.start)
117+
} =>
118+
Some(ref)
119+
case _ => None
120+
121+
def isTrackedNotNull(tree: Tree)(given Context) = tree match
122+
case Select(Apply(TypeApply(f: Ident, _), x :: Nil), _) =>
123+
f.symbol == defn.Compiletime_notNull
124+
&& tree.symbol.isStableMember
125+
&& isTracked(x).isDefined
126+
case _ =>
127+
false
118128

119129
/** The nullability context to be used after a case that matches pattern `pat`.
120130
* If `pat` is `null`, this will assert that the selector `sel` is not null afterwards.

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 37 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,16 @@ class Typer extends Namer
348348
findRefRecur(NoType, BindingPrec.NothingBound, NoContext)
349349
}
350350

351+
def toNotNullTermRef(tree: Tree, pt: Type)(implicit ctx: Context): Tree = tree.tpe match
352+
case tp @ OrNull(tpnn) : TermRef
353+
if pt != AssignProto && // Ensure it is not the lhs of Assign
354+
ctx.notNullInfos.impliesNotNull(tp) =>
355+
Apply(
356+
TypeApply(Ident(defn.Compiletime_notNull.namedType), TypeTree(tpnn) :: Nil),
357+
tree :: Nil)
358+
case _ =>
359+
tree
360+
351361
/** Attribute an identifier consisting of a simple name or wildcard
352362
*
353363
* @param tree The tree representing the identifier.
@@ -418,18 +428,7 @@ class Typer extends Namer
418428
tree.withType(ownType)
419429
}
420430

421-
val tree2 = ownType match {
422-
case ot: TermRef
423-
if ctx.explicitNulls &&
424-
pt != AssignProto && // Ensure it is not the lhs of Assign
425-
ctx.notNullInfos.impliesNotNull(ot) =>
426-
Apply(
427-
TypeApply(Ident(defn.Compiletime_notNull.namedType), TypeTree(ownType.stripNull) :: Nil),
428-
tree1 :: Nil)
429-
case _ =>
430-
tree1
431-
}
432-
431+
val tree2 = toNotNullTermRef(tree1, pt)
433432

434433
checkStableIdentPattern(tree2, pt)
435434
}
@@ -456,8 +455,11 @@ class Typer extends Namer
456455
case qual =>
457456
if (tree.name.isTypeName) checkStable(qual.tpe, qual.sourcePos)
458457
val select = assignType(cpy.Select(tree)(qual, tree.name), qual)
459-
if (select.tpe ne TryDynamicCallType) ConstFold(checkStableIdentPattern(select, pt))
460-
else if (pt.isInstanceOf[FunOrPolyProto] || pt == AssignProto) select
458+
459+
val select1 = toNotNullTermRef(select, pt)
460+
461+
if (select1.tpe ne TryDynamicCallType) ConstFold(checkStableIdentPattern(select1, pt))
462+
else if (pt.isInstanceOf[FunOrPolyProto] || pt == AssignProto) select1
461463
else typedDynamicSelect(tree, Nil, pt)
462464
}
463465

@@ -2220,27 +2222,31 @@ class Typer extends Namer
22202222
case Some(xtree) =>
22212223
traverse(xtree :: rest)
22222224
case none =>
2223-
val defCtx = mdef match
2225+
def defCtx = ctx.withNotNullInfos(initialNotNullInfos)
2226+
val newCtx = if (ctx.owner.isTerm) {
22242227
// Keep preceding not null facts in the current context only if `mdef`
22252228
// cannot be executed out-of-sequence.
2226-
case _: ValDef if !mdef.mods.is(Lazy) && ctx.owner.isTerm =>
2227-
ctx // all preceding statements will have been executed in this case
2228-
case _ =>
2229-
ctx.withNotNullInfos(initialNotNullInfos)
2230-
// We have to check the Completer of symbol befor typedValDef,
2231-
// otherwise the symbol is already completed using creation context.
2232-
mdef.getAttachment(SymOfTree).map(s => (s, s.infoOrCompleter)) match {
2233-
case Some((sym, completer: Namer#Completer))
2234-
if completer.creationContext.notNullInfos ne defCtx.notNullInfos =>
2235-
// The RHS of a val def should know about not null facts established
2236-
// in preceding statements (unless the ValDef is completed ahead of time,
2237-
// then it is impossible).
2238-
sym.info = Completer(completer.original)(
2239-
given completer.creationContext.withNotNullInfos(defCtx.notNullInfos))
2240-
case _ =>
2229+
// We have to check the Completer of symbol befor typedValDef,
2230+
// otherwise the symbol is already completed using creation context.
2231+
mdef.getAttachment(SymOfTree).map(s => (s, s.infoOrCompleter)) match {
2232+
case Some((sym, completer: Namer#Completer)) =>
2233+
if (completer.creationContext.notNullInfos ne ctx.notNullInfos)
2234+
// The RHS of a val def should know about not null facts established
2235+
// in preceding statements (unless the DefTree is completed ahead of time,
2236+
// then it is impossible).
2237+
sym.info = Completer(completer.original)(
2238+
given completer.creationContext.withNotNullInfos(ctx.notNullInfos))
2239+
ctx // all preceding statements will have been executed in this case
2240+
case _ =>
2241+
// If it has been completed, then it must be because there is a forward reference
2242+
// to the definition in the program. Hence, we don't Keep preceding not null facts
2243+
// in the current context.
2244+
defCtx
2245+
}
22412246
}
2247+
else defCtx
22422248

2243-
typed(mdef)(given defCtx) match {
2249+
typed(mdef)(given newCtx) match {
22442250
case mdef1: DefDef if !Inliner.bodyToInline(mdef1.symbol).isEmpty =>
22452251
buf += inlineExpansion(mdef1)
22462252
// replace body with expansion, because it will be used as inlined body

library/src/scala/compiletime/package.scala

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,15 @@ package object compiletime {
6464
*/
6565
type S[N <: Int] <: Int
6666

67-
/** Strip the Null type from x
67+
/** Strip the Null type from `x`
6868
*
69+
* ```
6970
* val x: String|Null = ???
7071
* val _:String = $notNull[String](x)
72+
* ```
73+
*
74+
* Since `$notNull` is erased later, if `x` is a stable path,
75+
* `$notNull(x)` is also a stable path.
7176
*/
72-
inline def $notNull[A](x: A | Null): x.type & A = x.asInstanceOf
77+
def $notNull[A](x: A | Null): x.type & A = x.asInstanceOf
7378
}

0 commit comments

Comments
 (0)