Skip to content

Commit 0a54fe6

Browse files
authored
Merge pull request scala#26 from noti0na1/dotty-explicit-nulls
* ValDefInBlockCompleter is replaced by general Completer * Let flow typing work for all definitions (DefDef and ValDef) in methods * Disable flow typing when forward references exist * Modify tests
2 parents 4fd2085 + a56557f commit 0a54fe6

File tree

4 files changed

+147
-107
lines changed

4 files changed

+147
-107
lines changed

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

Lines changed: 23 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -376,17 +376,6 @@ class Namer { typer: Typer =>
376376
case tree: TypeDef =>
377377
if (flags.is(Opaque) && ctx.owner.isClass) ctx.owner.setFlag(Opaque)
378378
new TypeDefCompleter(tree)(cctx)
379-
case tree: ValDef if ctx.explicitNulls && ctx.owner.is(Method) && !tree.mods.isOneOf(Lazy | Implicit) =>
380-
// Use a completer that completes itself with the completion text, so
381-
// we can use flow typing for `ValDef`s.
382-
// Don't use this special kind of completer for `lazy` or `implicit` symbols,
383-
// because those could be completed through a forward reference:
384-
// e.g.
385-
// val x: String|Null = ???
386-
// y /* y is completed through a forward reference! */
387-
// if (x == null) throw new NullPointerException()
388-
// lazy val y: Int = x.length
389-
new ValDefInBlockCompleter(tree)(cctx)
390379
case _ =>
391380
new Completer(tree)(cctx)
392381
}
@@ -767,23 +756,22 @@ class Namer { typer: Typer =>
767756

768757
protected def localContext(owner: Symbol): FreshContext = ctx.fresh.setOwner(owner).setTree(original)
769758

770-
/** The context in which the completer should be typed: usually it's the creation context,
771-
* but could also be the completion context.
772-
*/
773-
protected def typingContext(owner: Symbol, completionCtx: Context): FreshContext = localContext(owner)
774-
775759
/** The context with which this completer was created */
776760
def creationContext: Context = ctx
777761
ctx.typerState.markShared()
778762

779-
protected def typeSig(sym: Symbol, completionCtx: Context): Type = original match {
763+
protected def typeSig(sym: Symbol, ctx: Context): Type = original match {
780764
case original: ValDef =>
781765
if (sym.is(Module)) moduleValSig(sym)
782-
else valOrDefDefSig(original, sym, Nil, Nil, identity)(typingContext(sym, completionCtx).setNewScope)
766+
else {
767+
val withNewScope = ctx.fresh.setOwner(sym).setTree(original).setNewScope
768+
valOrDefDefSig(original, sym, Nil, Nil, identity)(withNewScope)
769+
}
783770
case original: DefDef =>
784771
val typer1 = ctx.typer.newLikeThis
785772
nestedTyper(sym) = typer1
786-
typer1.defDefSig(original, sym)(localContext(sym).setTyper(typer1))
773+
val withTyper = ctx.fresh.setOwner(sym).setTree(original).setTyper(typer1)
774+
typer1.defDefSig(original, sym)(withTyper)
787775
case imp: Import =>
788776
try {
789777
val expr1 = typedAheadExpr(imp.expr, AnySelectionProto)
@@ -810,7 +798,8 @@ class Namer { typer: Typer =>
810798
denot.info = UnspecifiedErrorType
811799
}
812800
else {
813-
completeInContext(denot, ctx)
801+
// the default behaviour is to complete using creation context
802+
completeInContext(denot, this.ctx)
814803
if (denot.isCompleted) registerIfChild(denot)
815804
}
816805
}
@@ -893,49 +882,24 @@ class Namer { typer: Typer =>
893882
}
894883
}
895884

896-
/** Intentionally left without `implicit ctx` parameter. We need
897-
* to pick up the context at the point where the completer was created.
885+
/** Intentionally left without `implicit ctx` parameter.
886+
* We use the given ctx to complete this Completer.
898887
*
899-
* If -Yexplicit-nulls, is enabled, we sometimes use the completion context.
900-
* See `ValDefInBlockCompleter`.
888+
* Normally, the creation context is passed, as passed in the complete method.
889+
* However, if -Yexplicit-nulls, is enabled, we pass in the current context
890+
* so that flow typing works with blocks.
901891
*/
902-
def completeInContext(denot: SymDenotation, completionContext: Context): Unit = {
892+
def completeInContext(denot: SymDenotation, ctx: Context): Unit = {
903893
val sym = denot.symbol
904894
addAnnotations(sym)
905895
addInlineInfo(sym)
906-
denot.info = typeSig(sym, completionContext)
896+
denot.info = typeSig(sym, ctx)
907897
invalidateIfClashingSynthetic(denot)
908898
Checking.checkWellFormed(sym)
909899
denot.info = avoidPrivateLeaks(sym)
910900
}
911901
}
912902

913-
/** A completer that uses its completion context (as opposed to the creation context)
914-
* to complete itself. This is used so that flow typing can handle `ValDef`s that appear within a block.
915-
*
916-
* Example:
917-
* Suppose we have a block containing
918-
*
919-
* 1. val x: String|Null = ???
920-
* 2. if (x == null) throw NPE
921-
* 3. val y = x
922-
*
923-
* We want to infer y: String on line 3, but if the completer for `y` uses its creation context,
924-
* then we won't have the additional flow facts that say that `y` is not null.
925-
*
926-
* The solution is to use a completer that completes itself using the context at completion time,
927-
* as opposed to creation time. Normally, we need to use the creation context, because a completer
928-
* can be completed at any point in the future. However, for `ValDef`s within a block, we know they'll
929-
* be completed immediately after the symbols are created, so it's safe to use this new kind of completer.
930-
*/
931-
class ValDefInBlockCompleter(original: ValDef)(implicit ctx: Context) extends Completer(original)(ctx) {
932-
assert(ctx.explicitNulls)
933-
934-
override def typingContext(owner: Symbol, completionCtx: Context): FreshContext = {
935-
completionCtx.fresh.setOwner(owner).setTree(original)
936-
}
937-
}
938-
939903
class TypeDefCompleter(original: TypeDef)(ictx: Context) extends Completer(original)(ictx) with TypeParamsCompleter {
940904
private[this] var myTypeParams: List[TypeSymbol] = null
941905
private[this] var nestedCtx: Context = null
@@ -964,7 +928,7 @@ class Namer { typer: Typer =>
964928
myTypeParams
965929
}
966930

967-
override protected def typeSig(sym: Symbol, completionContext: Context): Type =
931+
override protected def typeSig(sym: Symbol, ctx: Context): Type =
968932
typeDefSig(original, sym, completerTypeParams(sym)(ictx))(nestedCtx)
969933
}
970934

@@ -1134,7 +1098,7 @@ class Namer { typer: Typer =>
11341098
}
11351099

11361100
/** The type signature of a ClassDef with given symbol */
1137-
override def completeInContext(denot: SymDenotation, completionContext: Context): Unit = {
1101+
override def completeInContext(denot: SymDenotation, ctx: Context): Unit = {
11381102
val parents = impl.parents
11391103

11401104
/* The type of a parent constructor. Types constructor arguments
@@ -1170,26 +1134,26 @@ class Namer { typer: Typer =>
11701134
* (4) If the class is sealed, it is defined in the same compilation unit as the current class
11711135
*/
11721136
def checkedParentType(parent: untpd.Tree): Type = {
1173-
val ptype = parentType(parent)(ctx.superCallContext).dealiasKeepAnnots
1137+
val ptype = parentType(parent)(this.ctx.superCallContext).dealiasKeepAnnots
11741138
if (cls.isRefinementClass) ptype
11751139
else {
11761140
val pt = checkClassType(ptype, parent.sourcePos,
11771141
traitReq = parent ne parents.head, stablePrefixReq = true)
11781142
if (pt.derivesFrom(cls)) {
11791143
val addendum = parent match {
1180-
case Select(qual: Super, _) if ctx.scala2Mode =>
1144+
case Select(qual: Super, _) if this.ctx.scala2Mode =>
11811145
"\n(Note that inheriting a class of the same name is no longer allowed)"
11821146
case _ => ""
11831147
}
1184-
ctx.error(CyclicInheritance(cls, addendum), parent.sourcePos)
1148+
this.ctx.error(CyclicInheritance(cls, addendum), parent.sourcePos)
11851149
defn.ObjectType
11861150
}
11871151
else {
11881152
val pclazz = pt.typeSymbol
11891153
if (pclazz.is(Final))
1190-
ctx.error(ExtendFinalClass(cls, pclazz), cls.sourcePos)
1154+
this.ctx.error(ExtendFinalClass(cls, pclazz), cls.sourcePos)
11911155
if (pclazz.is(Sealed) && pclazz.associatedFile != cls.associatedFile)
1192-
ctx.error(UnableToExtendSealedClass(pclazz), cls.sourcePos)
1156+
this.ctx.error(UnableToExtendSealedClass(pclazz), cls.sourcePos)
11931157
pt
11941158
}
11951159
}

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

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2211,16 +2211,38 @@ class Typer extends Namer
22112211
traverse(xtree :: rest, facts)
22122212
case none =>
22132213
import untpd.modsDeco
2214-
val ctx1 = if (ctx.explicitNulls) {
2215-
mdef match {
2216-
case mdef: untpd.ValDef if ctx.owner.is(Method) && !mdef.mods.isOneOf(Lazy | Implicit) =>
2217-
ctx.fresh.addFlowFacts(facts)
2218-
case _ => ctx
2214+
// Force completer to use calling context (as opposed to the creation context)
2215+
// to complete itself. This approach is used so that flow typing can handle definitions
2216+
// appearing within a block.
2217+
//
2218+
// Example:
2219+
// Suppose we have a block containing
2220+
// 1. val x: String|Null = ???
2221+
// 2. if (x == null) throw NPE
2222+
// 3. val y = x
2223+
//
2224+
// We want to infer y: String on line 3, but if the completer for `y` uses its creation
2225+
// context, then we won't have the additional flow facts that say that `y` is not null.
2226+
//
2227+
// The solution is to use the context containing more information about the statements above.
2228+
val ctx2 = if (ctx.explicitNulls && ctx.owner.is(Method)) {
2229+
// We cannot use mdef.symbol to get the symbol of the tree here,
2230+
// since the tree has not been completed and doesn't have a denotation.
2231+
mdef.getAttachment(SymOfTree).map(s => (s, s.infoOrCompleter)) match {
2232+
case Some((sym, completer: Namer#Completer)) =>
2233+
val ctx1 = ctx.fresh.addFlowFacts(facts)
2234+
completer.completeInContext(sym, ctx1)
2235+
ctx1
2236+
case _ =>
2237+
// If it has been completed, then it must be because there is a forward reference
2238+
// to the definition in the program. We use the default (creation) context, and flow
2239+
// typing will not be applied.
2240+
ctx
22192241
}
2220-
} else {
2221-
ctx
22222242
}
2223-
typed(mdef)(ctx1) match {
2243+
else ctx
2244+
2245+
typed(mdef)(ctx2) match {
22242246
case mdef1: DefDef if !Inliner.bodyToInline(mdef1.symbol).isEmpty =>
22252247
buf += inlineExpansion(mdef1)
22262248
// replace body with expansion, because it will be used as inlined body

tests/explicit-nulls/neg/flow6.scala

Lines changed: 20 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,50 @@
1-
2-
// Test that flow inference behaves soundly within blocks.
3-
// This means that flow facts are propagated to ValDefs, but not
4-
// to DefDefs or lazy vals or implicit vals.
5-
// The reason is that forward references are allowed for `defs`,
6-
// so when we see
7-
// ```
8-
// f
9-
// if (x == null) return "foo"
10-
// def f = x.length
11-
// ```
12-
// we can't assume that f is executed _after_ the if statement
13-
// is executed.
1+
// Test forward references handled with flow typing
2+
// Currently, the flow typing will not be applied to definitions forwardly referred.
143
class Foo {
154

165
def test1(): Unit = {
176
val x: String|Null = ???
187
if (x == null) return ()
19-
val y = x.length // ok: x: String inferred
8+
def y = x.length // ok: x: String inferred
209
()
2110
}
2211

12+
// This test is similar to test1, but a forward DefDef referring
13+
// to y is added after x. y is completed before knowing the
14+
// fact "x != null", hence, the type x: String|Null is used.
2315
def test2(): Unit = {
2416
val x: String|Null = ???
17+
def z = y
2518
if (x == null) return ()
26-
def y = x.length // error: x: String|Null inferred
19+
def y = x.length // error: x: String|Null is inferred
2720
()
2821
}
2922

23+
// Since y is referred before definition, flow typing is not used here.
3024
def test3(): Unit = {
3125
val x: String|Null = ???
26+
lazy val z = y
3227
if (x == null) return ()
33-
lazy val y = x.length // error: x: String|Null inferred
28+
lazy val y = x.length // error: x: String|Null is inferred
3429
()
3530
}
3631

32+
// This case is invalid because z has an implicit forward reference to y,
33+
// but x, y and z aren't lazy (only forward references to lazy vals are allowed).
34+
// Since y is referred (by z) before definition, flow typing is not used here.
35+
// Only the typing error is shown because reference check is after typing.
3736
def test4(): Unit = {
37+
val z = implicitly[Int]
3838
val x: String|Null = ???
3939
if (x == null) return ()
4040
implicit val y: Int = x.length // error: x: String|Null inferred
4141
}
4242

43-
// This case is different from #3 because the type of y doesn't need
44-
// to be inferred, which triggers a different codepath within the completer.
43+
// Since z is referred before definition, flow typing is not used here.
4544
def test5(): Unit = {
4645
val x: String|Null = ???
4746
if (x == null) return ()
48-
lazy val y: Int = x.length // error: x: String|Null inferred
49-
()
50-
}
51-
52-
def test6(): Unit = {
53-
val x: String|Null = ???
54-
if (x == null) return ()
55-
def y: Int = x.length // error: x: String|Null inferred
56-
()
57-
}
58-
59-
// This test checks that flow facts are forgotten for defs, but only
60-
// the facts gathered within the current block are forgotten.
61-
// Other facts from outer blocks are remembered.
62-
def test7(): Unit = {
63-
val x: String|Null = ???
64-
if (x == null) {
65-
} else {
66-
def f = x.length // ok
67-
def f2: Int = x.length // ok
68-
}
47+
def y = z
48+
def z = x.length // error: x: String|Null inferred
6949
}
70-
}
50+
}

tests/explicit-nulls/pos/flow6.scala

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
2+
// Test that flow inference behaves soundly within blocks.
3+
// This means that flow facts are propagated to all ValDef and DefDef.
4+
class Foo {
5+
6+
def test1(): Unit = {
7+
val x: String|Null = ???
8+
if (x == null) return ()
9+
val y = x.length // ok: x: String inferred
10+
()
11+
}
12+
13+
def test2(): Unit = {
14+
val x: String|Null = ???
15+
if (x == null) return ()
16+
lazy val y = x.length // ok: x: String inferred
17+
()
18+
}
19+
20+
def test3(): Unit = {
21+
val x: String|Null = ???
22+
if (x == null) return ()
23+
implicit val y = x.length // ok: x: String inferred
24+
()
25+
}
26+
27+
def test4(): Unit = {
28+
val x: String|Null = ???
29+
if (x == null) return ()
30+
def y = x.length // ok: x: String inferred
31+
()
32+
}
33+
34+
// This case is different from #3 because the type of y doesn't need
35+
// to be inferred, which triggers a different codepath within the completer.
36+
def test5(): Unit = {
37+
val x: String|Null = ???
38+
if (x == null) return ()
39+
implicit val y: Int = x.length // ok: x: String inferred
40+
}
41+
42+
def test6(): Unit = {
43+
val x: String|Null = ???
44+
if (x == null) return ()
45+
lazy val y: Int = x.length // ok: x: String inferred
46+
()
47+
}
48+
49+
def test7(): Unit = {
50+
val x: String|Null = ???
51+
if (x == null) return ()
52+
def y: Int = x.length // ok: x: String inferred
53+
()
54+
}
55+
56+
def test8(): Unit = {
57+
lazy val x: String|Null = ???
58+
if (x == null) return ()
59+
val y = x.length // ok: x: String inferred
60+
()
61+
}
62+
63+
// This test checks that flow facts are forgotten for defs, but only
64+
// the facts gathered within the current block are forgotten.
65+
// Other facts from outer blocks are remembered.
66+
def test9(): Unit = {
67+
val x: String|Null = ???
68+
if (x == null) {
69+
} else {
70+
def f = x.length // ok
71+
def f2: Int = x.length // ok
72+
}
73+
}
74+
}

0 commit comments

Comments
 (0)