From 404098ff64c0efed4d188f3ac32e18f72cfa3e54 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Sat, 21 Sep 2024 19:54:39 +0200 Subject: [PATCH 1/4] Refactor NotNullInfo to record every reference which is retracted once. --- .../dotty/tools/dotc/typer/Nullables.scala | 32 +++++++--- .../src/dotty/tools/dotc/typer/Typer.scala | 15 ++++- tests/explicit-nulls/neg/i21380c.scala | 6 +- tests/explicit-nulls/neg/i21619.scala | 62 +++++++++++++++++++ 4 files changed, 100 insertions(+), 15 deletions(-) create mode 100644 tests/explicit-nulls/neg/i21619.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Nullables.scala b/compiler/src/dotty/tools/dotc/typer/Nullables.scala index 3f071dad2d03..74623ed7b4e9 100644 --- a/compiler/src/dotty/tools/dotc/typer/Nullables.scala +++ b/compiler/src/dotty/tools/dotc/typer/Nullables.scala @@ -52,15 +52,19 @@ object Nullables: val hiTree = if(hiTpe eq hi.typeOpt) hi else TypeTree(hiTpe) TypeBoundsTree(lo, hiTree, alias) - /** A set of val or var references that are known to be not null, plus a set of - * variable references that are not known (anymore) to be not null + /** A set of val or var references that are known to be not null, + * a set of variable references that are not known (anymore) to be not null, + * plus a set of variables that are known to be not null at any point. */ - case class NotNullInfo(asserted: Set[TermRef], retracted: Set[TermRef]): + case class NotNullInfo(asserted: Set[TermRef], retracted: Set[TermRef], onceRetracted: Set[TermRef]): assert((asserted & retracted).isEmpty) + assert(retracted.subsetOf(onceRetracted)) def isEmpty = this eq NotNullInfo.empty - def retractedInfo = NotNullInfo(Set(), retracted) + def retractedInfo = NotNullInfo(Set(), retracted, onceRetracted) + + def onceRetractedInfo = NotNullInfo(Set(), onceRetracted, onceRetracted) /** The sequential combination with another not-null info */ def seq(that: NotNullInfo): NotNullInfo = @@ -68,19 +72,29 @@ object Nullables: else if that.isEmpty then this else NotNullInfo( this.asserted.union(that.asserted).diff(that.retracted), - this.retracted.union(that.retracted).diff(that.asserted)) + this.retracted.union(that.retracted).diff(that.asserted), + this.onceRetracted.union(that.onceRetracted)) /** The alternative path combination with another not-null info. Used to merge * the nullability info of the two branches of an if. */ def alt(that: NotNullInfo): NotNullInfo = - NotNullInfo(this.asserted.intersect(that.asserted), this.retracted.union(that.retracted)) + NotNullInfo( + this.asserted.intersect(that.asserted), + this.retracted.union(that.retracted), + this.onceRetracted.union(that.onceRetracted)) + + def withOnceRetracted(that: NotNullInfo): NotNullInfo = + if that.isEmpty then this + else NotNullInfo(this.asserted, this.retracted, this.onceRetracted.union(that.onceRetracted)) object NotNullInfo: - val empty = new NotNullInfo(Set(), Set()) + val empty = new NotNullInfo(Set(), Set(), Set()) def apply(asserted: Set[TermRef], retracted: Set[TermRef]): NotNullInfo = - if asserted.isEmpty && retracted.isEmpty then empty - else new NotNullInfo(asserted, retracted) + apply(asserted, retracted, retracted) + def apply(asserted: Set[TermRef], retracted: Set[TermRef], onceRetracted: Set[TermRef]): NotNullInfo = + if asserted.isEmpty && onceRetracted.isEmpty then empty + else new NotNullInfo(asserted, retracted, onceRetracted) end NotNullInfo /** A pair of not-null sets, depending on whether a condition is `true` or `false` */ diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index ce72aac596c0..50dc976e4f69 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1543,8 +1543,10 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer def thenPathInfo = cond1.notNullInfoIf(true).seq(result.thenp.notNullInfo) def elsePathInfo = cond1.notNullInfoIf(false).seq(result.elsep.notNullInfo) result.withNotNullInfo( - if result.thenp.tpe.isRef(defn.NothingClass) then elsePathInfo - else if result.elsep.tpe.isRef(defn.NothingClass) then thenPathInfo + if result.thenp.tpe.isRef(defn.NothingClass) then + elsePathInfo.withOnceRetracted(thenPathInfo) + else if result.elsep.tpe.isRef(defn.NothingClass) then + thenPathInfo.withOnceRetracted(elsePathInfo) else thenPathInfo.alt(elsePathInfo) ) end typedIf @@ -2343,10 +2345,17 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer }: @unchecked val cases2 = cases2x.asInstanceOf[List[CaseDef]] - var nni = expr2.notNullInfo.retractedInfo + // Since we don't know at which point the the exception is thrown in the body, + // we have to collect any reference that is once retracted. + var nni = expr2.notNullInfo.onceRetractedInfo + // It is possible to have non-exhaustive cases, and some exceptions are thrown and not caught. + // Therefore, the code in the finallizer and after the try block can only rely on the retracted + // info from the cases' body. if cases2.nonEmpty then nni = nni.seq(cases2.map(_.notNullInfo.retractedInfo).reduce(_.alt(_))) + val finalizer1 = typed(tree.finalizer, defn.UnitType)(using ctx.addNotNullInfo(nni)) nni = nni.seq(finalizer1.notNullInfo) + assignType(cpy.Try(tree)(expr2, cases2, finalizer1), expr2, cases2).withNotNullInfo(nni) } diff --git a/tests/explicit-nulls/neg/i21380c.scala b/tests/explicit-nulls/neg/i21380c.scala index f86a5638e4c8..de3cd5bafd6b 100644 --- a/tests/explicit-nulls/neg/i21380c.scala +++ b/tests/explicit-nulls/neg/i21380c.scala @@ -32,9 +32,9 @@ def test4: Int = case npe: NullPointerException => x = "" case _ => x = "" x.length // error - // Although the catch block here is exhaustive, - // it is possible that the exception is thrown and not caught. - // Therefore, the code after the try block can only rely on the retracted info. + // Although the catch block here is exhaustive, it is possible to have non-exhaustive cases, + // and some exceptions are thrown and not caught. Therefore, the code in the finallizer and + // after the try block can only rely on the retracted info from the cases' body. def test5: Int = var x: String | Null = null diff --git a/tests/explicit-nulls/neg/i21619.scala b/tests/explicit-nulls/neg/i21619.scala new file mode 100644 index 000000000000..1c93af707b73 --- /dev/null +++ b/tests/explicit-nulls/neg/i21619.scala @@ -0,0 +1,62 @@ +def test1: String = + var x: String | Null = null + x = "" + var i: Int = 1 + try + i match + case _ => + x = null + throw new Exception() + x = "" + catch + case e: Exception => + x.replace("", "") // error + +def test2: String = + var x: String | Null = null + x = "" + var i: Int = 1 + try + i match + case _ => + x = null + throw new Exception() + x = "" + catch + case e: Exception => + x = "e" + x.replace("", "") // error + +def test3: String = + var x: String | Null = null + x = "" + var i: Int = 1 + try + i match + case _ => + x = null + throw new Exception() + x = "" + catch + case e: Exception => + finally + x = "f" + x.replace("", "") // ok + +def test4: String = + var x: String | Null = null + x = "" + var i: Int = 1 + try + try + if i == 1 then + x = null + throw new Exception() + else + x = "" + catch + case _ => + x = "" + catch + case _ => + x.replace("", "") // error \ No newline at end of file From 273c8672ddab62e3365f7ec31af9aa79bf3d2c21 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Sun, 22 Sep 2024 17:03:39 +0200 Subject: [PATCH 2/4] Use a different rule for NotNullInfo --- .../dotty/tools/dotc/typer/Nullables.scala | 44 ++++++------------- .../src/dotty/tools/dotc/typer/Typer.scala | 34 +++++++------- tests/explicit-nulls/neg/i21619.scala | 19 +++++++- 3 files changed, 50 insertions(+), 47 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Nullables.scala b/compiler/src/dotty/tools/dotc/typer/Nullables.scala index 74623ed7b4e9..62d2ccfb7200 100644 --- a/compiler/src/dotty/tools/dotc/typer/Nullables.scala +++ b/compiler/src/dotty/tools/dotc/typer/Nullables.scala @@ -53,48 +53,35 @@ object Nullables: TypeBoundsTree(lo, hiTree, alias) /** A set of val or var references that are known to be not null, - * a set of variable references that are not known (anymore) to be not null, - * plus a set of variables that are known to be not null at any point. + * plus a set of variable references that are once assigned to null. */ - case class NotNullInfo(asserted: Set[TermRef], retracted: Set[TermRef], onceRetracted: Set[TermRef]): - assert((asserted & retracted).isEmpty) - assert(retracted.subsetOf(onceRetracted)) - + case class NotNullInfo(asserted: Set[TermRef], retracted: Set[TermRef]): def isEmpty = this eq NotNullInfo.empty - def retractedInfo = NotNullInfo(Set(), retracted, onceRetracted) - - def onceRetractedInfo = NotNullInfo(Set(), onceRetracted, onceRetracted) + def retractedInfo = NotNullInfo(Set(), retracted) /** The sequential combination with another not-null info */ def seq(that: NotNullInfo): NotNullInfo = if this.isEmpty then that else if that.isEmpty then this else NotNullInfo( - this.asserted.union(that.asserted).diff(that.retracted), - this.retracted.union(that.retracted).diff(that.asserted), - this.onceRetracted.union(that.onceRetracted)) + this.asserted.diff(that.retracted).union(that.asserted), + this.retracted.union(that.retracted)) /** The alternative path combination with another not-null info. Used to merge * the nullability info of the two branches of an if. */ def alt(that: NotNullInfo): NotNullInfo = - NotNullInfo( - this.asserted.intersect(that.asserted), - this.retracted.union(that.retracted), - this.onceRetracted.union(that.onceRetracted)) + NotNullInfo(this.asserted.intersect(that.asserted), this.retracted.union(that.retracted)) - def withOnceRetracted(that: NotNullInfo): NotNullInfo = - if that.isEmpty then this - else NotNullInfo(this.asserted, this.retracted, this.onceRetracted.union(that.onceRetracted)) + def withRetracted(that: NotNullInfo): NotNullInfo = + NotNullInfo(this.asserted, this.retracted.union(that.retracted)) object NotNullInfo: - val empty = new NotNullInfo(Set(), Set(), Set()) + val empty = new NotNullInfo(Set(), Set()) def apply(asserted: Set[TermRef], retracted: Set[TermRef]): NotNullInfo = - apply(asserted, retracted, retracted) - def apply(asserted: Set[TermRef], retracted: Set[TermRef], onceRetracted: Set[TermRef]): NotNullInfo = - if asserted.isEmpty && onceRetracted.isEmpty then empty - else new NotNullInfo(asserted, retracted, onceRetracted) + if asserted.isEmpty && retracted.isEmpty then empty + else new NotNullInfo(asserted, retracted) end NotNullInfo /** A pair of not-null sets, depending on whether a condition is `true` or `false` */ @@ -247,16 +234,13 @@ object Nullables: * or retractions in `info` supersede infos in existing entries of `infos`. */ def extendWith(info: NotNullInfo) = - if info.isEmpty - || info.asserted.forall(infos.impliesNotNull(_)) - && !info.retracted.exists(infos.impliesNotNull(_)) - then infos + if info.isEmpty then infos else info :: infos /** Retract all references to mutable variables */ def retractMutables(using Context) = - val mutables = infos.foldLeft(Set[TermRef]())((ms, info) => - ms.union(info.asserted.filter(_.symbol.is(Mutable)))) + val mutables = infos.foldLeft(Set[TermRef]()): + (ms, info) => ms.union(info.asserted.filter(_.symbol.is(Mutable))) infos.extendWith(NotNullInfo(Set(), mutables)) end extension diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 50dc976e4f69..9c3d816978cf 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1544,9 +1544,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer def elsePathInfo = cond1.notNullInfoIf(false).seq(result.elsep.notNullInfo) result.withNotNullInfo( if result.thenp.tpe.isRef(defn.NothingClass) then - elsePathInfo.withOnceRetracted(thenPathInfo) + elsePathInfo.withRetracted(thenPathInfo) else if result.elsep.tpe.isRef(defn.NothingClass) then - thenPathInfo.withOnceRetracted(elsePathInfo) + thenPathInfo.withRetracted(elsePathInfo) else thenPathInfo.alt(elsePathInfo) ) end typedIf @@ -2143,9 +2143,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer def typedMatchFinish(tree: untpd.Match, sel: Tree, wideSelType: Type, cases: List[untpd.CaseDef], pt: Type)(using Context): Tree = { val cases1 = harmonic(harmonize, pt)(typedCases(cases, sel, wideSelType, pt.dropIfProto)) .asInstanceOf[List[CaseDef]] - var nni = sel.notNullInfo - if cases1.nonEmpty then nni = nni.seq(cases1.map(_.notNullInfo).reduce(_.alt(_))) - assignType(cpy.Match(tree)(sel, cases1), sel, cases1).withNotNullInfo(nni) + var nnInfo = sel.notNullInfo + if cases1.nonEmpty then nnInfo = nnInfo.seq(cases1.map(_.notNullInfo).reduce(_.alt(_))) + assignType(cpy.Match(tree)(sel, cases1), sel, cases1).withNotNullInfo(nnInfo) } def typedCases(cases: List[untpd.CaseDef], sel: Tree, wideSelType0: Type, pt: Type)(using Context): List[CaseDef] = @@ -2327,7 +2327,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val capabilityProof = caughtExceptions.reduce(OrType(_, _, true)) untpd.Block(makeCanThrow(capabilityProof), expr) - def typedTry(tree: untpd.Try, pt: Type)(using Context): Try = { + def typedTry(tree: untpd.Try, pt: Type)(using Context): Try = + var nnInfo = NotNullInfo.empty val expr2 :: cases2x = harmonic(harmonize, pt) { // We want to type check tree.expr first to comput NotNullInfo, but `addCanThrowCapabilities` // uses the types of patterns in `tree.cases` to determine the capabilities. @@ -2339,25 +2340,26 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val casesEmptyBody1 = tree.cases.mapconserve(cpy.CaseDef(_)(body = EmptyTree)) val casesEmptyBody2 = typedCases(casesEmptyBody1, EmptyTree, defn.ThrowableType, WildcardType) val expr1 = typed(addCanThrowCapabilities(tree.expr, casesEmptyBody2), pt.dropIfProto) - val casesCtx = ctx.addNotNullInfo(expr1.notNullInfo.retractedInfo) + + // Since we don't know at which point the the exception is thrown in the body, + // we have to collect any reference that is once retracted. + nnInfo = expr1.notNullInfo.retractedInfo + + val casesCtx = ctx.addNotNullInfo(nnInfo) val cases1 = typedCases(tree.cases, EmptyTree, defn.ThrowableType, pt.dropIfProto)(using casesCtx) expr1 :: cases1 }: @unchecked val cases2 = cases2x.asInstanceOf[List[CaseDef]] - // Since we don't know at which point the the exception is thrown in the body, - // we have to collect any reference that is once retracted. - var nni = expr2.notNullInfo.onceRetractedInfo // It is possible to have non-exhaustive cases, and some exceptions are thrown and not caught. // Therefore, the code in the finallizer and after the try block can only rely on the retracted // info from the cases' body. - if cases2.nonEmpty then nni = nni.seq(cases2.map(_.notNullInfo.retractedInfo).reduce(_.alt(_))) - - val finalizer1 = typed(tree.finalizer, defn.UnitType)(using ctx.addNotNullInfo(nni)) - nni = nni.seq(finalizer1.notNullInfo) + if cases2.nonEmpty then + nnInfo = nnInfo.seq(cases2.map(_.notNullInfo.retractedInfo).reduce(_.alt(_))) - assignType(cpy.Try(tree)(expr2, cases2, finalizer1), expr2, cases2).withNotNullInfo(nni) - } + val finalizer1 = typed(tree.finalizer, defn.UnitType)(using ctx.addNotNullInfo(nnInfo)) + nnInfo = nnInfo.seq(finalizer1.notNullInfo) + assignType(cpy.Try(tree)(expr2, cases2, finalizer1), expr2, cases2).withNotNullInfo(nnInfo) def typedTry(tree: untpd.ParsedTry, pt: Type)(using Context): Try = val cases: List[untpd.CaseDef] = tree.handler match diff --git a/tests/explicit-nulls/neg/i21619.scala b/tests/explicit-nulls/neg/i21619.scala index 1c93af707b73..244f993fd4e1 100644 --- a/tests/explicit-nulls/neg/i21619.scala +++ b/tests/explicit-nulls/neg/i21619.scala @@ -59,4 +59,21 @@ def test4: String = x = "" catch case _ => - x.replace("", "") // error \ No newline at end of file + x.replace("", "") // error + +def test5: Unit = + var x: String | Null = null + var y: String | Null = null + x = "" + y = "" + var i: Int = 1 + try + i match + case _ => + x = null + throw new Exception() + x = "" + catch + case _ => + val z1: String = x.replace("", "") // error + val z2: String = y.replace("", "") \ No newline at end of file From 0a319be27297818514e9c07c86e42159c8e0122c Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Fri, 11 Oct 2024 06:24:24 +0200 Subject: [PATCH 3/4] Consider cases with Nothing type --- .../src/dotty/tools/dotc/typer/Typer.scala | 24 ++++++++++++------- tests/explicit-nulls/neg/i21380b.scala | 18 ++++++++++++++ 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 9c3d816978cf..689e77417692 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1543,9 +1543,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer def thenPathInfo = cond1.notNullInfoIf(true).seq(result.thenp.notNullInfo) def elsePathInfo = cond1.notNullInfoIf(false).seq(result.elsep.notNullInfo) result.withNotNullInfo( - if result.thenp.tpe.isRef(defn.NothingClass) then + if result.thenp.tpe.isNothingType then elsePathInfo.withRetracted(thenPathInfo) - else if result.elsep.tpe.isRef(defn.NothingClass) then + else if result.elsep.tpe.isNothingType then thenPathInfo.withRetracted(elsePathInfo) else thenPathInfo.alt(elsePathInfo) ) @@ -2134,20 +2134,28 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case1 } .asInstanceOf[List[CaseDef]] - var nni = sel.notNullInfo - if cases1.nonEmpty then nni = nni.seq(cases1.map(_.notNullInfo).reduce(_.alt(_))) - assignType(cpy.Match(tree)(sel, cases1), sel, cases1).cast(pt).withNotNullInfo(nni) + assignType(cpy.Match(tree)(sel, cases1), sel, cases1).cast(pt) + .withNotNullInfo(notNullInfoFromCases(sel.notNullInfo, cases1)) } // Overridden in InlineTyper for inline matches def typedMatchFinish(tree: untpd.Match, sel: Tree, wideSelType: Type, cases: List[untpd.CaseDef], pt: Type)(using Context): Tree = { val cases1 = harmonic(harmonize, pt)(typedCases(cases, sel, wideSelType, pt.dropIfProto)) .asInstanceOf[List[CaseDef]] - var nnInfo = sel.notNullInfo - if cases1.nonEmpty then nnInfo = nnInfo.seq(cases1.map(_.notNullInfo).reduce(_.alt(_))) - assignType(cpy.Match(tree)(sel, cases1), sel, cases1).withNotNullInfo(nnInfo) + assignType(cpy.Match(tree)(sel, cases1), sel, cases1) + .withNotNullInfo(notNullInfoFromCases(sel.notNullInfo, cases1)) } + private def notNullInfoFromCases(initInfo: NotNullInfo, cases: List[CaseDef])(using Context): NotNullInfo = + var nnInfo = initInfo + if cases.nonEmpty then + val (nothingCases, normalCases) = cases.partition(_.body.tpe.isNothingType) + nnInfo = nothingCases.foldLeft(nnInfo): + (nni, c) => nni.withRetracted(c.notNullInfo) + if normalCases.nonEmpty then + nnInfo = nnInfo.seq(normalCases.map(_.notNullInfo).reduce(_.alt(_))) + nnInfo + def typedCases(cases: List[untpd.CaseDef], sel: Tree, wideSelType0: Type, pt: Type)(using Context): List[CaseDef] = var caseCtx = ctx var wideSelType = wideSelType0 diff --git a/tests/explicit-nulls/neg/i21380b.scala b/tests/explicit-nulls/neg/i21380b.scala index 83e23053547c..e4d0caa9e32f 100644 --- a/tests/explicit-nulls/neg/i21380b.scala +++ b/tests/explicit-nulls/neg/i21380b.scala @@ -18,4 +18,22 @@ def test3(i: Int) = i match case 1 if x != null => () case _ => x = " " + x.trim() // ok + +def test4(i: Int) = + var x: String | Null = null + var y: String | Null = null + i match + case 1 => x = "1" + case _ => y = " " + x.trim() // error + +def test5(i: Int): String = + var x: String | Null = null + var y: String | Null = null + i match + case 1 => x = "1" + case _ => + y = " " + return y x.trim() // ok \ No newline at end of file From ada9da3350fa5f77e078238df49f8e20542729fc Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Mon, 14 Oct 2024 15:21:03 +0200 Subject: [PATCH 4/4] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ondřej Lhoták --- compiler/src/dotty/tools/dotc/typer/Nullables.scala | 7 +++++-- compiler/src/dotty/tools/dotc/typer/Typer.scala | 2 +- tests/explicit-nulls/neg/i21380c.scala | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Nullables.scala b/compiler/src/dotty/tools/dotc/typer/Nullables.scala index 62d2ccfb7200..e6d764dc1be4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Nullables.scala +++ b/compiler/src/dotty/tools/dotc/typer/Nullables.scala @@ -52,8 +52,11 @@ object Nullables: val hiTree = if(hiTpe eq hi.typeOpt) hi else TypeTree(hiTpe) TypeBoundsTree(lo, hiTree, alias) - /** A set of val or var references that are known to be not null, - * plus a set of variable references that are once assigned to null. + /** A set of val or var references that are known to be not null + * after the tree finishes executing normally (non-exceptionally), + * plus a set of variable references that are ever assigned to null, + * and may therefore be null if execution of the tree is interrupted + * by an exception. */ case class NotNullInfo(asserted: Set[TermRef], retracted: Set[TermRef]): def isEmpty = this eq NotNullInfo.empty diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 689e77417692..7875163f3c8b 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2360,7 +2360,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val cases2 = cases2x.asInstanceOf[List[CaseDef]] // It is possible to have non-exhaustive cases, and some exceptions are thrown and not caught. - // Therefore, the code in the finallizer and after the try block can only rely on the retracted + // Therefore, the code in the finalizer and after the try block can only rely on the retracted // info from the cases' body. if cases2.nonEmpty then nnInfo = nnInfo.seq(cases2.map(_.notNullInfo.retractedInfo).reduce(_.alt(_))) diff --git a/tests/explicit-nulls/neg/i21380c.scala b/tests/explicit-nulls/neg/i21380c.scala index de3cd5bafd6b..9b7a721fbdf0 100644 --- a/tests/explicit-nulls/neg/i21380c.scala +++ b/tests/explicit-nulls/neg/i21380c.scala @@ -33,7 +33,7 @@ def test4: Int = case _ => x = "" x.length // error // Although the catch block here is exhaustive, it is possible to have non-exhaustive cases, - // and some exceptions are thrown and not caught. Therefore, the code in the finallizer and + // and some exceptions are thrown and not caught. Therefore, the code in the finalizer and // after the try block can only rely on the retracted info from the cases' body. def test5: Int =