Skip to content

Commit 72c545e

Browse files
committed
Add warnings for flexible types in public methods and fields
1 parent 48bc891 commit 72c545e

19 files changed

+160
-56
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/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/Typer.scala

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3089,13 +3089,32 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
30893089
//todo: make sure dependent method types do not depend on implicits or by-name params
30903090
}
30913091

3092-
/** (1) Check that the signature of the class member does not return a repeated parameter type
3093-
* (2) Make sure the definition's symbol is `sym`.
3094-
* (3) Set the `defTree` of `sym` to be `mdef`.
3092+
/** (1) Check that the signature of the class member does not return a repeated parameter type.
3093+
* (2) Check that the signature of the public class member does not expose a flexible type.
3094+
* (3) Make sure the definition's symbol is `sym`.
3095+
* (4) Set the `defTree` of `sym` to be `mdef`.
30953096
*/
30963097
private def postProcessInfo(mdef: MemberDef, sym: Symbol)(using Context): MemberDef =
30973098
if (!sym.isOneOf(Synthetic | InlineProxy | Param) && sym.info.finalResultType.isRepeatedParam)
30983099
report.error(em"Cannot return repeated parameter type ${sym.info.finalResultType}", sym.srcPos)
3100+
3101+
// Warn if a public method/field exposes FlexibleType in its result type under explicit nulls
3102+
// and encourage explicit annotation.
3103+
if ctx.phase.isTyper && ctx.explicitNulls && !ctx.isJava
3104+
&& sym.exists && sym.isPublic && sym.owner.isClass
3105+
&& !sym.isOneOf(Synthetic | InlineProxy | Param) then
3106+
val resTp = sym.info.finalResultType
3107+
if resTp.existsPart(_.isInstanceOf[FlexibleType], StopAt.Static) then
3108+
val suggestion = resTp match
3109+
case ft: FlexibleType =>
3110+
val hi = ft.hi
3111+
i"Consider annotating the type as ${hi} or ${hi} | Null explicitly"
3112+
case _ => "Consider annotating the type explicitly"
3113+
report.warning(
3114+
em"Public ${if sym.is(Method) then "method" else "field"} ${sym.show} exposes a flexible type ${resTp} in its inferred signature. $suggestion",
3115+
sym.srcPos
3116+
)
3117+
30993118
mdef.ensureHasSym(sym)
31003119
mdef.setDefTree
31013120

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/JLineTerminal.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ class JLineTerminal extends java.io.Closeable {
106106
) extends reader.ParsedLine {
107107
// Using dummy values, not sure what they are used for
108108
def wordIndex = -1
109-
def words = java.util.Collections.emptyList[String]
109+
def words: java.util.List[String] = java.util.Collections.emptyList[String]
110110
}
111111

112112
def parse(input: String, cursor: Int, context: ParseContext): reader.ParsedLine = {

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

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,20 +64,21 @@ class ScriptEngine extends AbstractScriptEngine {
6464

6565
object ScriptEngine {
6666
import java.util.Arrays
67+
import java.util.List
6768
import scala.util.Properties
6869

6970
class Factory extends ScriptEngineFactory {
7071
def getEngineName = "Scala REPL"
7172
def getEngineVersion = "3.0"
72-
def getExtensions = Arrays.asList("scala")
73+
def getExtensions: List[String] = Arrays.asList("scala")
7374
def getLanguageName = "Scala"
7475
def getLanguageVersion = Properties.versionString
75-
def getMimeTypes = Arrays.asList("application/x-scala")
76-
def getNames = Arrays.asList("scala")
76+
def getMimeTypes: List[String] = Arrays.asList("application/x-scala")
77+
def getNames: List[String] = Arrays.asList("scala")
7778

78-
def getMethodCallSyntax(obj: String, m: String, args: String*) = s"$obj.$m(${args.mkString(", ")})"
79+
def getMethodCallSyntax(obj: String, m: String, args: String*): String = s"$obj.$m(${args.mkString(", ")})"
7980

80-
def getOutputStatement(toDisplay: String) = s"""print("$toDisplay")"""
81+
def getOutputStatement(toDisplay: String): String = s"""print("$toDisplay")"""
8182

8283
def getParameter(key: String): Object = key match {
8384
case JScriptEngine.ENGINE => getEngineName
@@ -88,7 +89,7 @@ object ScriptEngine {
8889
case _ => null
8990
}
9091

91-
def getProgram(statements: String*) = statements.mkString("; ")
92+
def getProgram(statements: String*): String = statements.mkString("; ")
9293

9394
def getScriptEngine: JScriptEngine = new ScriptEngine
9495
}

0 commit comments

Comments
 (0)