From c5a8d45792cc69d6aa0c9dcdae65e36073843f7b Mon Sep 17 00:00:00 2001 From: alex Date: Sun, 26 Oct 2025 12:36:32 +0300 Subject: [PATCH] Warn if type argument was inferred as union type --- compiler/src/dotty/tools/dotc/Compiler.scala | 4 ++- .../tools/dotc/config/ScalaSettings.scala | 2 ++ .../tools/dotc/transform/WInferUnion.scala | 32 +++++++++++++++++++ tests/warn/infer-or-type.check | 10 ++++++ tests/warn/infer-or-type.scala | 8 +++++ 5 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 compiler/src/dotty/tools/dotc/transform/WInferUnion.scala create mode 100644 tests/warn/infer-or-type.check create mode 100644 tests/warn/infer-or-type.scala diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index 66044dd9462d..4d26516e09cc 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -33,7 +33,9 @@ class Compiler { protected def frontendPhases: List[List[Phase]] = List(new Parser) :: // Compiler frontend: scanner, parser List(new TyperPhase) :: // Compiler frontend: namer, typer - List(CheckUnused.PostTyper(), CheckShadowing()) :: // Check for unused, shadowed elements + List(new WInferUnion, // Check for type arguments inferred as union types + CheckUnused.PostTyper(), // Check for unused + CheckShadowing()) :: // Check for shadowed elements List(new YCheckPositions) :: // YCheck positions List(new sbt.ExtractDependencies) :: // Sends information on classes' dependencies to sbt via callbacks List(new semanticdb.ExtractSemanticDB.ExtractSemanticInfo) :: // Extract info into .semanticdb files diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index e383bf61f36d..3b9ad68f8326 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -169,6 +169,7 @@ private sealed trait WarningSettings: private val WtoStringInterpolated = BooleanSetting(WarningSetting, "Wtostring-interpolated", "Warn a standard interpolator used toString on a reference type.") private val WrecurseWithDefault = BooleanSetting(WarningSetting, "Wrecurse-with-default", "Warn when a method calls itself with a default argument.") private val WwrongArrow = BooleanSetting(WarningSetting, "Wwrong-arrow", "Warn if function arrow was used instead of context literal ?=>.") + private val WinferUnion = BooleanSetting(WarningSetting, "Winfer-union", "Warn if type argument was inferred as union type.") private val Wunused: Setting[List[ChoiceWithHelp[String]]] = MultiChoiceHelpSetting( WarningSetting, name = "Wunused", @@ -307,6 +308,7 @@ private sealed trait WarningSettings: def toStringInterpolated(using Context): Boolean = allOr(WtoStringInterpolated) def recurseWithDefault(using Context): Boolean = allOr(WrecurseWithDefault) def wrongArrow(using Context): Boolean = allOr(WwrongArrow) + def inferUnion(using Context): Boolean = allOr(WinferUnion) def safeInit(using Context): Boolean = allOr(WsafeInit) /** -X "Extended" or "Advanced" settings */ diff --git a/compiler/src/dotty/tools/dotc/transform/WInferUnion.scala b/compiler/src/dotty/tools/dotc/transform/WInferUnion.scala new file mode 100644 index 000000000000..30b274bd2455 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/WInferUnion.scala @@ -0,0 +1,32 @@ +package dotty.tools.dotc.transform + +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.ast.tpd.InferredTypeTree +import dotty.tools.dotc.core.Contexts.{Context, ctx} +import dotty.tools.dotc.core.Decorators.em +import dotty.tools.dotc.core.Types.OrType +import dotty.tools.dotc.report +import dotty.tools.dotc.transform.MegaPhase.MiniPhase + +class WInferUnion extends MiniPhase { + + override def phaseName: String = WInferUnion.name + + override def description: String = WInferUnion.description + + override def isEnabled(using Context): Boolean = ctx.settings.Whas.inferUnion + + override def transformTypeApply(tree: tpd.TypeApply)(using Context): tpd.Tree = + val inferredOrTypes = tree.args.find: tpt => + tpt.isInstanceOf[InferredTypeTree] && tpt.tpe.stripTypeVar.isInstanceOf[OrType] + inferredOrTypes.foreach: tpt => + report.warning( + em"""|A type argument was inferred to be union type ${tpt.tpe.stripTypeVar} + |This may indicate a programming error. + |""", tpt.srcPos) + tree +} + +object WInferUnion: + val name = "Winfer-union" + val description = "check for type arguments inferred as union types" diff --git a/tests/warn/infer-or-type.check b/tests/warn/infer-or-type.check new file mode 100644 index 000000000000..15b451de916b --- /dev/null +++ b/tests/warn/infer-or-type.check @@ -0,0 +1,10 @@ +-- Warning: tests/warn/infer-or-type.scala:6:18 ------------------------------------------------------------------------ +6 | val _ = List(1).contains("") // warn + | ^^^^^^^^^^^^^^^^ + | A type argument was inferred to be union type Int | String + | This may indicate a programming error. +-- Warning: tests/warn/infer-or-type.scala:7:10 ------------------------------------------------------------------------ +7 | val _ = Pair(1, "") // warn + | ^^^^ + | A type argument was inferred to be union type Int | String + | This may indicate a programming error. diff --git a/tests/warn/infer-or-type.scala b/tests/warn/infer-or-type.scala new file mode 100644 index 000000000000..7b8f2e2b5762 --- /dev/null +++ b/tests/warn/infer-or-type.scala @@ -0,0 +1,8 @@ +//> using options -Winfer-union + +case class Pair[A](x: A, y: A) + +def test = { + val _ = List(1).contains("") // warn + val _ = Pair(1, "") // warn +} \ No newline at end of file