diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 471a9953c4f0..36d533ff46bf 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -13,13 +13,14 @@ import util.{Property, SourceFile, SourcePosition, SrcPos, Chars} import config.{Feature, Config} import config.Feature.{sourceVersion, migrateTo3, enabled} import config.SourceVersion.* -import collection.mutable import reporting.* import printing.Formatting.hl import config.Printers import parsing.Parsers +import dotty.tools.dotc.util.chaining.* import scala.annotation.{unchecked as _, *}, internal.sharable +import scala.collection.mutable, mutable.ListBuffer object desugar { import untpd.* @@ -272,12 +273,12 @@ object desugar { */ private def desugarContextBounds( tdef: TypeDef, - evidenceBuf: mutable.ListBuffer[ValDef], + evidenceBuf: ListBuffer[ValDef], evidenceFlags: FlagSet, freshName: untpd.Tree => TermName, allParamss: List[ParamClause])(using Context): TypeDef = - val evidenceNames = mutable.ListBuffer[TermName]() + val evidenceNames = ListBuffer.empty[TermName] def desugarRHS(rhs: Tree): Tree = rhs match case ContextBounds(tbounds, ctxbounds) => @@ -322,7 +323,7 @@ object desugar { end desugarContextBounds def elimContextBounds(meth: Tree, isPrimaryConstructor: Boolean = false)(using Context): Tree = - val evidenceParamBuf = mutable.ListBuffer[ValDef]() + val evidenceParamBuf = ListBuffer.empty[ValDef] var seenContextBounds: Int = 0 def freshName(unused: Tree) = seenContextBounds += 1 // Start at 1 like FreshNameCreator. @@ -647,7 +648,7 @@ object desugar { * ultimately map to deferred givens. */ def typeDef(tdef: TypeDef)(using Context): Tree = - val evidenceBuf = new mutable.ListBuffer[ValDef] + val evidenceBuf = ListBuffer.empty[ValDef] val result = desugarContextBounds( tdef, evidenceBuf, (tdef.mods.flags.toTermFlags & AccessFlags) | Lazy | DeferredGivenFlags, @@ -1343,7 +1344,7 @@ object desugar { )).withSpan(tree.span) end makePolyFunctionType - /** Invent a name for an anonympus given of type or template `impl`. */ + /** Invent a name for an anonymous given of type or template `impl`. */ def inventGivenName(impl: Tree)(using Context): SimpleName = val str = impl match case impl: Template => @@ -1422,7 +1423,7 @@ object desugar { ids.map(expand(_, false)) else { val pats1 = if (tpt.isEmpty) pats else pats map (Typed(_, tpt)) - pats1 map (makePatDef(pdef, mods, _, rhs)) + pats1.map(makePatDef(original = pdef, mods, _, rhs)) } } @@ -1453,10 +1454,10 @@ object desugar { * val/var/lazy val p = e * * Otherwise, in case there is exactly one variable x_1 in pattern - * val/var/lazy val p = e ==> val/var/lazy val x_1 = (e: @unchecked) match (case p => (x_1)) + * val/var/lazy val p = e ==> val/var/lazy val x_1 = (e: @unchecked) match (case p => (x_1)) * * in case there are zero or more than one variables in pattern - * val/var/lazy p = e ==> private[this] synthetic [lazy] val t$ = (e: @unchecked) match (case p => (x_1, ..., x_N)) + * val/var/lazy p = e ==> private[this] synthetic [lazy] val t$ = (e: @unchecked) match (case p => (x_1, ..., x_N)) * val/var/def x_1 = t$._1 * ... * val/var/def x_N = t$._N @@ -1470,6 +1471,7 @@ object desugar { then cpy.Ident(id)(WildcardParamName.fresh()) else id derivedValDef(original, id1, tpt, rhs, mods) + case _ => def filterWildcardGivenBinding(givenPat: Bind): Boolean = @@ -1538,7 +1540,7 @@ object desugar { if useSelectors then Select(Ident(tmpName), nme.selectorName(n)) else Apply(Select(Ident(tmpName), nme.apply), Literal(Constant(n)) :: Nil) val restDefs = - for (((named, tpt), n) <- vars.zipWithIndex if named.name != nme.WILDCARD) + for ((named, tpt), n) <- vars.zipWithIndex if named.name != nme.WILDCARD yield if mods.is(Lazy) then DefDef(named.name.asTermName, Nil, tpt, selector(n)) @@ -2044,14 +2046,13 @@ object desugar { makeCaseLambda(CaseDef(gen.pat, EmptyTree, body) :: Nil, matchCheckMode) } - def hasGivenBind(pat: Tree): Boolean = pat.existsSubTree { - case pat @ Bind(_, pat1) => pat.mods.is(Given) + def hasGivenBind(pat: Tree): Boolean = pat.existsSubTree: + case pat @ Bind(_, _) => pat.mods.is(Given) case _ => false - } /** Does this pattern define any given bindings */ def isNestedGivenPattern(pat: Tree): Boolean = pat match - case pat @ Bind(_, pat1) => hasGivenBind(pat1) + case Bind(_, pat) => hasGivenBind(pat) case _ => hasGivenBind(pat) /** If `pat` is not an Identifier, a Typed(Ident, _), or a Bind, wrap @@ -2059,18 +2060,22 @@ object desugar { * that refers to the bound variable for the pattern. Wildcard Binds are * also replaced by Binds with fresh names. */ - def makeIdPat(pat: Tree): (Tree, Ident) = pat match { - case bind @ Bind(name, pat1) => - if name == nme.WILDCARD then - val name = UniqueName.fresh() - (cpy.Bind(pat)(name, pat1).withMods(bind.mods), Ident(name)) - else (pat, Ident(name)) + def makeIdPat(pat: Tree): (Tree, Ident) = pat match + case pat @ Bind(nme.WILDCARD, body) => + val name = + body match + case Typed(Ident(nme.WILDCARD), tpt) if pat.mods.is(Given) => inventGivenName(tpt) + case _ => UniqueName.fresh() + cpy.Bind(pat)(name, body) + .withMods(pat.mods) + -> + Ident(name) + case Bind(name, _) => (pat, Ident(name)) case id: Ident if isVarPattern(id) && id.name != nme.WILDCARD => (id, id) case Typed(id: Ident, _) if isVarPattern(id) && id.name != nme.WILDCARD => (pat, id) case _ => val name = UniqueName.fresh() (Bind(name, pat), Ident(name)) - } /** Make a pattern filter: * rhs.withFilter { case pat => true case _ => false } @@ -2128,11 +2133,11 @@ object desugar { } } - /** Is `pat` of the form `x`, `x T`, or `given T`? when used as the lhs of a generator, + /** Is `pat` of the form `x`, `x: T`, or `given T`? when used as the lhs of a generator, * these are all considered irrefutable. */ def isVarBinding(pat: Tree): Boolean = pat match - case pat @ Bind(_, pat1) if pat.mods.is(Given) => isVarBinding(pat1) + case bind @ Bind(_, pat) if bind.mods.is(Given) => isVarBinding(pat) case IdPattern(_) => true case _ => false @@ -2173,36 +2178,42 @@ object desugar { case (gen: GenFrom) :: (rest @ (GenFrom(_, _, _) :: _)) => val cont = makeFor(mapName, flatMapName, rest, body) Apply(rhsSelect(gen, flatMapName), makeLambda(gen, cont)) - case (gen: GenFrom) :: rest - if sourceVersion.enablesBetterFors - && rest.dropWhile(_.isInstanceOf[GenAlias]).headOption.forall(e => e.isInstanceOf[GenFrom]) // possible aliases followed by a generator or end of for - && !rest.takeWhile(_.isInstanceOf[GenAlias]).exists(a => isNestedGivenPattern(a.asInstanceOf[GenAlias].pat)) => - val cont = makeFor(mapName, flatMapName, rest, body) - val selectName = - if rest.exists(_.isInstanceOf[GenFrom]) then flatMapName - else mapName - val aply = Apply(rhsSelect(gen, selectName), makeLambda(gen, cont)) - markTrailingMap(aply, gen, selectName) - aply - case (gen: GenFrom) :: (rest @ GenAlias(_, _) :: _) => - val (valeqs, rest1) = rest.span(_.isInstanceOf[GenAlias]) - val pats = valeqs map { case GenAlias(pat, _) => pat } - val rhss = valeqs map { case GenAlias(_, rhs) => rhs } - val (defpat0, id0) = makeIdPat(gen.pat) - val (defpats, ids) = (pats map makeIdPat).unzip - val pdefs = valeqs.lazyZip(defpats).lazyZip(rhss).map { (valeq, defpat, rhs) => - val mods = defpat match - case defTree: DefTree => defTree.mods - case _ => Modifiers() - makePatDef(valeq, mods, defpat, rhs) - } - val rhs1 = makeFor(nme.map, nme.flatMap, GenFrom(defpat0, gen.expr, gen.checkMode) :: Nil, Block(pdefs, makeTuple(id0 :: ids).withAttachment(ForArtifact, ()))) - val allpats = gen.pat :: pats - val vfrom1 = GenFrom(makeTuple(allpats), rhs1, GenCheckMode.Ignore) - makeFor(mapName, flatMapName, vfrom1 :: rest1, body) + case (gen: GenFrom) :: (tail @ GenAlias(_, _) :: _) => + val (valeqs, suffix) = tail.span(_.isInstanceOf[GenAlias]) + // possible aliases followed by a generator or end of for, when betterFors. + // exclude value definitions with a given pattern (given T = x) + val better = sourceVersion.enablesBetterFors + && suffix.headOption.forall(_.isInstanceOf[GenFrom]) + && !valeqs.exists(a => isNestedGivenPattern(a.asInstanceOf[GenAlias].pat)) + if better then + val cont = makeFor(mapName, flatMapName, enums = tail, body) + val selectName = + if suffix.exists(_.isInstanceOf[GenFrom]) then flatMapName + else mapName + Apply(rhsSelect(gen, selectName), makeLambda(gen, cont)) + .tap(markTrailingMap(_, gen, selectName)) + else + val (pats, rhss) = valeqs.map { case GenAlias(pat, rhs) => (pat, rhs) }.unzip + val (defpat0, id0) = makeIdPat(gen.pat) + val (defpats, ids) = pats.map(makeIdPat).unzip + val pdefs = valeqs.lazyZip(defpats).lazyZip(rhss).map: (valeq, defpat, rhs) => + val mods = defpat match + case defTree: DefTree => defTree.mods + case _ => Modifiers() + makePatDef(original = valeq, mods, defpat, rhs) + val rhs1 = + val enums = GenFrom(defpat0, gen.expr, gen.checkMode) :: Nil + val body = Block(pdefs, makeTuple(id0 :: ids).withAttachment(ForArtifact, ())) + makeFor(nme.map, nme.flatMap, enums, body) + val allpats = gen.pat :: pats + val vfrom1 = GenFrom(makeTuple(allpats), rhs1, GenCheckMode.Ignore) + makeFor(mapName, flatMapName, enums = vfrom1 :: suffix, body) + end if case (gen: GenFrom) :: test :: rest => - val filtered = Apply(rhsSelect(gen, nme.withFilter), makeLambda(gen, test)) - val genFrom = GenFrom(gen.pat, filtered, if sourceVersion.enablesBetterFors then GenCheckMode.Filtered else GenCheckMode.Ignore) + val genFrom = + val filtered = Apply(rhsSelect(gen, nme.withFilter), makeLambda(gen, test)) + val mode = if sourceVersion.enablesBetterFors then GenCheckMode.Filtered else GenCheckMode.Ignore + GenFrom(gen.pat, filtered, mode) makeFor(mapName, flatMapName, genFrom :: rest, body) case GenAlias(_, _) :: _ if sourceVersion.enablesBetterFors => val (valeqs, rest) = enums.span(_.isInstanceOf[GenAlias]) @@ -2213,7 +2224,7 @@ object desugar { val mods = defpat match case defTree: DefTree => defTree.mods case _ => Modifiers() - makePatDef(valeq, mods, defpat, rhs) + makePatDef(original = valeq, mods, defpat, rhs) } Block(pdefs, makeFor(mapName, flatMapName, rest, body)) case _ => @@ -2279,7 +2290,7 @@ object desugar { makeFor(nme.map, nme.flatMap, enums, body) orElse tree case PatDef(mods, pats, tpt, rhs) => val pats1 = if (tpt.isEmpty) pats else pats map (Typed(_, tpt)) - flatTree(pats1 map (makePatDef(tree, mods, _, rhs))) + flatTree(pats1.map(makePatDef(original = tree, mods, _, rhs))) case ext: ExtMethods => Block(List(ext), syntheticUnitLiteral.withSpan(ext.span)) case f: FunctionWithMods if f.hasErasedParams => makeFunctionWithValDefs(f, pt) @@ -2396,7 +2407,7 @@ object desugar { * without duplicates */ private def getVariables(tree: Tree, shouldAddGiven: Context ?=> Bind => Boolean)(using Context): List[VarInfo] = { - val buf = mutable.ListBuffer[VarInfo]() + val buf = ListBuffer.empty[VarInfo] def seenName(name: Name) = buf exists (_._1.name == name) def add(named: NameTree, t: Tree): Unit = if (!seenName(named.name) && named.name.isTermName) buf += ((named, t)) diff --git a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala index 008e1c6feee8..0e1d60ad2f7f 100644 --- a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala +++ b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala @@ -19,7 +19,7 @@ import dotty.tools.dotc.rewrites.Rewrites import dotty.tools.dotc.transform.MegaPhase.MiniPhase import dotty.tools.dotc.typer.{ImportInfo, Typer} import dotty.tools.dotc.typer.Deriving.OriginalTypeClass -import dotty.tools.dotc.util.{Property, Spans, SrcPos}, Spans.Span +import dotty.tools.dotc.util.{Property, Spans, SrcPos}, Spans.{NoSpan, Span} import dotty.tools.dotc.util.Chars.{isLineBreakChar, isWhitespace} import dotty.tools.dotc.util.chaining.* @@ -426,12 +426,14 @@ object CheckUnused: class PostInlining extends CheckUnused(PhaseMode.Report, "PostInlining") class RefInfos: - val defs = mutable.Set.empty[(Symbol, SrcPos)] // definitions - val pats = mutable.Set.empty[(Symbol, SrcPos)] // pattern variables - val refs = mutable.Set.empty[Symbol] // references - val asss = mutable.Set.empty[Symbol] // targets of assignment - val skip = mutable.Set.empty[Symbol] // methods to skip (don't warn about their params) - val nowarn = mutable.Set.empty[Symbol] // marked @nowarn + val defs, // definitions + pats // pattern variables + = mutable.Set.empty[(Symbol, SrcPos)] + val refs, // references + asss, // targets of assignment + skip, // methods to skip (don't warn about their params) + nowarn // marked @nowarn + = mutable.Set.empty[Symbol] val imps = new IdentityHashMap[Import, Unit] // imports val sels = new IdentityHashMap[ImportSelector, Unit] // matched selectors def register(tree: Tree)(using Context): Unit = if inlined.isEmpty then @@ -606,6 +608,7 @@ object CheckUnused: warnAt(pos)(UnusedSymbol.localDefs) def checkPatvars() = + //println(infos.pats.mkString("PATVARS\n", "\n", "\n----\n")) // convert the one non-synthetic span so all are comparable; filter NoSpan below def uniformPos(sym: Symbol, pos: SrcPos): SrcPos = if pos.span.isSynthetic then pos else pos.sourcePos.withSpan(pos.span.toSynthetic) diff --git a/tests/pos/i23119.scala b/tests/pos/i23119.scala new file mode 100644 index 000000000000..cd8026005447 --- /dev/null +++ b/tests/pos/i23119.scala @@ -0,0 +1,29 @@ +//> using options -Wunused:patvars -Werror + +def make: IndexedSeq[FalsePositive] = + for { + i <- 1 to 2 + given Int = i + fp = FalsePositive() + } yield fp + +def broken = + for + i <- List(42) + (x, y) = "hello" -> "world" + yield + s"$x, $y" * i + +def alt: IndexedSeq[FalsePositive] = + given String = "hi" + for + given Int <- 1 to 2 + j: Int = summon[Int] // simple assign because irrefutable + _ = j + 1 + k :: Nil = j :: Nil : @unchecked // pattern in one var + fp = FalsePositive(using k) + yield fp + +class FalsePositive(using Int): + def usage(): Unit = + println(summon[Int])