Skip to content

Commit 213491f

Browse files
committed
warn about unnecessary uses of .nn
1 parent 0be2091 commit 213491f

File tree

5 files changed

+43
-2
lines changed

5 files changed

+43
-2
lines changed

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,18 @@ object NullOpsDecorator:
5050
val stripped = self.stripNull()
5151
stripped ne self
5252
}
53+
54+
def admitsNull(using Context): Boolean = {
55+
val widened = self.widenDealias
56+
widened.isNullType || widened.isAny || (widened match
57+
case OrType(l, r) => r.admitsNull || l.admitsNull
58+
case AndType(l, r) => r.admitsNull && l.admitsNull
59+
case TypeBounds(lo, hi) => lo.admitsNull
60+
case FlexibleType(lo, hi) => true
61+
case tp: TypeProxy => tp.underlying.admitsNull
62+
case _ => false
63+
)
64+
}
5365
end extension
5466

5567
import ast.tpd.*

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,7 @@ object StdNames {
564564
val next: N = "next"
565565
val nmeNewTermName: N = "newTermName"
566566
val nmeNewTypeName: N = "newTypeName"
567+
val nn: N = "nn"
567568
val noAutoTupling: N = "noAutoTupling"
568569
val normalize: N = "normalize"
569570
val notifyAll_ : N = "notifyAll"

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,7 @@ object Types extends TypeUtils {
373373
final def isNotNull(using Context): Boolean = this match {
374374
case tp: ConstantType => tp.value.value != null
375375
case tp: FlexibleType => false
376-
case tp: ClassInfo => !tp.cls.isNullableClass && tp.cls != defn.NothingClass
376+
case tp: ClassInfo => !tp.cls.isNullableClass && !tp.isNothingType
377377
case tp: AppliedType => tp.superType.isNotNull
378378
case tp: TypeBounds => tp.lo.isNotNull
379379
case tp: TypeProxy => tp.underlying.isNotNull

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1088,7 +1088,19 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
10881088
errorTree(tree, em"cannot convert to type selection") // will never be printed due to fallback
10891089
}
10901090

1091-
if (tree.qualifier.isType) {
1091+
def warnUnnecessaryNN(tree: Tree): Unit = {
1092+
if ctx.explicitNulls then {
1093+
val symbol = tree.symbol
1094+
if symbol.exists && symbol.owner == defn.ScalaPredefModuleClass && symbol.name == nme.nn then
1095+
tree match
1096+
case Apply(_, args) =>
1097+
if(args.head.tpe.isNotNull) then report.warning("Unnecessary .nn: qualifier is already not null", tree)
1098+
if pt.admitsNull then report.warning("Unnecessary .nn: expected type admits null", tree)
1099+
case _ =>
1100+
}
1101+
}
1102+
1103+
val tree1 = if (tree.qualifier.isType) {
10921104
val qual1 = typedType(tree.qualifier, shallowSelectionProto(tree.name, pt, this, tree.nameSpan))
10931105
assignType(cpy.Select(tree)(qual1, tree.name), qual1)
10941106
}
@@ -1098,6 +1110,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
10981110
tryAlternatively(typeSelectOnTerm)(tryJavaSelectOnType)
10991111
else
11001112
typeSelectOnTerm
1113+
1114+
warnUnnecessaryNN(tree1)
1115+
tree1
11011116
}
11021117

11031118
def typedThis(tree: untpd.This)(using Context): Tree = {
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
def f1(s: String): String = s.nn // warn
2+
def f2(s: String|Null): String|Null = s.nn // warn
3+
def f3(s: String|Null): Any = s.nn // warn
4+
def f4(s: String|Null): String = s.nn
5+
6+
def f5[T >: String](s: String|Null): T = s.nn
7+
def f6[T >: String|Null](s: String|Null): T = s.nn // warn
8+
9+
def f5a[T <: String](s: T): String = s.nn // warn
10+
11+
// flexible types
12+
def f7(s: String|Null) = "".concat(s.nn) // warn
13+
def f8(s: String): String = s.trim().nn // OK because the .nn could be useful as a dynamic null check

0 commit comments

Comments
 (0)