Skip to content

Commit f24da5c

Browse files
authored
Add warnings for inferred flexible types in public methods and fields (#23880)
Report warning if a public method/field exposes FlexibleType in its result type under explicit nulls and encourage explicit annotation. According to the discussion in #23682 ```scala def f(s: String) = s.trim // warn ``` ```scala -- Warning: Stest.scala:100:4 -------------------------------------------------- 100 |def f(s: String) = s.trim // warn | ^ |Public method f exposes a flexible type (String)? in its inferred signature. Consider annotating the type as String or String | Null explicitly 1 warning found ```
2 parents 8c9c051 + f879e64 commit f24da5c

29 files changed

+199
-83
lines changed

compiler/src/dotty/tools/MainGenericCompiler.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ case class CompileSettings(
8585

8686
object MainGenericCompiler {
8787

88-
val classpathSeparator = File.pathSeparator
88+
val classpathSeparator: String = File.pathSeparator
8989

9090
@sharable val javaOption = raw"""-J(.*)""".r
9191
@sharable val javaPropOption = raw"""-D(.+?)=(.?)""".r

compiler/src/dotty/tools/MainGenericRunner.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ case class Settings(
9696

9797
object MainGenericRunner {
9898

99-
val classpathSeparator = File.pathSeparator
99+
val classpathSeparator: String = File.pathSeparator
100100

101101
def processClasspath(cp: String, tail: List[String]): (List[String], List[String]) =
102102
val cpEntries = cp.split(classpathSeparator).toList

compiler/src/dotty/tools/backend/jvm/CodeGen.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ class CodeGen(val int: DottyBackendInterface, val primitives: DottyPrimitives)(
152152
new interfaces.AbstractFile {
153153
override def name = absfile.name
154154
override def path = absfile.path
155-
override def jfile = Optional.ofNullable(absfile.file)
155+
override def jfile: Optional[java.io.File] = Optional.ofNullable(absfile.file)
156156
}
157157

158158
private def genClass(cd: TypeDef, unit: CompilationUnit): ClassNode = {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2959,7 +2959,7 @@ object SymDenotations {
29592959
dependent = null
29602960
}
29612961

2962-
protected def addDependent(dep: InheritedCache) = {
2962+
protected def addDependent(dep: InheritedCache): Unit = {
29632963
if (dependent == null) dependent = new WeakHashMap
29642964
dependent.nn.put(dep, ())
29652965
}

compiler/src/dotty/tools/dotc/interactive/InteractiveDriver.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ class InteractiveDriver(val settings: List[String]) extends Driver {
234234
private def classesFromDir(dir: Path, buffer: mutable.ListBuffer[TypeName]): Unit =
235235
try
236236
Files.walkFileTree(dir, new SimpleFileVisitor[Path] {
237-
override def visitFile(path: Path, attrs: BasicFileAttributes) = {
237+
override def visitFile(path: Path, attrs: BasicFileAttributes): FileVisitResult = {
238238
if (!attrs.isDirectory) {
239239
val name = path.getFileName.toString
240240
if name.endsWith(tastySuffix) then

compiler/src/dotty/tools/dotc/sbt/APIUtils.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ import xsbti.api.SafeLazy.strict
1717
*/
1818
object APIUtils {
1919
private object Constants {
20-
val PublicAccess = api.Public.create()
21-
val EmptyModifiers = new api.Modifiers(false, false, false, false, false, false, false, false)
22-
val EmptyStructure = api.Structure.of(strict(Array.empty), strict(Array.empty), strict(Array.empty))
23-
val EmptyType = api.EmptyType.of()
20+
val PublicAccess: api.Public = api.Public.create()
21+
val EmptyModifiers: api.Modifiers = new api.Modifiers(false, false, false, false, false, false, false, false)
22+
val EmptyStructure: api.Structure = api.Structure.of(strict(Array.empty), strict(Array.empty), strict(Array.empty))
23+
val EmptyType: api.EmptyType = api.EmptyType.of()
2424
}
2525

2626
import Constants.*

compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -232,13 +232,13 @@ private class ExtractAPICollector(nonLocalClassSymbols: mutable.HashSet[Symbol])
232232

233233
private object Constants {
234234
val emptyStringArray = Array[String]()
235-
val local = api.ThisQualifier.create()
236-
val public = api.Public.create()
237-
val privateLocal = api.Private.create(local)
238-
val protectedLocal = api.Protected.create(local)
239-
val unqualified = api.Unqualified.create()
240-
val thisPath = api.This.create()
241-
val emptyType = api.EmptyType.create()
235+
val local: api.ThisQualifier = api.ThisQualifier.create()
236+
val public: api.Public = api.Public.create()
237+
val privateLocal: api.Private = api.Private.create(local)
238+
val protectedLocal: api.Protected = api.Protected.create(local)
239+
val unqualified: api.Unqualified = api.Unqualified.create()
240+
val thisPath: api.This = api.This.create()
241+
val emptyType: api.EmptyType = api.EmptyType.create()
242242
val emptyModifiers =
243243
new api.Modifiers(false, false, false, false, false,false, false, false)
244244
}

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1194,6 +1194,18 @@ object RefChecks {
11941194
report.warning(ExtensionNullifiedByMember(sym, target), sym.srcPos)
11951195
end checkExtensionMethods
11961196

1197+
/** Check that public (and protected) methods/fields do not expose flexible types. */
1198+
def checkPublicFlexibleTypes(sym: Symbol)(using Context): Unit =
1199+
if ctx.explicitNulls && !ctx.isJava
1200+
&& sym.exists && !sym.is(Private) && sym.owner.isClass
1201+
&& !sym.isOneOf(Synthetic | InlineProxy | Param | Exported) then
1202+
val resTp = sym.info.finalResultType
1203+
if resTp.existsPart(_.isInstanceOf[FlexibleType], StopAt.Static) then
1204+
report.warning(
1205+
em"${sym.show} exposes a flexible type in its inferred result type ${resTp}. Consider annotating the type explicitly",
1206+
sym.srcPos
1207+
)
1208+
11971209
/** Verify that references in the user-defined `@implicitNotFound` message are valid.
11981210
* (i.e. they refer to a type variable that really occurs in the signature of the annotated symbol.)
11991211
*/
@@ -1330,6 +1342,7 @@ class RefChecks extends MiniPhase { thisPhase =>
13301342
val sym = tree.symbol
13311343
checkNoPrivateOverrides(sym)
13321344
checkVolatile(sym)
1345+
checkPublicFlexibleTypes(sym)
13331346
if (sym.exists && sym.owner.isTerm) {
13341347
tree.rhs match {
13351348
case Ident(nme.WILDCARD) => report.error(UnboundPlaceholderParameter(), sym.srcPos)
@@ -1345,6 +1358,7 @@ class RefChecks extends MiniPhase { thisPhase =>
13451358
checkImplicitNotFoundAnnotation.defDef(sym.denot)
13461359
checkUnaryMethods(sym)
13471360
checkExtensionMethods(sym)
1361+
checkPublicFlexibleTypes(sym)
13481362
tree
13491363
}
13501364

compiler/src/dotty/tools/io/Path.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -245,12 +245,12 @@ class Path private[io] (val jpath: JPath) {
245245
if (!exists) false
246246
else {
247247
Files.walkFileTree(jpath, new SimpleFileVisitor[JPath]() {
248-
override def visitFile(file: JPath, attrs: BasicFileAttributes) = {
248+
override def visitFile(file: JPath, attrs: BasicFileAttributes): FileVisitResult = {
249249
Files.delete(file)
250250
FileVisitResult.CONTINUE
251251
}
252252

253-
override def postVisitDirectory(dir: JPath, exc: IOException) = {
253+
override def postVisitDirectory(dir: JPath, exc: IOException): FileVisitResult = {
254254
Files.delete(dir)
255255
FileVisitResult.CONTINUE
256256
}

compiler/src/dotty/tools/repl/AbstractFileClassLoader.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class AbstractFileClassLoader(val root: AbstractFile, parent: ClassLoader) exten
2626
// on JDK 20 the URL constructor we're using is deprecated,
2727
// but the recommended replacement, URL.of, doesn't exist on JDK 8
2828
@annotation.nowarn("cat=deprecation")
29-
override protected def findResource(name: String) =
29+
override protected def findResource(name: String): URL | Null =
3030
findAbstractFile(name) match
3131
case null => null
3232
case file => new URL(null, s"memory:${file.path}", new URLStreamHandler {
@@ -35,13 +35,13 @@ class AbstractFileClassLoader(val root: AbstractFile, parent: ClassLoader) exten
3535
override def getInputStream = file.input
3636
}
3737
})
38-
override protected def findResources(name: String) =
38+
override protected def findResources(name: String): java.util.Enumeration[URL] =
3939
findResource(name) match
4040
case null => Collections.enumeration(Collections.emptyList[URL]) //Collections.emptyEnumeration[URL]
4141
case url => Collections.enumeration(Collections.singleton(url))
4242

4343
override def findClass(name: String): Class[?] = {
44-
var file: AbstractFile = root
44+
var file: AbstractFile | Null = root
4545
val pathParts = name.split("[./]").toList
4646
for (dirPart <- pathParts.init) {
4747
file = file.lookupName(dirPart, true)

0 commit comments

Comments
 (0)