diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b9fd3b5b864b..db2a66b8b234 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -630,7 +630,10 @@ jobs: - ${{ github.workspace }}/../../cache/general:/root/.cache strategy: matrix: - branch: [main, lts-3.3] + series: [ + {repository: scala/scala3, branch: main}, # Scala Next nightly + {repository: scala/scala3-lts, branch: lts-3.3} # Scala LTS nightly + ] needs: [test_non_bootstrapped, test, mima, community_build_a, community_build_b, community_build_c, test_sbt, test_java8] if: "(github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && github.repository == 'scala/scala3'" env: @@ -660,7 +663,8 @@ jobs: - name: Git Checkout uses: actions/checkout@v4 with: - ref: ${{ matrix.branch }} + repository: ${{ matrix.series.repository }} + ref: ${{ matrix.series.branch }} - name: Add SBT proxy repositories run: cp -vf .github/workflows/repositories /root/.sbt/ ; true @@ -832,7 +836,7 @@ jobs: sha256sum "${msiInstaller}" > "${msiInstaller}.sha256" - name: Install GH CLI - uses: dev-hanz-ops/install-gh-cli-action@v0.2.0 + uses: dev-hanz-ops/install-gh-cli-action@v0.2.1 with: gh-cli-version: 2.59.0 diff --git a/.github/workflows/lts-backport.yaml b/.github/workflows/lts-backport.yaml index 59a9d76ac4d9..ef1df483a30f 100644 --- a/.github/workflows/lts-backport.yaml +++ b/.github/workflows/lts-backport.yaml @@ -15,7 +15,7 @@ jobs: with: fetch-depth: 0 - uses: coursier/cache-action@v6 - - uses: VirtusLab/scala-cli-setup@v1.6.1 + - uses: VirtusLab/scala-cli-setup@v1.6.2 - run: scala-cli ./project/scripts/addToBackportingProject.scala -- ${{ github.sha }} env: GRAPHQL_API_TOKEN: ${{ secrets.GRAPHQL_API_TOKEN }} diff --git a/.github/workflows/spec.yml b/.github/workflows/spec.yml index ab5f2b3d2fe1..cf13174e1f8e 100644 --- a/.github/workflows/spec.yml +++ b/.github/workflows/spec.yml @@ -49,7 +49,7 @@ jobs: env: USER_FOR_TEST: ${{ secrets.SPEC_DEPLOY_USER }} if: ${{ env.USER_FOR_TEST != '' }} - uses: burnett01/rsync-deployments@7.0.1 + uses: burnett01/rsync-deployments@7.0.2 with: switches: -rzv path: docs/_spec/_site/ diff --git a/community-build/community-projects/intent b/community-build/community-projects/intent index 466662fb36ed..c0c4a1939b04 160000 --- a/community-build/community-projects/intent +++ b/community-build/community-projects/intent @@ -1 +1 @@ -Subproject commit 466662fb36ed38d1f045449682bdc109496c6b2d +Subproject commit c0c4a1939b04a6ce4ae5de3aa8949f04674af1f7 diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala index e632def24700..1e07254808d6 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala @@ -818,7 +818,7 @@ trait BCodeSkelBuilder extends BCodeHelpers { methSymbol = dd.symbol jMethodName = methSymbol.javaSimpleName - returnType = asmMethodType(dd.symbol).returnType + returnType = asmMethodType(methSymbol).returnType isMethSymStaticCtor = methSymbol.isStaticConstructor resetMethodBookkeeping(dd) @@ -915,7 +915,7 @@ trait BCodeSkelBuilder extends BCodeHelpers { for (p <- params) { emitLocalVarScope(p.symbol, veryFirstProgramPoint, onePastLastProgramPoint, force = true) } } - if (isMethSymStaticCtor) { appendToStaticCtor(dd) } + if (isMethSymStaticCtor) { appendToStaticCtor() } } // end of emitNormalMethodBody() lineNumber(rhs) @@ -936,7 +936,7 @@ trait BCodeSkelBuilder extends BCodeHelpers { * * TODO document, explain interplay with `fabricateStaticInitAndroid()` */ - private def appendToStaticCtor(dd: DefDef): Unit = { + private def appendToStaticCtor(): Unit = { def insertBefore( location: asm.tree.AbstractInsnNode, diff --git a/compiler/src/dotty/tools/backend/jvm/ClassfileWriters.scala b/compiler/src/dotty/tools/backend/jvm/ClassfileWriters.scala index e2730c1e84ab..81929c11fdcf 100644 --- a/compiler/src/dotty/tools/backend/jvm/ClassfileWriters.scala +++ b/compiler/src/dotty/tools/backend/jvm/ClassfileWriters.scala @@ -12,10 +12,10 @@ import java.util.zip.{CRC32, Deflater, ZipEntry, ZipOutputStream} import dotty.tools.dotc.core.Contexts.* import dotty.tools.dotc.core.Decorators.em +import dotty.tools.dotc.util.chaining.* import dotty.tools.io.{AbstractFile, PlainFile, VirtualFile} import dotty.tools.io.PlainFile.toPlainFile import BTypes.InternalName -import scala.util.chaining.* import dotty.tools.io.JarArchive import scala.language.unsafeNulls diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index 6e1e65d03976..6aab7d54d59e 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -7,9 +7,8 @@ import typer.{TyperPhase, RefChecks} import parsing.Parser import Phases.Phase import transform.* -import dotty.tools.backend import backend.jvm.{CollectSuperCalls, GenBCode} -import localopt.StringInterpolatorOpt +import localopt.{StringInterpolatorOpt, DropForMap} /** The central class of the dotc compiler. The job of a compiler is to create * runs, which process given `phases` in a given `rootContext`. @@ -34,8 +33,7 @@ class Compiler { protected def frontendPhases: List[List[Phase]] = List(new Parser) :: // Compiler frontend: scanner, parser List(new TyperPhase) :: // Compiler frontend: namer, typer - List(new CheckUnused.PostTyper) :: // Check for unused elements - List(new CheckShadowing) :: // Check shadowing elements + List(CheckUnused.PostTyper(), CheckShadowing()) :: // Check for unused, 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 @@ -51,10 +49,10 @@ class Compiler { List(new sbt.ExtractAPI) :: // Sends a representation of the API of classes to sbt via callbacks List(new Inlining) :: // Inline and execute macros List(new PostInlining) :: // Add mirror support for inlined code - List(new CheckUnused.PostInlining) :: // Check for unused elements List(new Staging) :: // Check staging levels and heal staged types List(new Splicing) :: // Replace level 1 splices with holes List(new PickleQuotes) :: // Turn quoted trees into explicit run-time data structures + List(new CheckUnused.PostInlining) :: // Check for unused elements Nil /** Phases dealing with the transformation from pickled trees to backend trees */ @@ -70,7 +68,8 @@ class Compiler { new InlineVals, // Check right hand-sides of an `inline val`s new ExpandSAMs, // Expand single abstract method closures to anonymous classes new ElimRepeated, // Rewrite vararg parameters and arguments - new RefChecks) :: // Various checks mostly related to abstract members and overriding + new RefChecks, // Various checks mostly related to abstract members and overriding + new DropForMap) :: // Drop unused trailing map calls in for comprehensions List(new semanticdb.ExtractSemanticDB.AppendDiagnostics) :: // Attach warnings to extracted SemanticDB and write to .semanticdb file List(new init.Checker) :: // Check initialization of objects List(new ProtectedAccessors, // Add accessors for protected members diff --git a/compiler/src/dotty/tools/dotc/Run.scala b/compiler/src/dotty/tools/dotc/Run.scala index e505ace061a4..f3f948d1e0e6 100644 --- a/compiler/src/dotty/tools/dotc/Run.scala +++ b/compiler/src/dotty/tools/dotc/Run.scala @@ -38,6 +38,7 @@ import Run.Progress import scala.compiletime.uninitialized import dotty.tools.dotc.transform.MegaPhase import dotty.tools.dotc.transform.Pickler.AsyncTastyHolder +import java.util.{Timer, TimerTask} /** A compiler run. Exports various methods to compile source files */ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with ConstraintRunInfo { @@ -382,7 +383,7 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint initializeAsyncTasty() else () => {} - runPhases(allPhases = fusedPhases)(using runCtx) + showProgress(runPhases(allPhases = fusedPhases)(using runCtx)) cancelAsyncTasty() ctx.reporter.finalizeReporting() @@ -433,6 +434,26 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint process()(using unitCtx) } + /** If set to true, prints every 10 seconds the files currently being compiled. + * Turn this flag on if you want to find out which test among many takes more time + * to compile than the others or causes an infinite loop in the compiler. + */ + private inline val debugPrintProgress = false + + /** Period between progress reports, in ms */ + private inline val printProgressPeriod = 10000 + + /** Shows progress if debugPrintProgress is true */ + private def showProgress(proc: => Unit)(using Context): Unit = + if !debugPrintProgress then proc + else + val watchdog = new TimerTask: + def run() = println(i"[compiling $units]") + try + new Timer().schedule(watchdog, printProgressPeriod, printProgressPeriod) + proc + finally watchdog.cancel() + private sealed trait PrintedTree private /*final*/ case class SomePrintedTree(phase: String, tree: String) extends PrintedTree private object NoPrintedTree extends PrintedTree diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index f59220ae62c6..d075e6a981ef 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -47,6 +47,14 @@ object desugar { */ val UntupledParam: Property.Key[Unit] = Property.StickyKey() + /** An attachment key to indicate that a ValDef originated from a pattern. + */ + val PatternVar: Property.Key[Unit] = Property.StickyKey() + + /** An attachment key for Trees originating in for-comprehension, such as tupling of assignments. + */ + val ForArtifact: Property.Key[Unit] = Property.StickyKey() + /** An attachment key to indicate that a ValDef is an evidence parameter * for a context bound. */ @@ -56,6 +64,11 @@ object desugar { */ val PolyFunctionApply: Property.Key[Unit] = Property.StickyKey() + /** An attachment key to indicate that an Apply is created as a last `map` + * scall in a for-comprehension. + */ + val TrailingForMap: Property.Key[Unit] = Property.StickyKey() + /** What static check should be applied to a Match? */ enum MatchCheck { case None, Exhaustive, IrrefutablePatDef, IrrefutableGenFrom @@ -410,6 +423,7 @@ object desugar { .withMods(Modifiers( meth.mods.flags & (AccessFlags | Synthetic) | (vparam.mods.flags & Inline), meth.mods.privateWithin)) + .withSpan(vparam.rhs.span) val rest = defaultGetters(vparams :: paramss1, n + 1) if vparam.rhs.isEmpty then rest else defaultGetter :: rest case _ :: paramss1 => // skip empty parameter lists and type parameters @@ -859,7 +873,7 @@ object desugar { val nu = vparamss.foldLeft(makeNew(classTypeRef)) { (nu, vparams) => val app = Apply(nu, vparams.map(refOfDef)) vparams match { - case vparam :: _ if vparam.mods.is(Given) || vparam.name.is(ContextBoundParamName) => + case vparam :: _ if vparam.mods.isOneOf(GivenOrImplicit) || vparam.name.is(ContextBoundParamName) => app.setApplyKind(ApplyKind.Using) case _ => app } @@ -1507,7 +1521,7 @@ object desugar { val matchExpr = if (tupleOptimizable) rhs else - val caseDef = CaseDef(pat, EmptyTree, makeTuple(ids)) + val caseDef = CaseDef(pat, EmptyTree, makeTuple(ids).withAttachment(ForArtifact, ())) Match(makeSelector(rhs, MatchCheck.IrrefutablePatDef), caseDef :: Nil) vars match { case Nil if !mods.is(Lazy) => @@ -1537,6 +1551,7 @@ object desugar { ValDef(named.name.asTermName, tpt, selector(n)) .withMods(mods) .withSpan(named.span) + .withAttachment(PatternVar, ()) ) flatTree(firstDef :: restDefs) } @@ -1922,6 +1937,7 @@ object desugar { val vdef = ValDef(named.name.asTermName, tpt, rhs) .withMods(mods) .withSpan(original.span.withPoint(named.span.start)) + .withAttachment(PatternVar, ()) val mayNeedSetter = valDef(vdef) mayNeedSetter } @@ -1956,14 +1972,8 @@ object desugar { * * 3. * - * for (P <- G) yield P ==> G - * - * If betterFors is enabled, P is a variable or a tuple of variables and G is not a withFilter. - * * for (P <- G) yield E ==> G.map (P => E) * - * Otherwise - * * 4. * * for (P_1 <- G_1; P_2 <- G_2; ...) ... @@ -2136,14 +2146,20 @@ object desugar { case (Tuple(ts1), Tuple(ts2)) => ts1.corresponds(ts2)(deepEquals) case _ => false + def markTrailingMap(aply: Apply, gen: GenFrom, selectName: TermName): Unit = + if betterForsEnabled + && selectName == mapName + && gen.checkMode != GenCheckMode.Filtered // results of withFilter have the wrong type + && (deepEquals(gen.pat, body) || deepEquals(body, Tuple(Nil))) + then + aply.putAttachment(TrailingForMap, ()) + enums match { case Nil if betterForsEnabled => body case (gen: GenFrom) :: Nil => - if betterForsEnabled - && gen.checkMode != GenCheckMode.Filtered // results of withFilter have the wrong type - && deepEquals(gen.pat, body) - then gen.expr // avoid a redundant map with identity - else Apply(rhsSelect(gen, mapName), makeLambda(gen, body)) + val aply = Apply(rhsSelect(gen, mapName), makeLambda(gen, body)) + markTrailingMap(aply, gen, mapName) + aply case (gen: GenFrom) :: (rest @ (GenFrom(_, _, _) :: _)) => val cont = makeFor(mapName, flatMapName, rest, body) Apply(rhsSelect(gen, flatMapName), makeLambda(gen, cont)) @@ -2154,7 +2170,9 @@ object desugar { val selectName = if rest.exists(_.isInstanceOf[GenFrom]) then flatMapName else mapName - Apply(rhsSelect(gen, selectName), makeLambda(gen, cont)) + 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 } @@ -2167,7 +2185,7 @@ object desugar { 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))) + 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) diff --git a/compiler/src/dotty/tools/dotc/ast/MainProxies.scala b/compiler/src/dotty/tools/dotc/ast/MainProxies.scala index 7bf83d548c97..9ed19c93d1ba 100644 --- a/compiler/src/dotty/tools/dotc/ast/MainProxies.scala +++ b/compiler/src/dotty/tools/dotc/ast/MainProxies.scala @@ -98,17 +98,17 @@ object MainProxies { val body = Try(call, handler :: Nil, EmptyTree) val mainArg = ValDef(nme.args, TypeTree(defn.ArrayType.appliedTo(defn.StringType)), EmptyTree) .withFlags(Param) - /** Replace typed `Ident`s that have been typed with a TypeSplice with the reference to the symbol. - * The annotations will be retype-checked in another scope that may not have the same imports. + + /** This context is used to create the `TypeSplices` wrapping annotations + * below. These should have `mainFun` as their owner (and not the + * enclosing package class that we would get otherwise) so that + * subsequent owner changes (for example in `Typer.typedTypedSplice`) are + * correct. See #22364 and associated tests. */ - def insertTypeSplices = new TreeMap { - override def transform(tree: Tree)(using Context): Tree = tree match - case tree: tpd.Ident @unchecked => TypedSplice(tree) - case tree => super.transform(tree) - } + val annotsCtx = ctx.fresh.setOwner(mainFun) val annots = mainFun.annotations .filterNot(_.matches(defn.MainAnnot)) - .map(annot => insertTypeSplices.transform(annot.tree)) + .map(annot => TypedSplice(annot.tree)(using annotsCtx)) val mainMeth = DefDef(nme.main, (mainArg :: Nil) :: Nil, TypeTree(defn.UnitType), body) .withFlags(JavaStatic | Synthetic) .withAnnotations(annots) diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index e0fe17755257..32ab8378ae16 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -466,6 +466,8 @@ trait UntypedTreeInfo extends TreeInfo[Untyped] { self: Trees.Instance[Untyped] */ private def defKind(tree: Tree)(using Context): FlagSet = unsplice(tree) match { case EmptyTree | _: Import => NoInitsInterface + case tree: TypeDef if ctx.settings.YcompileScala2Library.value => + if (tree.isClassDef) EmptyFlags else NoInitsInterface case tree: TypeDef => if (tree.isClassDef) NoInits else NoInitsInterface case tree: DefDef => if tree.unforcedRhs == EmptyTree @@ -477,6 +479,8 @@ trait UntypedTreeInfo extends TreeInfo[Untyped] { self: Trees.Instance[Untyped] NoInitsInterface else if tree.mods.is(Given) && tree.paramss.isEmpty then EmptyFlags // might become a lazy val: TODO: check whether we need to suppress NoInits once we have new lazy val impl + else if ctx.settings.YcompileScala2Library.value then + EmptyFlags else NoInits case tree: ValDef => if (tree.unforcedRhs == EmptyTree) NoInitsInterface else EmptyFlags diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index 4c7ca396117e..fdefc14aadd6 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -461,8 +461,11 @@ object Trees { else if qualifier.span.exists && qualifier.span.start > span.point then // right associative val realName = name.stripModuleClassSuffix.lastPart Span(span.start, span.start + realName.length, point) - else - Span(point, span.end, point) + else if span.pointMayBeIncorrect then + val realName = name.stripModuleClassSuffix.lastPart + val probablyPoint = span.end - realName.length + Span(probablyPoint, span.end, probablyPoint) + else Span(point, span.end, point) else span } diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index e28aeb8e0313..a5e96f1f9ce2 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -570,7 +570,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: // For example, `(x: T, y: x.f.type) => Unit`. In this case, when we // substitute `x.f.type`, `x` becomes a `TermParamRef`. But the new method // type is still under initialization and `paramInfos` is still `null`, - // so the new `NamedType` will not have a denoation. + // so the new `NamedType` will not have a denotation. def adaptedInfo(psym: Symbol, info: mt.PInfo): mt.PInfo = mt.companion match case mtc: MethodTypeCompanion => mtc.adaptParamInfo(psym, info).asInstanceOf[mt.PInfo] case _ => info diff --git a/compiler/src/dotty/tools/dotc/config/CliCommand.scala b/compiler/src/dotty/tools/dotc/config/CliCommand.scala index b0046ee49cd1..a0edb2b8cded 100644 --- a/compiler/src/dotty/tools/dotc/config/CliCommand.scala +++ b/compiler/src/dotty/tools/dotc/config/CliCommand.scala @@ -7,7 +7,7 @@ import Settings.* import core.Contexts.* import printing.Highlighting -import scala.util.chaining.given +import dotty.tools.dotc.util.chaining.* import scala.PartialFunction.cond trait CliCommand: diff --git a/compiler/src/dotty/tools/dotc/config/Feature.scala b/compiler/src/dotty/tools/dotc/config/Feature.scala index b726f0e33544..91f228bca560 100644 --- a/compiler/src/dotty/tools/dotc/config/Feature.scala +++ b/compiler/src/dotty/tools/dotc/config/Feature.scala @@ -29,9 +29,7 @@ object Feature: val dependent = experimental("dependent") val erasedDefinitions = experimental("erasedDefinitions") val symbolLiterals = deprecated("symbolLiterals") - val fewerBraces = experimental("fewerBraces") val saferExceptions = experimental("saferExceptions") - val clauseInterleaving = experimental("clauseInterleaving") val pureFunctions = experimental("pureFunctions") val captureChecking = experimental("captureChecking") val into = experimental("into") @@ -40,6 +38,7 @@ object Feature: val betterMatchTypeExtractors = experimental("betterMatchTypeExtractors") val quotedPatternsWithPolymorphicFunctions = experimental("quotedPatternsWithPolymorphicFunctions") val betterFors = experimental("betterFors") + val packageObjectValues = experimental("packageObjectValues") def experimentalAutoEnableFeatures(using Context): List[TermName] = defn.languageExperimentalFeatures @@ -61,9 +60,7 @@ object Feature: (dependent, "Allow dependent method types"), (erasedDefinitions, "Allow erased definitions"), (symbolLiterals, "Allow symbol literals"), - (fewerBraces, "Enable support for using indentation for arguments"), (saferExceptions, "Enable safer exceptions"), - (clauseInterleaving, "Enable clause interleaving"), (pureFunctions, "Enable pure functions for capture checking"), (captureChecking, "Enable experimental capture checking"), (into, "Allow into modifier on parameter types"), @@ -125,9 +122,6 @@ object Feature: def namedTypeArgsEnabled(using Context) = enabled(namedTypeArguments) - def clauseInterleavingEnabled(using Context) = - sourceVersion.isAtLeast(`3.6`) || enabled(clauseInterleaving) - def betterForsEnabled(using Context) = enabled(betterFors) def genericNumberLiteralsEnabled(using Context) = enabled(genericNumberLiterals) @@ -170,9 +164,6 @@ object Feature: def migrateTo3(using Context): Boolean = sourceVersion == `3.0-migration` - def fewerBracesEnabled(using Context) = - sourceVersion.isAtLeast(`3.3`) || enabled(fewerBraces) - /** If current source migrates to `version`, issue given warning message * and return `true`, otherwise return `false`. */ diff --git a/compiler/src/dotty/tools/dotc/config/MigrationVersion.scala b/compiler/src/dotty/tools/dotc/config/MigrationVersion.scala index 1d99caa789d3..f77aa0b06308 100644 --- a/compiler/src/dotty/tools/dotc/config/MigrationVersion.scala +++ b/compiler/src/dotty/tools/dotc/config/MigrationVersion.scala @@ -32,6 +32,7 @@ enum MigrationVersion(val warnFrom: SourceVersion, val errorFrom: SourceVersion) case ParameterEnclosedByParenthesis extends MigrationVersion(future, future) case XmlLiteral extends MigrationVersion(future, future) case GivenSyntax extends MigrationVersion(future, never) + case ImplicitParamsWithoutUsing extends MigrationVersion(`3.7`, future) require(warnFrom.ordinal <= errorFrom.ordinal) diff --git a/compiler/src/dotty/tools/dotc/config/PathResolver.scala b/compiler/src/dotty/tools/dotc/config/PathResolver.scala index f60727e6bba2..67be0e3587cb 100644 --- a/compiler/src/dotty/tools/dotc/config/PathResolver.scala +++ b/compiler/src/dotty/tools/dotc/config/PathResolver.scala @@ -53,8 +53,7 @@ object PathResolver { def classPathEnv: String = envOrElse("CLASSPATH", "") def sourcePathEnv: String = envOrElse("SOURCEPATH", "") - //using propOrNone/getOrElse instead of propOrElse so that searchForBootClasspath is lazy evaluated - def javaBootClassPath: String = propOrNone("sun.boot.class.path") getOrElse searchForBootClasspath + def javaBootClassPath: String = propOrElse("sun.boot.class.path", searchForBootClasspath) def javaExtDirs: String = propOrEmpty("java.ext.dirs") def scalaHome: String = propOrEmpty("scala.home") diff --git a/compiler/src/dotty/tools/dotc/config/Properties.scala b/compiler/src/dotty/tools/dotc/config/Properties.scala index 41cd14955759..c2046899aaef 100644 --- a/compiler/src/dotty/tools/dotc/config/Properties.scala +++ b/compiler/src/dotty/tools/dotc/config/Properties.scala @@ -45,7 +45,7 @@ trait PropertiesTrait { def propIsSet(name: String): Boolean = System.getProperty(name) != null def propIsSetTo(name: String, value: String): Boolean = propOrNull(name) == value - def propOrElse(name: String, alt: String): String = System.getProperty(name, alt) + def propOrElse(name: String, alt: => String): String = Option(System.getProperty(name)).getOrElse(alt) def propOrEmpty(name: String): String = propOrElse(name, "") def propOrNull(name: String): String = propOrElse(name, null) def propOrNone(name: String): Option[String] = Option(propOrNull(name)) @@ -53,11 +53,11 @@ trait PropertiesTrait { def setProp(name: String, value: String): String = System.setProperty(name, value) def clearProp(name: String): String = System.clearProperty(name) - def envOrElse(name: String, alt: String): String = Option(System getenv name) getOrElse alt + def envOrElse(name: String, alt: => String): String = Option(System getenv name) getOrElse alt def envOrNone(name: String): Option[String] = Option(System getenv name) // for values based on propFilename - def scalaPropOrElse(name: String, alt: String): String = scalaProps.getProperty(name, alt) + def scalaPropOrElse(name: String, alt: => String): String = scalaProps.getProperty(name, alt) def scalaPropOrEmpty(name: String): String = scalaPropOrElse(name, "") def scalaPropOrNone(name: String): Option[String] = Option(scalaProps.getProperty(name)) diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 6f7dd940c573..986e3f3b9c26 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -11,7 +11,7 @@ import dotty.tools.io.{AbstractFile, Directory, JDK9Reflectors, PlainDirectory, import Setting.ChoiceWithHelp import ScalaSettingCategories.* -import scala.util.chaining.* +import dotty.tools.dotc.util.chaining.* import java.util.zip.Deflater @@ -174,28 +174,20 @@ private sealed trait WarningSettings: choices = List( ChoiceWithHelp("nowarn", ""), ChoiceWithHelp("all", ""), - ChoiceWithHelp( - name = "imports", - description = "Warn if an import selector is not referenced.\n" + - "NOTE : overrided by -Wunused:strict-no-implicit-warn"), + ChoiceWithHelp("imports", "Warn if an import selector is not referenced."), ChoiceWithHelp("privates", "Warn if a private member is unused"), ChoiceWithHelp("locals", "Warn if a local definition is unused"), ChoiceWithHelp("explicits", "Warn if an explicit parameter is unused"), ChoiceWithHelp("implicits", "Warn if an implicit parameter is unused"), ChoiceWithHelp("params", "Enable -Wunused:explicits,implicits"), + ChoiceWithHelp("patvars","Warn if a variable bound in a pattern is unused"), + //ChoiceWithHelp("inlined", "Apply -Wunused to inlined expansions"), // TODO ChoiceWithHelp("linted", "Enable -Wunused:imports,privates,locals,implicits"), ChoiceWithHelp( name = "strict-no-implicit-warn", description = "Same as -Wunused:import, only for imports of explicit named members.\n" + "NOTE : This overrides -Wunused:imports and NOT set by -Wunused:all" ), - // ChoiceWithHelp("patvars","Warn if a variable bound in a pattern is unused"), - ChoiceWithHelp( - name = "unsafe-warn-patvars", - description = "(UNSAFE) Warn if a variable bound in a pattern is unused.\n" + - "This warning can generate false positive, as warning cannot be\n" + - "suppressed yet." - ) ), default = Nil ) @@ -207,7 +199,6 @@ private sealed trait WarningSettings: // Is any choice set for -Wunused? def any(using Context): Boolean = Wall.value || Wunused.value.nonEmpty - // overrided by strict-no-implicit-warn def imports(using Context) = (allOr("imports") || allOr("linted")) && !(strictNoImplicitWarn) def locals(using Context) = @@ -221,9 +212,8 @@ private sealed trait WarningSettings: def params(using Context) = allOr("params") def privates(using Context) = allOr("privates") || allOr("linted") - def patvars(using Context) = - isChoiceSet("unsafe-warn-patvars") // not with "all" - // allOr("patvars") // todo : rename once fixed + def patvars(using Context) = allOr("patvars") + def inlined(using Context) = isChoiceSet("inlined") def linted(using Context) = allOr("linted") def strictNoImplicitWarn(using Context) = diff --git a/compiler/src/dotty/tools/dotc/config/SourceVersion.scala b/compiler/src/dotty/tools/dotc/config/SourceVersion.scala index 3200f64fa6f9..199350949233 100644 --- a/compiler/src/dotty/tools/dotc/config/SourceVersion.scala +++ b/compiler/src/dotty/tools/dotc/config/SourceVersion.scala @@ -31,6 +31,11 @@ enum SourceVersion: def isAtMost(v: SourceVersion) = stable.ordinal <= v.ordinal + def enablesFewerBraces = isAtLeast(`3.3`) + def enablesClauseInterleaving = isAtLeast(`3.6`) + def enablesNewGivens = isAtLeast(`3.6`) + def enablesNamedTuples = isAtLeast(`3.7`) + object SourceVersion extends Property.Key[SourceVersion]: def defaultSourceVersion = `3.7` diff --git a/compiler/src/dotty/tools/dotc/config/WrappedProperties.scala b/compiler/src/dotty/tools/dotc/config/WrappedProperties.scala index 20304b74c1da..a72830331e9f 100644 --- a/compiler/src/dotty/tools/dotc/config/WrappedProperties.scala +++ b/compiler/src/dotty/tools/dotc/config/WrappedProperties.scala @@ -14,12 +14,12 @@ trait WrappedProperties extends PropertiesTrait { protected def propCategory: String = "wrapped" protected def pickJarBasedOn: Class[?] = this.getClass - override def propIsSet(name: String): Boolean = wrap(super.propIsSet(name)) exists (x => x) - override def propOrElse(name: String, alt: String): String = wrap(super.propOrElse(name, alt)) getOrElse alt - override def setProp(name: String, value: String): String = wrap(super.setProp(name, value)).orNull - override def clearProp(name: String): String = wrap(super.clearProp(name)).orNull - override def envOrElse(name: String, alt: String): String = wrap(super.envOrElse(name, alt)) getOrElse alt - override def envOrNone(name: String): Option[String] = wrap(super.envOrNone(name)).flatten + override def propIsSet(name: String): Boolean = wrap(super.propIsSet(name)) exists (x => x) + override def propOrElse(name: String, alt: => String): String = wrap(super.propOrElse(name, alt)) getOrElse alt + override def setProp(name: String, value: String): String = wrap(super.setProp(name, value)).orNull + override def clearProp(name: String): String = wrap(super.clearProp(name)).orNull + override def envOrElse(name: String, alt: => String): String = wrap(super.envOrElse(name, alt)) getOrElse alt + override def envOrNone(name: String): Option[String] = wrap(super.envOrNone(name)).flatten def systemProperties: Iterator[(String, String)] = { import scala.jdk.CollectionConverters.* diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 8ad23c736d74..b72f2ee4b9ef 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -494,6 +494,8 @@ class Definitions { @tu lazy val Predef_undefined: Symbol = ScalaPredefModule.requiredMethod(nme.???) @tu lazy val ScalaPredefModuleClass: ClassSymbol = ScalaPredefModule.moduleClass.asClass + @tu lazy val SameTypeClass: ClassSymbol = requiredClass("scala.=:=") + @tu lazy val SameType_refl: Symbol = SameTypeClass.companionModule.requiredMethod(nme.refl) @tu lazy val SubTypeClass: ClassSymbol = requiredClass("scala.<:<") @tu lazy val SubType_refl: Symbol = SubTypeClass.companionModule.requiredMethod(nme.refl) @@ -616,6 +618,7 @@ class Definitions { @tu lazy val Int_== : Symbol = IntClass.requiredMethod(nme.EQ, List(IntType)) @tu lazy val Int_>= : Symbol = IntClass.requiredMethod(nme.GE, List(IntType)) @tu lazy val Int_<= : Symbol = IntClass.requiredMethod(nme.LE, List(IntType)) + @tu lazy val Int_> : Symbol = IntClass.requiredMethod(nme.GT, List(IntType)) @tu lazy val LongType: TypeRef = valueTypeRef("scala.Long", java.lang.Long.TYPE, LongEnc, nme.specializedTypeNames.Long) def LongClass(using Context): ClassSymbol = LongType.symbol.asClass @tu lazy val Long_+ : Symbol = LongClass.requiredMethod(nme.PLUS, List(LongType)) @@ -834,6 +837,7 @@ class Definitions { @tu lazy val QuotedExprClass: ClassSymbol = requiredClass("scala.quoted.Expr") @tu lazy val QuotesClass: ClassSymbol = requiredClass("scala.quoted.Quotes") + @tu lazy val Quotes_reflectModule: Symbol = QuotesClass.requiredClass("reflectModule") @tu lazy val Quotes_reflect: Symbol = QuotesClass.requiredValue("reflect") @tu lazy val Quotes_reflect_asTerm: Symbol = Quotes_reflect.requiredMethod("asTerm") @tu lazy val Quotes_reflect_Apply: Symbol = Quotes_reflect.requiredValue("Apply") @@ -955,6 +959,7 @@ class Definitions { def NonEmptyTupleClass(using Context): ClassSymbol = NonEmptyTupleTypeRef.symbol.asClass lazy val NonEmptyTuple_tail: Symbol = NonEmptyTupleClass.requiredMethod("tail") @tu lazy val PairClass: ClassSymbol = requiredClass("scala.*:") + @tu lazy val PairClass_unapply: Symbol = PairClass.companionModule.requiredMethod("unapply") @tu lazy val TupleXXLClass: ClassSymbol = requiredClass("scala.runtime.TupleXXL") def TupleXXLModule(using Context): Symbol = TupleXXLClass.companionModule @@ -1063,6 +1068,7 @@ class Definitions { @tu lazy val UntrackedCapturesAnnot: ClassSymbol = requiredClass("scala.caps.untrackedCaptures") @tu lazy val UseAnnot: ClassSymbol = requiredClass("scala.caps.use") @tu lazy val VolatileAnnot: ClassSymbol = requiredClass("scala.volatile") + @tu lazy val LanguageFeatureMetaAnnot: ClassSymbol = requiredClass("scala.annotation.meta.languageFeature") @tu lazy val BeanGetterMetaAnnot: ClassSymbol = requiredClass("scala.annotation.meta.beanGetter") @tu lazy val BeanSetterMetaAnnot: ClassSymbol = requiredClass("scala.annotation.meta.beanSetter") @tu lazy val FieldMetaAnnot: ClassSymbol = requiredClass("scala.annotation.meta.field") @@ -1336,12 +1342,19 @@ class Definitions { case ByNameFunction(_) => true case _ => false + object NamedTupleDirect: + def unapply(t: Type)(using Context): Option[(Type, Type)] = + t match + case AppliedType(tycon, nmes :: vals :: Nil) if tycon.typeSymbol == NamedTupleTypeRef.symbol => + Some((nmes, vals)) + case _ => None + object NamedTuple: def apply(nmes: Type, vals: Type)(using Context): Type = AppliedType(NamedTupleTypeRef, nmes :: vals :: Nil) def unapply(t: Type)(using Context): Option[(Type, Type)] = t match - case AppliedType(tycon, nmes :: vals :: Nil) if tycon.typeSymbol == NamedTupleTypeRef.symbol => + case NamedTupleDirect(nmes, vals) => Some((nmes, vals)) case tp: TypeProxy => val t = unapply(tp.superType); t @@ -1794,18 +1807,24 @@ class Definitions { || (sym eq Any_typeTest) || (sym eq Any_typeCast) - /** Is this type a `TupleN` type? + /** Is `tp` a `TupleN` type? + * + * @return true if the type of `tp` is `TupleN[T1, T2, ..., Tn]` + */ + def isDirectTupleNType(tp: Type)(using Context): Boolean = + val arity = tp.argInfos.length + arity <= MaxTupleArity && { + val tupletp = TupleType(arity) + tupletp != null && tp.isRef(tupletp.symbol) + } + + /** Is `tp` (an alias of) a `TupleN` type? * * @return true if the dealiased type of `tp` is `TupleN[T1, T2, ..., Tn]` */ - def isTupleNType(tp: Type)(using Context): Boolean = { + def isTupleNType(tp: Type)(using Context): Boolean = val tp1 = tp.dealias - val arity = tp1.argInfos.length - arity <= MaxTupleArity && { - val tupletp = TupleType(arity) - tupletp != null && tp1.isRef(tupletp.symbol) - } - } + isDirectTupleNType(tp1) def tupleType(elems: List[Type]): Type = { val arity = elems.length diff --git a/compiler/src/dotty/tools/dotc/core/Mode.scala b/compiler/src/dotty/tools/dotc/core/Mode.scala index cce162092168..6fd76e37977d 100644 --- a/compiler/src/dotty/tools/dotc/core/Mode.scala +++ b/compiler/src/dotty/tools/dotc/core/Mode.scala @@ -166,6 +166,36 @@ object Mode { */ val ForceInline: Mode = newMode(29, "ForceInline") + /** Are we typing the argument of an annotation? + * + * This mode is used through [[Applications.isAnnotConstr]] to avoid lifting + * arguments of annotation constructors. This mode is disabled in nested + * applications (from [[ProtoTypes.typedArg]]) and in "explicit" annotation + * constructors applications (annotation classes constructed with `new`). + * + * In the following example: + * + * ```scala + * @annot(y = new annot(y = Array("World"), x = 1), x = 2) + * ``` + * + * the mode will be set when typing `@annot(...)` but not when typing + * `new annot(...)`, such that the arguments of the former are not lifted but + * the arguments of the later can be: + * + * ```scala + * @annot(x = 2, y = { + * val y$3: Array[String] = + * Array.apply[String](["World" : String]*)( + * scala.reflect.ClassTag.apply[String](classOf[String])) + * new annot(x = 1, y = y$3) + * }) + * ``` + * + * See #22035, #22526, #22553 and `dependent-annot-default-args.scala`. + */ + val InAnnotation: Mode = newMode(30, "InAnnotation") + /** Skip inlining of methods. */ val NoInline: Mode = newMode(31, "NoInline") } diff --git a/compiler/src/dotty/tools/dotc/core/NamerOps.scala b/compiler/src/dotty/tools/dotc/core/NamerOps.scala index 363a01665564..dbdb46aba334 100644 --- a/compiler/src/dotty/tools/dotc/core/NamerOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NamerOps.scala @@ -39,14 +39,17 @@ object NamerOps: */ extension (tp: Type) def separateRefinements(cls: ClassSymbol, refinements: mutable.LinkedHashMap[Name, Type] | Null)(using Context): Type = + val widenSkolemsMap = new TypeMap: + def apply(tp: Type) = mapOver(tp.widenSkolem) tp match case RefinedType(tp1, rname, rinfo) => try tp1.separateRefinements(cls, refinements) finally if refinements != null then + val rinfo1 = widenSkolemsMap(rinfo) refinements(rname) = refinements.get(rname) match - case Some(tp) => tp & rinfo - case None => rinfo + case Some(tp) => tp & rinfo1 + case None => rinfo1 case tp @ AnnotatedType(tp1, ann) => tp.derivedAnnotatedType(tp1.separateRefinements(cls, refinements), ann) case tp: RecType => @@ -146,9 +149,11 @@ object NamerOps: */ def addConstructorApplies(scope: MutableScope, cls: ClassSymbol, modcls: ClassSymbol)(using Context): scope.type = def proxy(constr: Symbol): Symbol = + var flags = ApplyProxyFlags | (constr.flagsUNSAFE & AccessFlags) + if cls.is(Protected) && !modcls.is(Protected) then flags |= Protected newSymbol( modcls, nme.apply, - ApplyProxyFlags | (constr.flagsUNSAFE & AccessFlags), + flags, ApplyProxyCompleter(constr), cls.privateWithin, constr.coord) @@ -172,12 +177,15 @@ object NamerOps: /** A new symbol that is the constructor companion for class `cls` */ def classConstructorCompanion(cls: ClassSymbol)(using Context): TermSymbol = + var flags = ConstructorCompanionFlags + if cls.is(Protected) then flags |= Protected val companion = newModuleSymbol( cls.owner, cls.name.toTermName, - ConstructorCompanionFlags, ConstructorCompanionFlags, + flags, flags, constructorCompanionCompleter(cls), - coord = cls.coord, - compUnitInfo = cls.compUnitInfo) + cls.privateWithin, + cls.coord, + cls.compUnitInfo) companion.moduleClass.registerCompanion(cls) cls.registerCompanion(companion.moduleClass) companion diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index 56d71c7fb57e..90e5544f19af 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -425,6 +425,7 @@ object StdNames { val array_length : N = "array_length" val array_update : N = "array_update" val arraycopy: N = "arraycopy" + val arity: N = "arity" val as: N = "as" val asTerm: N = "asTerm" val asModule: N = "asModule" diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 670663ff2161..0204622c5925 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -1232,6 +1232,15 @@ object SymDenotations { else if (this.exists) owner.enclosingMethod else NoSymbol + /** The closest enclosing method or static symbol containing this definition. + * A local dummy owner is mapped to the primary constructor of the class. + */ + final def enclosingMethodOrStatic(using Context): Symbol = + if this.is(Method) || this.hasAnnotation(defn.ScalaStaticAnnot) then symbol + else if this.isClass then primaryConstructor + else if this.exists then owner.enclosingMethodOrStatic + else NoSymbol + /** The closest enclosing extension method containing this definition, * including methods outside the current class. */ diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 0abd92ee784d..cc0471d40213 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -2983,11 +2983,11 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling * * 1. Single inheritance of classes * 2. Final classes cannot be extended - * 3. ConstantTypes with distinct values are non intersecting - * 4. TermRefs with distinct values are non intersecting + * 3. ConstantTypes with distinct values are non-intersecting + * 4. TermRefs with distinct values are non-intersecting * 5. There is no value of type Nothing * - * Note on soundness: the correctness of match types relies on on the + * Note on soundness: the correctness of match types relies on the * property that in all possible contexts, the same match type expression * is either stuck or reduces to the same case. * @@ -3070,6 +3070,8 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling disjointnessBoundary(tp.effectiveBounds.hi) case tp: ErrorType => defn.AnyType + case tp: NoType.type => + defn.AnyType end disjointnessBoundary (disjointnessBoundary(tp1), disjointnessBoundary(tp2)) match @@ -3204,22 +3206,38 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling end match } + /** Are `cls1` and `cls1` provablyDisjoint classes, i.e., is `cls1 ⋔ cls2` true? + * + * Note that "class" where includes traits, module classes, and (in the recursive case) + * enum value term symbols. + */ private def provablyDisjointClasses(cls1: Symbol, cls2: Symbol)(using Context): Boolean = def isDecomposable(cls: Symbol): Boolean = cls.is(Sealed) && !cls.hasAnonymousChild def decompose(cls: Symbol): List[Symbol] = - cls.children.flatMap { child => + cls.children.map: child => if child.isTerm then - child.info.classSymbols // allow enum vals to be decomposed to their enum class (then filtered out) and any mixins - else child :: Nil - }.filter(child => child.exists && child != cls) + // Enum vals with mixins, such as in i21860 or i22266, + // don't have a single class symbol. + // So instead of decomposing to NoSymbol + // (which leads to erroneously considering an enum type + // as disjoint from one of the mixin, eg. i21860.scala), + // or instead of decomposing to all the class symbols of + // the enum value (which leads to other mixins being decomposed, + // and infinite recursion, eg. i22266), + // we decompose to the enum value term symbol, and handle + // that within the rest of provablyDisjointClasses. + child.info.classSymbol.orElse(child) + else child + .filter(child => child.exists && child != cls) def eitherDerivesFromOther(cls1: Symbol, cls2: Symbol): Boolean = cls1.derivesFrom(cls2) || cls2.derivesFrom(cls1) def smallestNonTraitBase(cls: Symbol): Symbol = - cls.asClass.baseClasses.find(!_.is(Trait)).get + val classes = if cls.isClass then cls.asClass.baseClasses else cls.info.classSymbols + classes.find(!_.is(Trait)).get if (eitherDerivesFromOther(cls1, cls2)) false diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index 33a1b6ae789e..4c705c4252c0 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -326,7 +326,7 @@ object TypeErasure { val sym = t.symbol // Only a few classes have both primitives and references as subclasses. if (sym eq defn.AnyClass) || (sym eq defn.AnyValClass) || (sym eq defn.MatchableClass) || (sym eq defn.SingletonClass) - || isScala2 && !(t.derivesFrom(defn.ObjectClass) || t.isNullType) then + || isScala2 && !(t.derivesFrom(defn.ObjectClass) || t.isNullType | t.isNothingType) then NoSymbol // We only need to check for primitives because derived value classes in arrays are always boxed. else if sym.isPrimitiveValueClass then @@ -596,8 +596,8 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst * will be returned. * * In all other situations, |T| will be computed as follow: - * - For a refined type scala.Array+[T]: - * - if T is Nothing or Null, []Object + * - For a refined type scala.Array[T]: + * - {Scala 2} if T is Nothing or Null, []Object * - otherwise, if T <: Object, []|T| * - otherwise, if T is a type parameter coming from Java, []Object * - otherwise, Object @@ -781,12 +781,14 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst private def eraseArray(tp: Type)(using Context) = { val defn.ArrayOf(elemtp) = tp: @unchecked - if (isGenericArrayElement(elemtp, isScala2 = sourceLanguage.isScala2)) defn.ObjectType - else - try - val eElem = erasureFn(sourceLanguage, semiEraseVCs = false, isConstructor, isSymbol, inSigName)(elemtp) - if eElem.isInstanceOf[WildcardType] then WildcardType - else JavaArrayType(eElem) + if isGenericArrayElement(elemtp, isScala2 = sourceLanguage.isScala2) then + defn.ObjectType + else if sourceLanguage.isScala2 && (elemtp.hiBound.isNullType || elemtp.hiBound.isNothingType) then + JavaArrayType(defn.ObjectType) + else + try erasureFn(sourceLanguage, semiEraseVCs = false, isConstructor, isSymbol, inSigName)(elemtp) match + case _: WildcardType => WildcardType + case elem => JavaArrayType(elem) catch case ex: Throwable => handleRecursive("erase array type", tp.show, ex) } diff --git a/compiler/src/dotty/tools/dotc/core/TypeUtils.scala b/compiler/src/dotty/tools/dotc/core/TypeUtils.scala index 14ccf32c7787..739cc2b74a16 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeUtils.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeUtils.scala @@ -128,22 +128,19 @@ class TypeUtils: case None => throw new AssertionError("not a tuple") def namedTupleElementTypesUpTo(bound: Int, derived: Boolean, normalize: Boolean = true)(using Context): List[(TermName, Type)] = + def extractNamesTypes(nmes: Type, vals: Type): List[(TermName, Type)] = + val names = nmes.tupleElementTypesUpTo(bound, normalize).getOrElse(Nil).map(_.dealias).map: + case ConstantType(Constant(str: String)) => str.toTermName + case t => throw TypeError(em"Malformed NamedTuple: names must be string types, but $t was found.") + val values = vals.tupleElementTypesUpTo(bound, normalize).getOrElse(Nil) + names.zip(values) + (if normalize then self.normalized else self).dealias match // for desugaring and printer, ignore derived types to avoid infinite recursion in NamedTuple.unapply - case AppliedType(tycon, nmes :: vals :: Nil) if !derived && tycon.typeSymbol == defn.NamedTupleTypeRef.symbol => - val names = nmes.tupleElementTypesUpTo(bound, normalize).getOrElse(Nil).map(_.dealias).map: - case ConstantType(Constant(str: String)) => str.toTermName - case t => throw TypeError(em"Malformed NamedTuple: names must be string types, but $t was found.") - val values = vals.tupleElementTypesUpTo(bound, normalize).getOrElse(Nil) - names.zip(values) + case defn.NamedTupleDirect(nmes, vals) => extractNamesTypes(nmes, vals) case t if !derived => Nil // default cause, used for post-typing - case defn.NamedTuple(nmes, vals) => - val names = nmes.tupleElementTypesUpTo(bound, normalize).getOrElse(Nil).map(_.dealias).map: - case ConstantType(Constant(str: String)) => str.toTermName - case t => throw TypeError(em"Malformed NamedTuple: names must be string types, but $t was found.") - val values = vals.tupleElementTypesUpTo(bound, normalize).getOrElse(Nil) - names.zip(values) + case defn.NamedTuple(nmes, vals) => extractNamesTypes(nmes, vals) case t => Nil diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 2fcf628dbc01..7c0c89da97ee 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -2473,7 +2473,7 @@ object Types extends TypeUtils { else lastd match { case lastd: SymDenotation => if stillValid(lastd) && checkedPeriod.code != NowhereCode then finish(lastd.current) - else finish(memberDenot(lastd.initial.name, allowPrivate = false)) + else finish(memberDenot(lastd.initial.name, allowPrivate = lastd.is(Private))) case _ => fromDesignator } @@ -6073,6 +6073,9 @@ object Types extends TypeUtils { def forward(ref: CaptureRef): CaptureRef = val result = this(ref) def ensureTrackable(tp: Type): CaptureRef = tp match + /* Issue #22437: handle case when info is not yet available during postProcess in CC setup */ + case tp: (TypeParamRef | TermRef) if tp.underlying == NoType => + tp case tp: CaptureRef => if tp.isTrackableRef then tp else ensureTrackable(tp.underlying) @@ -6084,6 +6087,9 @@ object Types extends TypeUtils { /** A restriction of the inverse to a function on tracked CaptureRefs */ def backward(ref: CaptureRef): CaptureRef = inverse(ref) match + /* Ensure bijection for issue #22437 fix in method forward above: */ + case result: (TypeParamRef | TermRef) if result.underlying == NoType => + result case result: CaptureRef if result.isTrackableRef => result end BiTypeMap diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 7b80c7c80a21..cf9885d16d1f 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -861,7 +861,7 @@ class TreePickler(pickler: TastyPickler, attributes: Attributes) { assert(isModifierTag(tag)) writeByte(tag) } - assert(!flags.is(Scala2x)) + if flags.is(Scala2x) then assert(attributes.scala2StandardLibrary) if (flags.is(Private)) writeModTag(PRIVATE) if (flags.is(Protected)) writeModTag(PROTECTED) if (flags.is(Final, butNot = Module)) writeModTag(FINAL) diff --git a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala index 3b91312740d1..25245f5ca1b6 100644 --- a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala @@ -535,7 +535,14 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas true) && // We discard the private val representing a case accessor. We only enter the case accessor def. // We do need to load these symbols to read properly unpickle the annotations on the symbol (see sbt-test/scala2-compat/i19421). - !flags.isAllOf(CaseAccessor | PrivateLocal, butNot = Method) + !flags.isAllOf(CaseAccessor | PrivateLocal, butNot = Method) && + // Skip entering extension methods: they will be recreated by the ExtensionMethods phase. + // Same trick is used by tasty-query (see + //https://github.com/scalacenter/tasty-query/blob/fdefadcabb2f21d5c4b71f728b81c68f6fddcc0f/tasty-query/shared/src/main/scala/tastyquery/reader/pickles/PickleReader.scala#L261-L273 + //) + // This trick is also useful when reading the Scala 2 Standard library from tasty, since + // the extension methods will not be present, and it avoid having to distinguish between Scala2 pickles and Scala2 tasty (stdlib) + !(owner.is(ModuleClass) && sym.name.endsWith("$extension")) if (canEnter) owner.asClass.enter(sym, symScope(owner)) diff --git a/compiler/src/dotty/tools/dotc/inlines/Inliner.scala b/compiler/src/dotty/tools/dotc/inlines/Inliner.scala index 982d15f8bcf7..27d95d055f40 100644 --- a/compiler/src/dotty/tools/dotc/inlines/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/inlines/Inliner.scala @@ -384,6 +384,9 @@ class Inliner(val call: tpd.Tree)(using Context): */ private val opaqueProxies = new mutable.ListBuffer[(TermRef, TermRef)] + /** TermRefs for which we already started synthesising proxies */ + private val visitedTermRefs = new mutable.HashSet[TermRef] + protected def hasOpaqueProxies = opaqueProxies.nonEmpty /** Map first halves of opaqueProxies pairs to second halves, using =:= as equality */ @@ -401,8 +404,9 @@ class Inliner(val call: tpd.Tree)(using Context): for cls <- ref.widen.baseClasses do if cls.containsOpaques && (forThisProxy || inlinedMethod.isContainedIn(cls)) - && mapRef(ref).isEmpty + && !visitedTermRefs.contains(ref) then + visitedTermRefs += ref val refiningRef = OpaqueProxy(ref, cls, call.span) val refiningSym = refiningRef.symbol.asTerm val refinedType = refiningRef.info diff --git a/compiler/src/dotty/tools/dotc/inlines/PrepareInlineable.scala b/compiler/src/dotty/tools/dotc/inlines/PrepareInlineable.scala index bb950fbe43cd..47a47f10f905 100644 --- a/compiler/src/dotty/tools/dotc/inlines/PrepareInlineable.scala +++ b/compiler/src/dotty/tools/dotc/inlines/PrepareInlineable.scala @@ -105,8 +105,8 @@ object PrepareInlineable { def preTransform(tree: Tree)(using Context): Tree = tree match { case tree: RefTree if needsAccessor(tree.symbol) => if (tree.symbol.isConstructor) { - report.error("Implementation restriction: cannot use private constructors in inline methods", tree.srcPos) - tree // TODO: create a proper accessor for the private constructor + report.error("Private constructors used in inline methods require @publicInBinary", tree.srcPos) + tree } else val accessor = useAccessor(tree) diff --git a/compiler/src/dotty/tools/dotc/interactive/Completion.scala b/compiler/src/dotty/tools/dotc/interactive/Completion.scala index 333af6a26b3b..8a1cc10373db 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Completion.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Completion.scala @@ -147,11 +147,12 @@ object Completion: checkBacktickPrefix(ident.source.content(), ident.span.start, ident.span.end) case (tree: untpd.RefTree) :: _ if tree.name != nme.ERROR => - tree.name.toString.take(pos.span.point - tree.span.point) - - case _ => naiveCompletionPrefix(pos.source.content().mkString, pos.point) - + val nameStart = tree.span.point + val start = if pos.source.content().lift(nameStart).contains('`') then nameStart + 1 else nameStart + tree.name.toString.take(pos.span.point - start) + case _ => + naiveCompletionPrefix(pos.source.content().mkString, pos.point) end completionPrefix private object GenericImportSelector: @@ -662,7 +663,7 @@ object Completion: */ private def implicitConversionTargets(qual: tpd.Tree)(using Context): Set[SearchSuccess] = { val typer = ctx.typer - val conversions = new typer.ImplicitSearch(defn.AnyType, qual, pos.span).allImplicits + val conversions = new typer.ImplicitSearch(defn.AnyType, qual, pos.span, Set.empty).allImplicits interactiv.println(i"implicit conversion targets considered: ${conversions.toList}%, %") conversions diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 968dcccc3d00..cfb132dd95ce 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -34,6 +34,7 @@ import config.Feature.{sourceVersion, migrateTo3} import config.SourceVersion.* import config.SourceVersion import dotty.tools.dotc.config.MigrationVersion +import dotty.tools.dotc.util.chaining.* object Parsers { @@ -104,6 +105,9 @@ object Parsers { private val InCase: Region => Region = Scanners.InCase(_) private val InCond: Region => Region = Scanners.InParens(LPAREN, _) private val InFor : Region => Region = Scanners.InBraces(_) + private val InOldCond: Region => Region = // old-style Cond to allow indent when InParens, see #22608 + case p: Scanners.InParens => Scanners.Indented(p.indentWidth, p.prefix, p) + case r => r def unimplementedExpr(using Context): Select = Select(scalaDot(nme.Predef), nme.???) @@ -884,7 +888,7 @@ object Parsers { } }) canRewrite &= (in.isAfterLineEnd || statCtdTokens.contains(in.token)) // test (5) - if canRewrite && (!underColonSyntax || Feature.fewerBracesEnabled) then + if canRewrite && (!underColonSyntax || sourceVersion.enablesFewerBraces) then val openingPatchStr = if !colonRequired then "" else if testChar(startOpening - 1, Chars.isOperatorPart(_)) then " :" @@ -1010,7 +1014,7 @@ object Parsers { skipParams() lookahead.isColon && { - !sourceVersion.isAtLeast(`3.6`) + !sourceVersion.enablesNewGivens || { // in the new given syntax, a `:` at EOL after an identifier represents a single identifier given // Example: // given C: @@ -1087,10 +1091,11 @@ object Parsers { */ def followingIsLambdaAfterColon(): Boolean = val lookahead = in.LookaheadScanner(allowIndent = true) + .tap(_.currentRegion.knownWidth = in.currentRegion.indentWidth) def isArrowIndent() = lookahead.isArrow && { - lookahead.nextToken() + lookahead.observeArrowIndented() lookahead.token == INDENT || lookahead.token == EOF } lookahead.nextToken() @@ -1163,7 +1168,7 @@ object Parsers { * body */ def isColonLambda = - Feature.fewerBracesEnabled && in.token == COLONfollow && followingIsLambdaAfterColon() + sourceVersion.enablesFewerBraces && in.token == COLONfollow && followingIsLambdaAfterColon() /** operand { infixop operand | MatchClause } [postfixop], * @@ -1868,7 +1873,7 @@ object Parsers { infixOps(t, canStartInfixTypeTokens, operand, Location.ElseWhere, ParseKind.Type, isOperator = !followingIsVararg() && !isPureArrow - && !(isIdent(nme.as) && sourceVersion.isAtLeast(`3.6`) && inContextBound) + && !(isIdent(nme.as) && sourceVersion.enablesNewGivens && inContextBound) && nextCanFollowOperator(canStartInfixTypeTokens)) /** RefinedType ::= WithType {[nl] Refinement} [`^` CaptureSet] @@ -2261,7 +2266,7 @@ object Parsers { def contextBound(pname: TypeName): Tree = val t = toplevelTyp(inContextBound = true) val ownName = - if isIdent(nme.as) && sourceVersion.isAtLeast(`3.6`) then + if isIdent(nme.as) && sourceVersion.enablesNewGivens then in.nextToken() ident() else EmptyTermName @@ -2274,7 +2279,7 @@ object Parsers { def contextBounds(pname: TypeName): List[Tree] = if in.isColon then in.nextToken() - if in.token == LBRACE && sourceVersion.isAtLeast(`3.6`) + if in.token == LBRACE && sourceVersion.enablesNewGivens then inBraces(commaSeparated(() => contextBound(pname))) else val bound = contextBound(pname) @@ -2323,25 +2328,25 @@ object Parsers { def condExpr(altToken: Token): Tree = val t: Tree = if in.token == LPAREN then - var t: Tree = atSpan(in.offset): - makeTupleOrParens(inParensWithCommas(commaSeparated(exprInParens))) - if in.token != altToken then - if toBeContinued(altToken) then - t = inSepRegion(InCond) { + inSepRegion(InOldCond): // allow inferred NEWLINE for observeIndented below + atSpan(in.offset): + makeTupleOrParens(inParensWithCommas(commaSeparated(exprInParens))) + .pipe: t => + if in.token == altToken then t + else if toBeContinued(altToken) then + inSepRegion(InCond): expr1Rest( postfixExprRest( simpleExprRest(t, Location.ElseWhere), Location.ElseWhere), Location.ElseWhere) - } else if rewriteToNewSyntax(t.span) then - dropParensOrBraces(t.span.start, s"${tokenString(altToken)}") + dropParensOrBraces(t.span.start, tokenString(altToken)) in.observeIndented() return t - t else if in.isNestedStart then - try expr() finally newLinesOpt() + expr().tap(_ => newLinesOpt()) else inSepRegion(InCond)(expr()) if rewriteToOldSyntax(t.span.startPos) then revertToParens(t) @@ -3498,7 +3503,7 @@ object Parsers { val hkparams = typeParamClauseOpt(ParamOwner.Hk) val bounds = if paramOwner.acceptsCtxBounds then typeAndCtxBounds(name) - else if sourceVersion.isAtLeast(`3.6`) && paramOwner == ParamOwner.Type then typeAndCtxBounds(name) + else if sourceVersion.enablesNewGivens && paramOwner == ParamOwner.Type then typeAndCtxBounds(name) else typeBounds() TypeDef(name, lambdaAbstract(hkparams, bounds)).withMods(mods) } @@ -3967,7 +3972,7 @@ object Parsers { val ident = termIdent() var name = ident.name.asTermName val paramss = - if Feature.clauseInterleavingEnabled(using in.languageImportContext) then + if sourceVersion.enablesClauseInterleaving then typeOrTermParamClauses(ParamOwner.Def, numLeadParams) else val tparams = typeParamClauseOpt(ParamOwner.Def) @@ -4067,7 +4072,7 @@ object Parsers { case SEMI | NEWLINE | NEWLINES | COMMA | RBRACE | OUTDENT | EOF => makeTypeDef(typeAndCtxBounds(tname)) case _ if (staged & StageKind.QuotedPattern) != 0 - || sourceVersion.isAtLeast(`3.6`) && in.isColon => + || sourceVersion.enablesNewGivens && in.isColon => makeTypeDef(typeAndCtxBounds(tname)) case _ => syntaxErrorOrIncomplete(ExpectedTypeBoundOrEquals(in.token)) @@ -4242,7 +4247,7 @@ object Parsers { def givenDef(start: Offset, mods: Modifiers, givenMod: Mod) = atSpan(start, nameStart) { var mods1 = addMod(mods, givenMod) val nameStart = in.offset - var newSyntaxAllowed = sourceVersion.isAtLeast(`3.6`) + var newSyntaxAllowed = sourceVersion.enablesNewGivens val hasEmbeddedColon = !in.isColon && followingIsGivenDefWithColon() val name = if isIdent && hasEmbeddedColon then ident() else EmptyTermName diff --git a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala index 2007b633a7c5..ed20c189796b 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala @@ -17,7 +17,7 @@ import scala.collection.mutable import scala.collection.immutable.SortedMap import rewrites.Rewrites.patch import config.Feature -import config.Feature.{migrateTo3, fewerBracesEnabled} +import config.Feature.migrateTo3 import config.SourceVersion.{`3.0`, `3.0-migration`} import config.MigrationVersion import reporting.{NoProfile, Profile, Message} @@ -617,6 +617,7 @@ object Scanners { if nextWidth < lastWidth then currentRegion = topLevelRegion(nextWidth) else if !isLeadingInfixOperator(nextWidth) && !statCtdTokens.contains(lastToken) && lastToken != INDENT then currentRegion match + case _ if token == EOF => // no OUTDENT at EOF case r: Indented => insert(OUTDENT, offset) handleNewIndentWidth(r.enclosing, ir => @@ -637,7 +638,6 @@ object Scanners { insert(OUTDENT, offset) else if r.isInstanceOf[InBraces] && !closingRegionTokens.contains(token) then report.warning("Line is indented too far to the left, or a `}` is missing", sourcePos()) - else if lastWidth < nextWidth || lastWidth == nextWidth && (lastToken == MATCH || lastToken == CATCH) && token == CASE then if canStartIndentTokens.contains(lastToken) then @@ -657,20 +657,34 @@ object Scanners { def spaceTabMismatchMsg(lastWidth: IndentWidth, nextWidth: IndentWidth): Message = em"""Incompatible combinations of tabs and spaces in indentation prefixes. |Previous indent : $lastWidth - |Latest indent : $nextWidth""" + |Latest indent : $nextWidth""" def observeColonEOL(inTemplate: Boolean): Unit = val enabled = if token == COLONop && inTemplate then report.deprecationWarning(em"`:` after symbolic operator is deprecated; use backticks around operator instead", sourcePos(offset)) true - else token == COLONfollow && (inTemplate || fewerBracesEnabled) + else token == COLONfollow && (inTemplate || sourceVersion.enablesFewerBraces) if enabled then peekAhead() val atEOL = isAfterLineEnd || token == EOF reset() if atEOL then token = COLONeol + // consume => and insert if applicable + def observeArrowIndented(): Unit = + if isArrow && indentSyntax then + peekAhead() + val atEOL = isAfterLineEnd || token == EOF + reset() + if atEOL then + val nextWidth = indentWidth(next.offset) + val lastWidth = currentRegion.indentWidth + if lastWidth < nextWidth then + currentRegion = Indented(nextWidth, COLONeol, currentRegion) + offset = next.offset + token = INDENT + def observeIndented(): Unit = if indentSyntax && isNewLine then val nextWidth = indentWidth(next.offset) @@ -679,7 +693,6 @@ object Scanners { currentRegion = Indented(nextWidth, COLONeol, currentRegion) offset = next.offset token = INDENT - end observeIndented /** Insert an token if next token closes an indentation region. * Exception: continue if indentation region belongs to a `match` and next token is `case`. @@ -1099,7 +1112,7 @@ object Scanners { reset() next - class LookaheadScanner(val allowIndent: Boolean = false) extends Scanner(source, offset, allowIndent = allowIndent) { + class LookaheadScanner(allowIndent: Boolean = false) extends Scanner(source, offset, allowIndent = allowIndent) { override protected def initialCharBufferSize = 8 override def languageImportContext = Scanner.this.languageImportContext } @@ -1651,7 +1664,7 @@ object Scanners { case class InCase(outer: Region) extends Region(OUTDENT) /** A class describing an indentation region. - * @param width The principal indendation width + * @param width The principal indentation width * @param prefix The token before the initial of the region */ case class Indented(width: IndentWidth, prefix: Token, outer: Region | Null) extends Region(OUTDENT): diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 27ab73f0fe4d..32115e6bc087 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -168,7 +168,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { changePrec(GlobalPrec) { val argStr: Text = if args.length == 2 - && !defn.isTupleNType(args.head) + && !defn.isDirectTupleNType(args.head) && !isGiven then atPrec(InfixPrec) { argText(args.head) } diff --git a/compiler/src/dotty/tools/dotc/report.scala b/compiler/src/dotty/tools/dotc/report.scala index 2ccf918e12fa..7f1eeb8e22eb 100644 --- a/compiler/src/dotty/tools/dotc/report.scala +++ b/compiler/src/dotty/tools/dotc/report.scala @@ -1,15 +1,12 @@ package dotty.tools.dotc -import reporting.* -import Diagnostic.* -import util.{SourcePosition, NoSourcePosition, SrcPos} -import core.* -import Contexts.*, Flags.*, Symbols.*, Decorators.* -import config.SourceVersion import ast.* -import config.Feature.sourceVersion +import core.*, Contexts.*, Flags.*, Symbols.*, Decorators.* +import config.Feature.sourceVersion, config.{MigrationVersion, SourceVersion} +import reporting.*, Diagnostic.* +import util.{SourcePosition, NoSourcePosition, SrcPos} + import java.lang.System.currentTimeMillis -import dotty.tools.dotc.config.MigrationVersion object report: @@ -55,6 +52,9 @@ object report: else issueWarning(new FeatureWarning(msg, pos.sourcePos)) end featureWarning + def warning(msg: Message, pos: SrcPos, origin: String)(using Context): Unit = + issueWarning(LintWarning(msg, addInlineds(pos), origin)) + def warning(msg: Message, pos: SrcPos)(using Context): Unit = issueWarning(new Warning(msg, addInlineds(pos))) diff --git a/compiler/src/dotty/tools/dotc/reporting/Diagnostic.scala b/compiler/src/dotty/tools/dotc/reporting/Diagnostic.scala index 6a2d88f4e82f..20be33716831 100644 --- a/compiler/src/dotty/tools/dotc/reporting/Diagnostic.scala +++ b/compiler/src/dotty/tools/dotc/reporting/Diagnostic.scala @@ -8,9 +8,9 @@ import dotty.tools.dotc.config.Settings.Setting import dotty.tools.dotc.core.Contexts.* import dotty.tools.dotc.interfaces.Diagnostic.{ERROR, INFO, WARNING} import dotty.tools.dotc.util.SourcePosition +import dotty.tools.dotc.util.chaining.* import java.util.{Collections, Optional, List => JList} -import scala.util.chaining.* import core.Decorators.toMessage object Diagnostic: @@ -36,6 +36,18 @@ object Diagnostic: pos: SourcePosition ) extends Error(msg, pos) + /** A Warning with an origin. The semantics of `origin` depend on the warning. + * For example, an unused import warning has an origin that specifies the unused selector. + * The origin of a deprecation is the deprecated element. + */ + trait OriginWarning(val origin: String): + self: Warning => + + /** Lints are likely to be filtered. Provide extra axes for filtering by `-Wconf`. + */ + class LintWarning(msg: Message, pos: SourcePosition, origin: String) + extends Warning(msg, pos), OriginWarning(origin) + class Warning( msg: Message, pos: SourcePosition @@ -73,13 +85,9 @@ object Diagnostic: def enablingOption(using Context): Setting[Boolean] = ctx.settings.unchecked } - class DeprecationWarning( - msg: Message, - pos: SourcePosition, - val origin: String - ) extends ConditionalWarning(msg, pos) { + class DeprecationWarning(msg: Message, pos: SourcePosition, origin: String) + extends ConditionalWarning(msg, pos), OriginWarning(origin): def enablingOption(using Context): Setting[Boolean] = ctx.settings.deprecation - } class MigrationWarning( msg: Message, @@ -104,5 +112,5 @@ class Diagnostic( override def diagnosticRelatedInformation: JList[interfaces.DiagnosticRelatedInformation] = Collections.emptyList() - override def toString: String = s"$getClass at $pos: $message" + override def toString: String = s"$getClass at $pos L${pos.line+1}: $message" end Diagnostic diff --git a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala index 25f2f879077e..8f8f4676f43b 100644 --- a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala +++ b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala @@ -132,7 +132,7 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe case MissingCompanionForStaticID // errorNumber: 116 case PolymorphicMethodMissingTypeInParentID // errorNumber: 117 case ParamsNoInlineID // errorNumber: 118 - case JavaSymbolIsNotAValueID // errorNumber: 119 + case SymbolIsNotAValueID // errorNumber: 119 case DoubleDefinitionID // errorNumber: 120 case MatchCaseOnlyNullWarningID // errorNumber: 121 case ImportedTwiceID // errorNumber: 122 @@ -221,6 +221,7 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe case GivenSearchPriorityID // errorNumber: 205 case EnumMayNotBeValueClassesID // errorNumber: 206 case IllegalUnrollPlacementID // errorNumber: 207 + case ExtensionHasDefaultID // errorNumber: 208 def errorNumber = ordinal - 1 diff --git a/compiler/src/dotty/tools/dotc/reporting/WConf.scala b/compiler/src/dotty/tools/dotc/reporting/WConf.scala index 4ca62a8ebe3d..f29bef75959a 100644 --- a/compiler/src/dotty/tools/dotc/reporting/WConf.scala +++ b/compiler/src/dotty/tools/dotc/reporting/WConf.scala @@ -14,11 +14,13 @@ import scala.annotation.internal.sharable import scala.util.matching.Regex enum MessageFilter: - def matches(message: Diagnostic): Boolean = this match + def matches(message: Diagnostic): Boolean = + import Diagnostic.* + this match case Any => true - case Deprecated => message.isInstanceOf[Diagnostic.DeprecationWarning] - case Feature => message.isInstanceOf[Diagnostic.FeatureWarning] - case Unchecked => message.isInstanceOf[Diagnostic.UncheckedWarning] + case Deprecated => message.isInstanceOf[DeprecationWarning] + case Feature => message.isInstanceOf[FeatureWarning] + case Unchecked => message.isInstanceOf[UncheckedWarning] case MessageID(errorId) => message.msg.errorId == errorId case MessagePattern(pattern) => val noHighlight = message.msg.message.replaceAll("\\e\\[[\\d;]*[^\\d;]","") @@ -31,7 +33,7 @@ enum MessageFilter: pattern.findFirstIn(path).nonEmpty case Origin(pattern) => message match - case message: Diagnostic.DeprecationWarning => pattern.findFirstIn(message.origin).nonEmpty + case message: OriginWarning => pattern.findFirstIn(message.origin).nonEmpty case _ => false case None => false @@ -56,12 +58,12 @@ object WConf: private type Conf = (List[MessageFilter], Action) def parseAction(s: String): Either[List[String], Action] = s match - case "error" | "e" => Right(Error) - case "warning" | "w" => Right(Warning) - case "verbose" | "v" => Right(Verbose) - case "info" | "i" => Right(Info) - case "silent" | "s" => Right(Silent) - case _ => Left(List(s"unknown action: `$s`")) + case "error" | "e" => Right(Error) + case "warning" | "w" => Right(Warning) + case "verbose" | "v" => Right(Verbose) + case "info" | "i" => Right(Info) + case "silent" | "s" => Right(Silent) + case _ => Left(List(s"unknown action: `$s`")) private def regex(s: String) = try Right(s.r) diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 20347f706502..b321ed35a23f 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -2317,7 +2317,7 @@ class ParamsNoInline(owner: Symbol)(using Context) def explain(using Context) = "" } -class JavaSymbolIsNotAValue(symbol: Symbol)(using Context) extends TypeMsg(JavaSymbolIsNotAValueID) { +class SymbolIsNotAValue(symbol: Symbol)(using Context) extends TypeMsg(SymbolIsNotAValueID) { def msg(using Context) = val kind = if symbol is Package then i"$symbol" @@ -2514,6 +2514,17 @@ class ExtensionNullifiedByMember(method: Symbol, target: Symbol)(using Context) | |The extension may be invoked as though selected from an arbitrary type if conversions are in play.""" +class ExtensionHasDefault(method: Symbol)(using Context) + extends Message(ExtensionHasDefaultID): + def kind = MessageKind.PotentialIssue + def msg(using Context) = + i"""Extension method ${hl(method.name.toString)} should not have a default argument for its receiver.""" + def explain(using Context) = + i"""The receiver cannot be omitted when an extension method is invoked as a selection. + |A default argument for that parameter would never be used in that case. + |An extension method can be invoked as a regular method, but if that is the intended usage, + |it should not be defined as an extension.""" + class TraitCompanionWithMutableStatic()(using Context) extends SyntaxMsg(TraitCompanionWithMutableStaticID) { def msg(using Context) = i"Companion of traits cannot define mutable @static fields" @@ -3290,22 +3301,24 @@ extends TypeMsg(ConstructorProxyNotValueID): |companion value with the (term-)name `A`. However, these context bound companions |are not values themselves, they can only be referred to in selections.""" -class UnusedSymbol(errorText: String)(using Context) +class UnusedSymbol(errorText: String, val actions: List[CodeAction] = Nil)(using Context) extends Message(UnusedSymbolID) { def kind = MessageKind.UnusedSymbol override def msg(using Context) = errorText override def explain(using Context) = "" -} - -object UnusedSymbol { - def imports(using Context): UnusedSymbol = new UnusedSymbol(i"unused import") - def localDefs(using Context): UnusedSymbol = new UnusedSymbol(i"unused local definition") - def explicitParams(using Context): UnusedSymbol = new UnusedSymbol(i"unused explicit parameter") - def implicitParams(using Context): UnusedSymbol = new UnusedSymbol(i"unused implicit parameter") - def privateMembers(using Context): UnusedSymbol = new UnusedSymbol(i"unused private member") - def patVars(using Context): UnusedSymbol = new UnusedSymbol(i"unused pattern variable") -} + override def actions(using Context) = this.actions +} + +object UnusedSymbol: + def imports(actions: List[CodeAction])(using Context): UnusedSymbol = UnusedSymbol(i"unused import", actions) + def localDefs(using Context): UnusedSymbol = UnusedSymbol(i"unused local definition") + def explicitParams(using Context): UnusedSymbol = UnusedSymbol(i"unused explicit parameter") + def implicitParams(using Context): UnusedSymbol = UnusedSymbol(i"unused implicit parameter") + def privateMembers(using Context): UnusedSymbol = UnusedSymbol(i"unused private member") + def patVars(using Context): UnusedSymbol = UnusedSymbol(i"unused pattern variable") + def unsetLocals(using Context): UnusedSymbol = UnusedSymbol(i"unset local variable, consider using an immutable val instead") + def unsetPrivates(using Context): UnusedSymbol = UnusedSymbol(i"unset private variable, consider using an immutable val instead") class NonNamedArgumentInJavaAnnotation(using Context) extends SyntaxMsg(NonNamedArgumentInJavaAnnotationID): diff --git a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala index 75e859111932..c303c40485ce 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala @@ -28,7 +28,7 @@ import ExtractAPI.NonLocalClassSymbolsInCurrentUnits import scala.collection.mutable import scala.util.hashing.MurmurHash3 -import scala.util.chaining.* +import dotty.tools.dotc.util.chaining.* /** This phase sends a representation of the API of classes to sbt via callbacks. * diff --git a/compiler/src/dotty/tools/dotc/sbt/package.scala b/compiler/src/dotty/tools/dotc/sbt/package.scala index 1c6b38b07a84..8efa25569325 100644 --- a/compiler/src/dotty/tools/dotc/sbt/package.scala +++ b/compiler/src/dotty/tools/dotc/sbt/package.scala @@ -10,7 +10,6 @@ import interfaces.IncrementalCallback import dotty.tools.io.FileWriters.BufferingReporter import dotty.tools.dotc.core.Decorators.em -import scala.util.chaining.given import scala.util.control.NonFatal inline val TermNameHash = 1987 // 300th prime diff --git a/compiler/src/dotty/tools/dotc/semanticdb/SemanticSymbolBuilder.scala b/compiler/src/dotty/tools/dotc/semanticdb/SemanticSymbolBuilder.scala index 81f5d37f443f..84993b531c69 100644 --- a/compiler/src/dotty/tools/dotc/semanticdb/SemanticSymbolBuilder.scala +++ b/compiler/src/dotty/tools/dotc/semanticdb/SemanticSymbolBuilder.scala @@ -90,9 +90,15 @@ class SemanticSymbolBuilder: b.append('+').append(idx + 1) case _ => end find - val sig = sym.signature - val targetName = sym.targetName - find(sym => sym.signature == sig && sym.targetName == targetName) + try + val sig = sym.signature + val targetName = sym.targetName + find(sym => sym.signature == sig && sym.targetName == targetName) + catch + // sym.signature might not exist + // this solves tests/best-effort/compiler-semanticdb-crash + case _: MissingType if ctx.usedBestEffortTasty => + def addDescriptor(sym: Symbol): Unit = if sym.is(ModuleClass) then @@ -111,7 +117,7 @@ class SemanticSymbolBuilder: addName(b, sym.name) if sym.is(Package) then b.append('/') else if sym.isType || sym.isAllOf(JavaModule) then b.append('#') - else if sym.isOneOf(Method | Mutable) + else if sym.is(Method) || (sym.is(Mutable) && !sym.is(JavaDefined)) && (!sym.is(StableRealizable) || sym.isConstructor) then b.append('('); addOverloadIdx(sym); b.append(").") else b.append('.') diff --git a/compiler/src/dotty/tools/dotc/semanticdb/TypeOps.scala b/compiler/src/dotty/tools/dotc/semanticdb/TypeOps.scala index 4293ecd6ca43..2d98535657a2 100644 --- a/compiler/src/dotty/tools/dotc/semanticdb/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/semanticdb/TypeOps.scala @@ -9,13 +9,13 @@ import core.Annotations.Annotation import core.Flags import core.Names.Name import core.StdNames.tpnme -import scala.util.chaining.scalaUtilChainingOps import collection.mutable import dotty.tools.dotc.{semanticdb => s} import Scala3.{FakeSymbol, SemanticSymbol, WildcardTypeSymbol, TypeParamRefSymbol, TermParamRefSymbol, RefinementSymbol} import dotty.tools.dotc.core.Names.Designator +import dotty.tools.dotc.util.chaining.* class TypeOps: import SymbolScopeOps.* diff --git a/compiler/src/dotty/tools/dotc/staging/CrossStageSafety.scala b/compiler/src/dotty/tools/dotc/staging/CrossStageSafety.scala index 85f2e84429c3..b958b64f7c54 100644 --- a/compiler/src/dotty/tools/dotc/staging/CrossStageSafety.scala +++ b/compiler/src/dotty/tools/dotc/staging/CrossStageSafety.scala @@ -203,11 +203,19 @@ class CrossStageSafety extends TreeMapWithStages { /** Check level consistency of terms references */ private def checkLevelConsistency(tree: Ident | This)(using Context): Unit = + def isStatic(pre: Type)(using Context): Boolean = pre match + case pre: NamedType => + val sym = pre.currentSymbol + sym.is(Package) || sym.isStaticOwner && isStatic(pre.prefix) + case pre: ThisType => isStatic(pre.tref) + case _ => true new TypeTraverser { def traverse(tp: Type): Unit = tp match case tp @ TermRef(NoPrefix, _) if !tp.symbol.isStatic && level != levelOf(tp.symbol) => levelError(tp.symbol, tp, tree.srcPos) + case tp: ThisType if isStatic(tp) => + // static object (OK) case tp: ThisType if level != -1 && level != levelOf(tp.cls) => levelError(tp.cls, tp, tree.srcPos) case tp: AnnotatedType => @@ -215,7 +223,7 @@ class CrossStageSafety extends TreeMapWithStages { case _ if tp.typeSymbol.is(Package) => // OK case _ => - traverseChildren(tp) + traverseChildren(tp) }.traverse(tree.tpe) private def levelError(sym: Symbol, tp: Type, pos: SrcPos)(using Context): tp.type = { diff --git a/compiler/src/dotty/tools/dotc/transform/CheckShadowing.scala b/compiler/src/dotty/tools/dotc/transform/CheckShadowing.scala index 87d652bd9133..3adb3ab0ce7d 100644 --- a/compiler/src/dotty/tools/dotc/transform/CheckShadowing.scala +++ b/compiler/src/dotty/tools/dotc/transform/CheckShadowing.scala @@ -49,26 +49,22 @@ class CheckShadowing extends MiniPhase: override def description: String = CheckShadowing.description + override def isEnabled(using Context): Boolean = ctx.settings.Wshadow.value.nonEmpty + override def isRunnable(using Context): Boolean = - super.isRunnable && - ctx.settings.Wshadow.value.nonEmpty && - !ctx.isJava + super.isRunnable && ctx.settings.Wshadow.value.nonEmpty && !ctx.isJava - // Setup before the traversal override def prepareForUnit(tree: tpd.Tree)(using Context): Context = val data = ShadowingData() val fresh = ctx.fresh.setProperty(_key, data) shadowingDataApply(sd => sd.registerRootImports())(using fresh) - // Reporting on traversal's end override def transformUnit(tree: tpd.Tree)(using Context): tpd.Tree = shadowingDataApply(sd => reportShadowing(sd.getShadowingResult) ) tree - // MiniPhase traversal : - override def prepareForPackageDef(tree: tpd.PackageDef)(using Context): Context = shadowingDataApply(sd => sd.inNewScope()) ctx @@ -91,16 +87,16 @@ class CheckShadowing extends MiniPhase: ) override def prepareForTypeDef(tree: tpd.TypeDef)(using Context): Context = - if tree.symbol.isAliasType then // if alias, the parent is the current symbol - nestedTypeTraverser(tree.symbol).traverse(tree.rhs) - if tree.symbol.is(Param) then // if param, the parent is up - val owner = tree.symbol.owner + val sym = tree.symbol + if sym.isAliasType then // if alias, the parent is the current symbol + nestedTypeTraverser(sym).traverse(tree.rhs) + if sym.is(Param) then // if param, the parent is up + val owner = sym.owner val parent = if (owner.isConstructor) then owner.owner else owner nestedTypeTraverser(parent).traverse(tree.rhs)(using ctx.outer) - shadowingDataApply(sd => sd.registerCandidate(parent, tree)) - else - ctx - + if isValidTypeParamOwner(sym.owner) then + shadowingDataApply(sd => sd.registerCandidate(parent, tree)) + ctx override def transformPackageDef(tree: tpd.PackageDef)(using Context): tpd.Tree = shadowingDataApply(sd => sd.outOfScope()) @@ -115,13 +111,14 @@ class CheckShadowing extends MiniPhase: tree override def transformTypeDef(tree: tpd.TypeDef)(using Context): tpd.Tree = - if tree.symbol.is(Param) && isValidTypeParamOwner(tree.symbol.owner) then // Do not register for constructors the work is done for the Class owned equivalent TypeDef + // Do not register for constructors the work is done for the Class owned equivalent TypeDef + if tree.symbol.is(Param) && isValidTypeParamOwner(tree.symbol.owner) then shadowingDataApply(sd => sd.computeTypeParamShadowsFor(tree.symbol.owner)(using ctx.outer)) - if tree.symbol.isAliasType then // No need to start outer here, because the TypeDef reached here it's already the parent + // No need to start outer here, because the TypeDef reached here it's already the parent + if tree.symbol.isAliasType then shadowingDataApply(sd => sd.computeTypeParamShadowsFor(tree.symbol)(using ctx)) tree - // Helpers : private def isValidTypeParamOwner(owner: Symbol)(using Context): Boolean = !owner.isConstructor && !owner.is(Synthetic) && !owner.is(Exported) @@ -141,7 +138,7 @@ class CheckShadowing extends MiniPhase: override def traverse(tree: tpd.Tree)(using Context): Unit = tree match - case t:tpd.TypeDef => + case t: tpd.TypeDef => val newCtx = shadowingDataApply(sd => sd.registerCandidate(parent, t) ) @@ -157,7 +154,7 @@ class CheckShadowing extends MiniPhase: override def traverse(tree: tpd.Tree)(using Context): Unit = tree match - case t:tpd.Import => + case t: tpd.Import => val newCtx = shadowingDataApply(sd => sd.registerImport(t)) traverseChildren(tree)(using newCtx) case _ => @@ -240,7 +237,7 @@ object CheckShadowing: val declarationScope = ctx.effectiveScope val res = declarationScope.lookup(symbol.name) res match - case s: Symbol if s.isType => Some(s) + case s: Symbol if s.isType && s != symbol => Some(s) case _ => lookForUnitShadowedType(symbol)(using ctx.outer) /** Register if the valDef is a private declaration that shadows an inherited field */ @@ -310,4 +307,3 @@ object CheckShadowing: case class ShadowResult(warnings: List[ShadowWarning]) end CheckShadowing - diff --git a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala index 6e626fc5dd9e..0a1058782447 100644 --- a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala +++ b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala @@ -1,883 +1,915 @@ package dotty.tools.dotc.transform -import scala.annotation.tailrec -import scala.collection.mutable - -import dotty.tools.uncheckedNN -import dotty.tools.dotc.ast.tpd -import dotty.tools.dotc.core.Symbols.* -import dotty.tools.dotc.ast.tpd.{Inlined, TreeTraverser} -import dotty.tools.dotc.ast.untpd -import dotty.tools.dotc.ast.untpd.ImportSelector +import dotty.tools.dotc.ast.desugar.{ForArtifact, PatternVar} +import dotty.tools.dotc.ast.tpd.* +import dotty.tools.dotc.ast.untpd, untpd.ImportSelector import dotty.tools.dotc.config.ScalaSettings import dotty.tools.dotc.core.Contexts.* -import dotty.tools.dotc.core.Decorators.{em, i} -import dotty.tools.dotc.core.Denotations.SingleDenotation import dotty.tools.dotc.core.Flags.* -import dotty.tools.dotc.core.Phases.Phase -import dotty.tools.dotc.core.StdNames +import dotty.tools.dotc.core.Names.{Name, SimpleName, DerivedName, TermName, termName} +import dotty.tools.dotc.core.NameOps.{isAnonymousFunctionName, isReplWrapperName} +import dotty.tools.dotc.core.NameKinds.{BodyRetainerName, ContextBoundParamName, ContextFunctionParamName, WildcardParamName} +import dotty.tools.dotc.core.StdNames.nme +import dotty.tools.dotc.core.Symbols.{ClassSymbol, NoSymbol, Symbol, defn, isDeprecated, requiredClass, requiredModule} +import dotty.tools.dotc.core.Types.* import dotty.tools.dotc.report -import dotty.tools.dotc.reporting.Message -import dotty.tools.dotc.reporting.UnusedSymbol as UnusedSymbolMessage -import dotty.tools.dotc.typer.ImportInfo -import dotty.tools.dotc.util.{Property, SrcPos} -import dotty.tools.dotc.core.Mode -import dotty.tools.dotc.core.Types.{AnnotatedType, ConstantType, NoType, TermRef, Type, TypeTraverser} -import dotty.tools.dotc.core.Flags.flagsString -import dotty.tools.dotc.core.Flags -import dotty.tools.dotc.core.Names.{Name, TermName} -import dotty.tools.dotc.core.NameOps.isReplWrapperName +import dotty.tools.dotc.reporting.{CodeAction, UnusedSymbol} +import dotty.tools.dotc.rewrites.Rewrites import dotty.tools.dotc.transform.MegaPhase.MiniPhase -import dotty.tools.dotc.core.Annotations -import dotty.tools.dotc.core.Definitions -import dotty.tools.dotc.core.NameKinds.WildcardParamName -import dotty.tools.dotc.core.Symbols.Symbol -import dotty.tools.dotc.core.StdNames.nme -import dotty.tools.dotc.util.Spans.Span -import scala.math.Ordering +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.Chars.{isLineBreakChar, isWhitespace} +import dotty.tools.dotc.util.chaining.* +import java.util.IdentityHashMap -/** - * A compiler phase that checks for unused imports or definitions - * - * Basically, it gathers definition/imports and their usage. If a - * definition/imports does not have any usage, then it is reported. - */ -class CheckUnused private (phaseMode: CheckUnused.PhaseMode, suffix: String, _key: Property.Key[CheckUnused.UnusedData]) extends MiniPhase: - import CheckUnused.* - import UnusedData.* - - private inline def unusedDataApply[U](inline f: UnusedData => U)(using Context): Context = - ctx.property(_key) match - case Some(ud) => f(ud) - case None => () - ctx +import scala.collection.mutable, mutable.{ArrayBuilder, ListBuffer, Stack} - override def phaseName: String = CheckUnused.phaseNamePrefix + suffix +import CheckUnused.* - override def description: String = CheckUnused.description +/** A compiler phase that checks for unused imports or definitions. + */ +class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPhase: - override def isRunnable(using Context): Boolean = - super.isRunnable && - ctx.settings.WunusedHas.any && - !ctx.isJava + override def phaseName: String = s"checkUnused$suffix" - // ========== SETUP ============ + override def description: String = "check for unused elements" - override def prepareForUnit(tree: tpd.Tree)(using Context): Context = - val data = UnusedData() - tree.getAttachment(_key).foreach(oldData => - data.unusedAggregate = oldData.unusedAggregate - ) - val fresh = ctx.fresh.setProperty(_key, data) - tree.putAttachment(_key, data) - fresh + override def isEnabled(using Context): Boolean = ctx.settings.WunusedHas.any - // ========== END + REPORTING ========== + override def isRunnable(using Context): Boolean = super.isRunnable && ctx.settings.WunusedHas.any && !ctx.isJava - override def transformUnit(tree: tpd.Tree)(using Context): tpd.Tree = - unusedDataApply { ud => - ud.finishAggregation() - if(phaseMode == PhaseMode.Report) then - ud.unusedAggregate.foreach(reportUnused) - } + override def prepareForUnit(tree: Tree)(using Context): Context = + val infos = tree.getAttachment(refInfosKey).getOrElse: + RefInfos().tap(tree.withAttachment(refInfosKey, _)) + ctx.fresh.setProperty(refInfosKey, infos) + override def transformUnit(tree: Tree)(using Context): tree.type = + if phaseMode == PhaseMode.Report then + reportUnused() + tree.removeAttachment(refInfosKey) tree - // ========== MiniPhase Prepare ========== - override def prepareForOther(tree: tpd.Tree)(using Context): Context = - // A standard tree traverser covers cases not handled by the Mega/MiniPhase - traverser.traverse(tree) - ctx - - override def prepareForInlined(tree: tpd.Inlined)(using Context): Context = - traverser.traverse(tree.call) - ctx - - override def prepareForIdent(tree: tpd.Ident)(using Context): Context = + override def transformIdent(tree: Ident)(using Context): tree.type = if tree.symbol.exists then - unusedDataApply { ud => - @tailrec - def loopOnNormalizedPrefixes(prefix: Type, depth: Int): Unit = - // limit to 10 as failsafe for the odd case where there is an infinite cycle - if depth < 10 && prefix.exists then - ud.registerUsed(prefix.classSymbol, None) - loopOnNormalizedPrefixes(prefix.normalizedPrefix, depth + 1) - - loopOnNormalizedPrefixes(tree.typeOpt.normalizedPrefix, depth = 0) - ud.registerUsed(tree.symbol, Some(tree.name)) - } + // if in an inline expansion, resolve at summonInline (synthetic pos) or in an enclosing call site + val resolving = + refInfos.inlined.isEmpty + || tree.srcPos.isZeroExtentSynthetic + || refInfos.inlined.exists(_.sourcePos.contains(tree.srcPos.sourcePos)) + if resolving && !ignoreTree(tree) then + resolveUsage(tree.symbol, tree.name, tree.typeOpt.importPrefix.skipPackageObject) else if tree.hasType then - unusedDataApply(_.registerUsed(tree.tpe.classSymbol, Some(tree.name))) + resolveUsage(tree.tpe.classSymbol, tree.name, tree.tpe.importPrefix.skipPackageObject) + tree + + // import x.y; y may be rewritten x.y, also import x.z as y + override def transformSelect(tree: Select)(using Context): tree.type = + val name = tree.removeAttachment(OriginalName).getOrElse(nme.NO_NAME) + if tree.span.isSynthetic && tree.symbol == defn.TypeTest_unapply then + tree.qualifier.tpe.underlying.finalResultType match + case AppliedType(_, args) => // tycon.typeSymbol == defn.TypeTestClass + val res = args(1) // T in TypeTest[-S, T] + val target = res.dealias.typeSymbol + resolveUsage(target, target.name, res.importPrefix.skipPackageObject) // case _: T => + case _ => + else if tree.qualifier.span.isSynthetic || name.exists(_ != tree.symbol.name) then + if !ignoreTree(tree) then + resolveUsage(tree.symbol, name, tree.qualifier.tpe) else - ctx - - override def prepareForSelect(tree: tpd.Select)(using Context): Context = - val name = tree.removeAttachment(OriginalName) - unusedDataApply(_.registerUsed(tree.symbol, name, includeForImport = tree.qualifier.span.isSynthetic)) - - override def prepareForBlock(tree: tpd.Block)(using Context): Context = - pushInBlockTemplatePackageDef(tree) - - override def prepareForTemplate(tree: tpd.Template)(using Context): Context = - pushInBlockTemplatePackageDef(tree) - - override def prepareForPackageDef(tree: tpd.PackageDef)(using Context): Context = - pushInBlockTemplatePackageDef(tree) - - override def prepareForValDef(tree: tpd.ValDef)(using Context): Context = - unusedDataApply{ud => - // do not register the ValDef generated for `object` - traverseAnnotations(tree.symbol) - if !tree.symbol.is(Module) then - ud.registerDef(tree) - if tree.name.startsWith("derived$") && tree.typeOpt != NoType then - ud.registerUsed(tree.typeOpt.typeSymbol, None, isDerived = true) - ud.addIgnoredUsage(tree.symbol) - } - - override def prepareForDefDef(tree: tpd.DefDef)(using Context): Context = - unusedDataApply: ud => - if !tree.symbol.is(Private) then - tree.termParamss.flatten.foreach { p => - ud.addIgnoredParam(p.symbol) - } - ud.registerTrivial(tree) - traverseAnnotations(tree.symbol) - ud.registerDef(tree) - ud.addIgnoredUsage(tree.symbol) - - override def prepareForTypeDef(tree: tpd.TypeDef)(using Context): Context = - unusedDataApply: ud => - traverseAnnotations(tree.symbol) - if !tree.symbol.is(Param) then // Ignore type parameter (as Scala 2) - ud.registerDef(tree) - ud.addIgnoredUsage(tree.symbol) - - override def prepareForBind(tree: tpd.Bind)(using Context): Context = - traverseAnnotations(tree.symbol) - unusedDataApply(_.registerPatVar(tree)) + refUsage(tree.symbol) + tree - override def prepareForTypeTree(tree: tpd.TypeTree)(using Context): Context = - if !tree.isInstanceOf[tpd.InferredTypeTree] then typeTraverser(unusedDataApply).traverse(tree.tpe) - ctx + override def transformLiteral(tree: Literal)(using Context): tree.type = + tree.getAttachment(Typer.AdaptedTree).foreach(transformAllDeep) + tree - override def prepareForAssign(tree: tpd.Assign)(using Context): Context = - unusedDataApply{ ud => - val sym = tree.lhs.symbol - if sym.exists then - ud.registerSetVar(sym) - } + override def prepareForCaseDef(tree: CaseDef)(using Context): Context = + nowarner.traverse(tree.pat) + ctx - // ========== MiniPhase Transform ========== + override def prepareForApply(tree: Apply)(using Context): Context = + // ignore tupling of for assignments, as they are not usages of vars + if tree.hasAttachment(ForArtifact) then + tree match + case Apply(TypeApply(Select(fun, nme.apply), _), args) => + if fun.symbol.is(Module) && defn.isTupleClass(fun.symbol.companionClass) then + args.foreach(_.withAttachment(ForArtifact, ())) + case _ => + ctx - override def transformBlock(tree: tpd.Block)(using Context): tpd.Tree = - popOutBlockTemplatePackageDef() + override def prepareForAssign(tree: Assign)(using Context): Context = + tree.lhs.putAttachment(Ignore, ()) // don't take LHS reference as a read + ctx + override def transformAssign(tree: Assign)(using Context): tree.type = + tree.lhs.removeAttachment(Ignore) + val sym = tree.lhs.symbol + if sym.exists then + refInfos.asss.addOne(sym) tree - override def transformTemplate(tree: tpd.Template)(using Context): tpd.Tree = - popOutBlockTemplatePackageDef() + override def prepareForMatch(tree: Match)(using Context): Context = + // exonerate case.pat against tree.selector (simple var pat only for now) + tree.selector match + case Ident(nm) => tree.cases.foreach(k => allowVariableBindings(List(nm), List(k.pat))) + case _ => + ctx + override def transformMatch(tree: Match)(using Context): tree.type = + if tree.isInstanceOf[InlineMatch] && tree.selector.isEmpty then + val sf = defn.Compiletime_summonFrom + resolveUsage(sf, sf.name, NoPrefix) tree - override def transformPackageDef(tree: tpd.PackageDef)(using Context): tpd.Tree = - popOutBlockTemplatePackageDef() + override def transformTypeTree(tree: TypeTree)(using Context): tree.type = + tree.tpe match + case AnnotatedType(_, annot) => transformAllDeep(annot.tree) + case tpt if !tree.isInferred && tpt.typeSymbol.exists => resolveUsage(tpt.typeSymbol, tpt.typeSymbol.name, NoPrefix) + case _ => tree - override def transformValDef(tree: tpd.ValDef)(using Context): tpd.Tree = - unusedDataApply(_.removeIgnoredUsage(tree.symbol)) + override def prepareForInlined(tree: Inlined)(using Context): Context = + refInfos.inlined.push(tree.call.srcPos) + ctx + override def transformInlined(tree: Inlined)(using Context): tree.type = + val _ = refInfos.inlined.pop() + if !tree.call.isEmpty && phaseMode.eq(PhaseMode.Aggregate) then + transformAllDeep(tree.call) tree - override def transformDefDef(tree: tpd.DefDef)(using Context): tpd.Tree = - unusedDataApply(_.removeIgnoredUsage(tree.symbol)) + override def prepareForBind(tree: Bind)(using Context): Context = + refInfos.register(tree) + ctx + + override def prepareForValDef(tree: ValDef)(using Context): Context = + if !tree.symbol.is(Deferred) && tree.rhs.symbol != defn.Predef_undefined then + refInfos.register(tree) + ctx + override def transformValDef(tree: ValDef)(using Context): tree.type = + traverseAnnotations(tree.symbol) + if tree.name.startsWith("derived$") && tree.hasType then + def loop(t: Tree): Unit = t match + case Ident(name) => + val target = + val ts0 = t.tpe.typeSymbol + if ts0.is(ModuleClass) then ts0.companionModule else ts0 + resolveUsage(target, name, t.tpe.underlyingPrefix.skipPackageObject) + case Select(t, _) => loop(t) + case _ => + tree.getAttachment(OriginalTypeClass).foreach(loop) + if tree.symbol.isAllOf(DeferredGivenFlags) then + resolveUsage(defn.Compiletime_deferred, nme.NO_NAME, NoPrefix) tree - override def transformTypeDef(tree: tpd.TypeDef)(using Context): tpd.Tree = - unusedDataApply(_.removeIgnoredUsage(tree.symbol)) + override def prepareForDefDef(tree: DefDef)(using Context): Context = + def trivial = tree.symbol.is(Deferred) || isUnconsuming(tree.rhs) + def nontrivial = tree.symbol.isConstructor || tree.symbol.isAnonymousFunction + if !nontrivial && trivial then refInfos.skip.addOne(tree.symbol) + if tree.symbol.is(Inline) then + refInfos.inliners += 1 + else if !tree.symbol.is(Deferred) && tree.rhs.symbol != defn.Predef_undefined then + refInfos.register(tree) + ctx + override def transformDefDef(tree: DefDef)(using Context): tree.type = + traverseAnnotations(tree.symbol) + if tree.symbol.is(Inline) then + refInfos.inliners -= 1 + if tree.symbol.isAllOf(DeferredGivenFlags) then + resolveUsage(defn.Compiletime_deferred, nme.NO_NAME, NoPrefix) tree + override def transformTypeDef(tree: TypeDef)(using Context): tree.type = + traverseAnnotations(tree.symbol) + if !tree.symbol.is(Param) then // type parameter to do? + refInfos.register(tree) + tree - // ---------- MiniPhase HELPERS ----------- + override def prepareForTemplate(tree: Template)(using Context): Context = + ctx.fresh.setProperty(resolvedKey, Resolved()) + + override def prepareForPackageDef(tree: PackageDef)(using Context): Context = + ctx.fresh.setProperty(resolvedKey, Resolved()) + + override def prepareForStats(trees: List[Tree])(using Context): Context = + ctx.fresh.setProperty(resolvedKey, Resolved()) + + override def transformOther(tree: Tree)(using Context): tree.type = + tree match + case imp: Import => + if phaseMode eq PhaseMode.Aggregate then + refInfos.register(imp) + transformAllDeep(imp.expr) + for selector <- imp.selectors do + if selector.isGiven then + selector.bound match + case untpd.TypedSplice(bound) => transformAllDeep(bound) + case _ => + case AppliedTypeTree(tpt, args) => + transformAllDeep(tpt) + args.foreach(transformAllDeep) + case RefinedTypeTree(tpt, refinements) => + transformAllDeep(tpt) + refinements.foreach(transformAllDeep) + case LambdaTypeTree(tparams, body) => + tparams.foreach(transformAllDeep) + transformAllDeep(body) + case SingletonTypeTree(ref) => + // selftype of object is not a usage + val moduleSelfRef = ctx.owner.is(Module) && ctx.owner == tree.symbol.companionModule.moduleClass + if !moduleSelfRef then + transformAllDeep(ref) + case TypeBoundsTree(lo, hi, alias) => + transformAllDeep(lo) + transformAllDeep(hi) + transformAllDeep(alias) + case tree: NamedArg => transformAllDeep(tree.arg) + case Annotated(arg, annot) => + transformAllDeep(arg) + transformAllDeep(annot) + case Quote(body, tags) => + transformAllDeep(body) + tags.foreach(transformAllDeep) + case Splice(expr) => + transformAllDeep(expr) + case QuotePattern(bindings, body, quotes) => + bindings.foreach(transformAllDeep) + transformAllDeep(body) + transformAllDeep(quotes) + case SplicePattern(body, typeargs, args) => + transformAllDeep(body) + typeargs.foreach(transformAllDeep) + args.foreach(transformAllDeep) + case MatchTypeTree(bound, selector, cases) => + transformAllDeep(bound) + transformAllDeep(selector) + cases.foreach(transformAllDeep) + case ByNameTypeTree(result) => + transformAllDeep(result) + //case _: InferredTypeTree => // do nothing + //case _: Export => // nothing to do + //case _ if tree.isType => + case _ => + tree - private def pushInBlockTemplatePackageDef(tree: tpd.Block | tpd.Template | tpd.PackageDef)(using Context): Context = - unusedDataApply { ud => - ud.pushScope(UnusedData.ScopeType.fromTree(tree)) - } - ctx + private def traverseAnnotations(sym: Symbol)(using Context): Unit = + for annot <- sym.denot.annotations do + transformAllDeep(annot.tree) - private def popOutBlockTemplatePackageDef()(using Context): Context = - unusedDataApply { ud => - ud.popScope() - } - ctx + // if sym is not an enclosing element, record the reference + def refUsage(sym: Symbol)(using Context): Unit = + if !ctx.outersIterator.exists(cur => cur.owner eq sym) then + refInfos.refs.addOne(sym) - /** - * This traverse is the **main** component of this phase + /** Look up a reference in enclosing contexts to determine whether it was introduced by a definition or import. + * The binding of highest precedence must then be correct. * - * It traverses the tree and gathers the data in the - * corresponding context property + * Unqualified locals and fully qualified globals are neither imported nor in scope; + * e.g., in `scala.Int`, `scala` is in scope for typer, but here we reverse-engineer the attribution. + * For Select, lint does not look up `.scala` (so top-level syms look like magic) but records `scala.Int`. + * For Ident, look-up finds the root import as usual. A competing import is OK because higher precedence. */ - private def traverser = new TreeTraverser: - import tpd.* - import UnusedData.ScopeType - - /* Register every imports, definition and usage */ - override def traverse(tree: tpd.Tree)(using Context): Unit = - val newCtx = if tree.symbol.exists then ctx.withOwner(tree.symbol) else ctx - tree match - case imp: tpd.Import => - unusedDataApply(_.registerImport(imp)) - imp.selectors.filter(_.isGiven).map(_.bound).collect { - case untpd.TypedSplice(tree1) => tree1 - }.foreach(traverse(_)(using newCtx)) - traverseChildren(tree)(using newCtx) - case ident: Ident => - prepareForIdent(ident) - traverseChildren(tree)(using newCtx) - case sel: Select => - prepareForSelect(sel) - traverseChildren(tree)(using newCtx) - case tree: (tpd.Block | tpd.Template | tpd.PackageDef) => - //! DIFFERS FROM MINIPHASE - pushInBlockTemplatePackageDef(tree) - traverseChildren(tree)(using newCtx) - popOutBlockTemplatePackageDef() - case t: tpd.ValDef => - prepareForValDef(t) - traverseChildren(tree)(using newCtx) - transformValDef(t) - case t: tpd.DefDef => - prepareForDefDef(t) - traverseChildren(tree)(using newCtx) - transformDefDef(t) - case t: tpd.TypeDef => - prepareForTypeDef(t) - traverseChildren(tree)(using newCtx) - transformTypeDef(t) - case t: tpd.Bind => - prepareForBind(t) - traverseChildren(tree)(using newCtx) - case t:tpd.Assign => - prepareForAssign(t) - traverseChildren(tree) - case _: tpd.InferredTypeTree => - case t@tpd.RefinedTypeTree(tpt, refinements) => - //! DIFFERS FROM MINIPHASE - typeTraverser(unusedDataApply).traverse(t.tpe) - traverse(tpt)(using newCtx) - case t@tpd.TypeTree() => - //! DIFFERS FROM MINIPHASE - typeTraverser(unusedDataApply).traverse(t.tpe) - traverseChildren(tree)(using newCtx) - case _ => - //! DIFFERS FROM MINIPHASE - traverseChildren(tree)(using newCtx) - end traverse - end traverser - - /** This is a type traverser which catch some special Types not traversed by the term traverser above */ - private def typeTraverser(dt: (UnusedData => Any) => Unit)(using Context) = new TypeTraverser: - override def traverse(tp: Type): Unit = - if tp.typeSymbol.exists then dt(_.registerUsed(tp.typeSymbol, Some(tp.typeSymbol.name))) - tp match - case AnnotatedType(_, annot) => - dt(_.registerUsed(annot.symbol, None)) - traverseChildren(tp) - case _ => - traverseChildren(tp) - - /** This traverse the annotations of the symbol */ - private def traverseAnnotations(sym: Symbol)(using Context): Unit = - sym.denot.annotations.foreach(annot => traverser.traverse(annot.tree)) - - - /** Do the actual reporting given the result of the anaylsis */ - private def reportUnused(res: UnusedData.UnusedResult)(using Context): Unit = - res.warnings.toList.sortBy(_.pos.span.point)(using Ordering[Int]).foreach { s => - s match - case UnusedSymbol(t, _, WarnTypes.Imports) => - report.warning(UnusedSymbolMessage.imports, t) - case UnusedSymbol(t, _, WarnTypes.LocalDefs) => - report.warning(UnusedSymbolMessage.localDefs, t) - case UnusedSymbol(t, _, WarnTypes.ExplicitParams) => - report.warning(UnusedSymbolMessage.explicitParams, t) - case UnusedSymbol(t, _, WarnTypes.ImplicitParams) => - report.warning(UnusedSymbolMessage.implicitParams, t) - case UnusedSymbol(t, _, WarnTypes.PrivateMembers) => - report.warning(UnusedSymbolMessage.privateMembers, t) - case UnusedSymbol(t, _, WarnTypes.PatVars) => - report.warning(UnusedSymbolMessage.patVars, t) - case UnusedSymbol(t, _, WarnTypes.UnsetLocals) => - report.warning("unset local variable, consider using an immutable val instead", t) - case UnusedSymbol(t, _, WarnTypes.UnsetPrivates) => - report.warning("unset private variable, consider using an immutable val instead", t) - } - + def resolveUsage(sym: Symbol, name: Name, prefix: Type)(using Context): Unit = + import PrecedenceLevels.* + + def matchingSelector(info: ImportInfo): ImportSelector | Null = + val qtpe = info.site + def hasAltMember(nm: Name) = qtpe.member(nm).hasAltWith(_.symbol == sym) + def loop(sels: List[ImportSelector]): ImportSelector | Null = sels match + case sel :: sels => + val matches = + if sel.isWildcard then + // the qualifier must have the target symbol as a member + hasAltMember(sym.name) && { + if sel.isGiven then // Further check that the symbol is a given or implicit and conforms to the bound + sym.isOneOf(GivenOrImplicit) + && (sel.bound.isEmpty || sym.info.finalResultType <:< sel.boundTpe) + && (prefix.eq(NoPrefix) || qtpe =:= prefix) + else + !sym.is(Given) // Normal wildcard, check that the symbol is not a given (but can be implicit) + } + else + // if there is an explicit name, it must match + !name.exists(_.toTermName != sel.rename) + && (prefix.eq(NoPrefix) || qtpe =:= prefix) + && (hasAltMember(sel.name) || hasAltMember(sel.name.toTypeName)) + if matches then sel else loop(sels) + case nil => null + loop(info.selectors) + + def checkMember(ctxsym: Symbol): Boolean = + ctxsym.isClass && sym.owner.isClass + && ctxsym.thisType.baseClasses.contains(sym.owner) + && ctxsym.thisType.member(sym.name).hasAltWith(d => d.containsSym(sym) && !name.exists(_ != d.name)) + + // Attempt to cache a result at the given context. Not all contexts bear a cache, including NoContext. + // If there is already any result for the name and prefix, do nothing. + def addCached(where: Context, result: Precedence): Unit = + if where.moreProperties ne null then + where.property(resolvedKey) match + case Some(resolved) => + resolved.record(sym, name, prefix, result) + case none => + + // Avoid spurious NoSymbol and also primary ctors which are never warned about. + if !sym.exists || sym.isPrimaryConstructor then return + + // Find the innermost, highest precedence. Contexts have no nesting levels but assume correctness. + // If the sym is an enclosing definition (the owner of a context), it does not count toward usages. + val isLocal = sym.isLocalToBlock + var candidate: Context = NoContext + var cachePoint: Context = NoContext // last context with Resolved cache + var importer: ImportSelector | Null = null // non-null for import context + var precedence = NoPrecedence // of current resolution + var done = false + var cached = false + val ctxs = ctx.outersIterator + while !done && ctxs.hasNext do + val cur = ctxs.next() + if cur.owner eq sym then + addCached(cachePoint, Definition) + return // found enclosing definition + else if isLocal then + if cur.owner eq sym.owner then + done = true // for local def, just checking that it is not enclosing + else + val cachedPrecedence = + cur.property(resolvedKey) match + case Some(resolved) => + // conservative, cache must be nested below the result context + if precedence.isNone then + cachePoint = cur // no result yet, and future result could be cached here + resolved.hasRecord(sym, name, prefix) + case none => NoPrecedence + cached = !cachedPrecedence.isNone + if cached then + // if prefer cached precedence, then discard previous result + if precedence.weakerThan(cachedPrecedence) then + candidate = NoContext + importer = null + cachePoint = cur // actual cache context + precedence = cachedPrecedence // actual cached precedence + done = true + else if cur.isImportContext then + val sel = matchingSelector(cur.importInfo.nn) + if sel != null then + if cur.importInfo.nn.isRootImport then + if precedence.weakerThan(OtherUnit) then + precedence = OtherUnit + candidate = cur + importer = sel + done = true + else if sel.isWildcard then + if precedence.weakerThan(Wildcard) then + precedence = Wildcard + candidate = cur + importer = sel + else + if precedence.weakerThan(NamedImport) then + precedence = NamedImport + candidate = cur + importer = sel + else if checkMember(cur.owner) then + if sym.srcPos.sourcePos.source == ctx.source then + precedence = Definition + candidate = cur + importer = null // ignore import in same scope; we can't check nesting level + done = true + else if precedence.weakerThan(OtherUnit) then + precedence = OtherUnit + candidate = cur + end while + // record usage and possibly an import + refInfos.refs.addOne(sym) + if candidate != NoContext && candidate.isImportContext && importer != null then + refInfos.sels.put(importer, ()) + // possibly record that we have performed this look-up + // if no result was found, take it as Definition (local or rooted head of fully qualified path) + val adjusted = if precedence.isNone then Definition else precedence + if !cached && (cachePoint ne NoContext) then + addCached(cachePoint, adjusted) + if cachePoint ne ctx then + addCached(ctx, adjusted) // at this ctx, since cachePoint may be far up the outer chain + end resolveUsage end CheckUnused object CheckUnused: - val phaseNamePrefix: String = "checkUnused" - val description: String = "check for unused elements" enum PhaseMode: case Aggregate case Report - private enum WarnTypes: - case Imports - case LocalDefs - case ExplicitParams - case ImplicitParams - case PrivateMembers - case PatVars - case UnsetLocals - case UnsetPrivates - - /** - * The key used to retrieve the "unused entity" analysis metadata, - * from the compilation `Context` - */ - private val _key = Property.StickyKey[UnusedData] + val refInfosKey = Property.StickyKey[RefInfos] - val OriginalName = Property.StickyKey[Name] + val resolvedKey = Property.Key[Resolved] - class PostTyper extends CheckUnused(PhaseMode.Aggregate, "PostTyper", _key) + inline def refInfos(using Context): RefInfos = ctx.property(refInfosKey).get - class PostInlining extends CheckUnused(PhaseMode.Report, "PostInlining", _key) + inline def resolved(using Context): Resolved = + ctx.property(resolvedKey) match + case Some(res) => res + case _ => throw new MatchError("no Resolved for context") - /** - * A stateful class gathering the infos on : - * - imports - * - definitions - * - usage - */ - private class UnusedData: - import collection.mutable.{Set => MutSet, Map => MutMap, Stack => MutStack, ListBuffer => MutList} - import UnusedData.* - - /** The current scope during the tree traversal */ - val currScopeType: MutStack[ScopeType] = MutStack(ScopeType.Other) - - var unusedAggregate: Option[UnusedResult] = None - - /* IMPORTS */ - private val impInScope = MutStack(MutList[ImportSelectorData]()) - /** - * We store the symbol along with their accessibility without import. - * Accessibility to their definition in outer context/scope - * - * See the `isAccessibleAsIdent` extension method below in the file - */ - private val usedInScope = MutStack(MutSet[(Symbol, Option[Name], Boolean)]()) - private val usedInPosition = MutMap.empty[Name, MutSet[Symbol]] - /* unused import collected during traversal */ - private val unusedImport = MutList.empty[ImportSelectorData] - - /* LOCAL DEF OR VAL / Private Def or Val / Pattern variables */ - private val localDefInScope = MutList.empty[tpd.MemberDef] - private val privateDefInScope = MutList.empty[tpd.MemberDef] - private val explicitParamInScope = MutList.empty[tpd.MemberDef] - private val implicitParamInScope = MutList.empty[tpd.MemberDef] - private val patVarsInScope = MutList.empty[tpd.Bind] - - /** All variables sets*/ - private val setVars = MutSet[Symbol]() - - /** All used symbols */ - private val usedDef = MutSet[Symbol]() - /** Do not register as used */ - private val doNotRegister = MutSet[Symbol]() - - /** Trivial definitions, avoid registering params */ - private val trivialDefs = MutSet[Symbol]() - - private val paramsToSkip = MutSet[Symbol]() - - - def finishAggregation(using Context)(): Unit = - val unusedInThisStage = this.getUnused - this.unusedAggregate match { - case None => - this.unusedAggregate = Some(unusedInThisStage) - case Some(prevUnused) => - val intersection = unusedInThisStage.warnings.intersect(prevUnused.warnings) - this.unusedAggregate = Some(UnusedResult(intersection)) - } - - - /** - * Register a found (used) symbol along with its name - * - * The optional name will be used to target the right import - * as the same element can be imported with different renaming - */ - def registerUsed(sym: Symbol, name: Option[Name], includeForImport: Boolean = true, isDerived: Boolean = false)(using Context): Unit = - if sym.exists && !isConstructorOfSynth(sym) && !doNotRegister(sym) then - if sym.isConstructor then - registerUsed(sym.owner, None, includeForImport) // constructor are "implicitly" imported with the class - else - // If the symbol is accessible in this scope without an import, do not register it for unused import analysis - val includeForImport1 = - includeForImport - && (name.exists(_.toTermName != sym.name.toTermName) || !sym.isAccessibleAsIdent) - - def addIfExists(sym: Symbol): Unit = - if sym.exists then - usedDef += sym - if includeForImport1 then - usedInScope.top += ((sym, name, isDerived)) - addIfExists(sym) - addIfExists(sym.companionModule) - addIfExists(sym.companionClass) - if sym.sourcePos.exists then - for n <- name do - usedInPosition.getOrElseUpdate(n, MutSet.empty) += sym - - /** Register a symbol that should be ignored */ - def addIgnoredUsage(sym: Symbol)(using Context): Unit = - doNotRegister ++= sym.everySymbol - - /** Remove a symbol that shouldn't be ignored anymore */ - def removeIgnoredUsage(sym: Symbol)(using Context): Unit = - doNotRegister --= sym.everySymbol - - def addIgnoredParam(sym: Symbol)(using Context): Unit = - paramsToSkip += sym - - /** Register an import */ - def registerImport(imp: tpd.Import)(using Context): Unit = - if - !tpd.languageImport(imp.expr).nonEmpty - && !imp.isGeneratedByEnum - && !isTransparentAndInline(imp) - && currScopeType.top != ScopeType.ReplWrapper // #18383 Do not report top-level import's in the repl as unused - then - val qualTpe = imp.expr.tpe - - // Put wildcard imports at the end, because they have lower priority within one Import - val reorderdSelectors = - val (wildcardSels, nonWildcardSels) = imp.selectors.partition(_.isWildcard) - nonWildcardSels ::: wildcardSels - - val excludedMembers: mutable.Set[TermName] = mutable.Set.empty - - val newDataInScope = - for sel <- reorderdSelectors yield - val data = new ImportSelectorData(qualTpe, sel) - if shouldSelectorBeReported(imp, sel) || isImportExclusion(sel) || isImportIgnored(imp, sel) then - // Immediately mark the selector as used - data.markUsed() - if isImportExclusion(sel) then - excludedMembers += sel.name - if sel.isWildcard && excludedMembers.nonEmpty then - // mark excluded members for the wildcard import - data.markExcluded(excludedMembers.toSet) - data - impInScope.top.appendAll(newDataInScope) - end registerImport - - /** Register (or not) some `val` or `def` according to the context, scope and flags */ - def registerDef(memDef: tpd.MemberDef)(using Context): Unit = - if memDef.isValidMemberDef && !isDefIgnored(memDef) then - if memDef.isValidParam then - if memDef.symbol.isOneOf(GivenOrImplicit) then - if !paramsToSkip.contains(memDef.symbol) then - implicitParamInScope += memDef - else if !paramsToSkip.contains(memDef.symbol) then - explicitParamInScope += memDef - else if currScopeType.top == ScopeType.Local then - localDefInScope += memDef - else if memDef.shouldReportPrivateDef then - privateDefInScope += memDef - - /** Register pattern variable */ - def registerPatVar(patvar: tpd.Bind)(using Context): Unit = - if !patvar.symbol.isUnusedAnnot then - patVarsInScope += patvar - - /** enter a new scope */ - def pushScope(newScopeType: ScopeType): Unit = - // unused imports : - currScopeType.push(newScopeType) - impInScope.push(MutList()) - usedInScope.push(MutSet()) - - def registerSetVar(sym: Symbol): Unit = - setVars += sym - - /** - * leave the current scope and do : - * - * - If there are imports in this scope check for unused ones - */ - def popScope()(using Context): Unit = - currScopeType.pop() - val usedInfos = usedInScope.pop() - val selDatas = impInScope.pop() - - for usedInfo <- usedInfos do - val (sym, optName, isDerived) = usedInfo - val usedData = selDatas.find { selData => - sym.isInImport(selData, optName, isDerived) - } - usedData match - case Some(data) => - data.markUsed() - case None => - // Propagate the symbol one level up - if usedInScope.nonEmpty then - usedInScope.top += usedInfo - end for // each in `used` - - for selData <- selDatas do - if !selData.isUsed then - unusedImport += selData - end popScope - - /** - * Leave the scope and return a `List` of unused `ImportSelector`s - * - * The given `List` is sorted by line and then column of the position - */ + /** Attachment holding the name of an Ident as written by the user. */ + val OriginalName = Property.StickyKey[Name] - def getUnused(using Context): UnusedResult = - popScope() + /** Suppress warning in a tree, such as a patvar name allowed by special convention. */ + val NoWarn = Property.StickyKey[Unit] - def isUsedInPosition(name: Name, span: Span): Boolean = - usedInPosition.get(name) match - case Some(syms) => syms.exists(sym => span.contains(sym.span)) - case None => false + /** Ignore reference. */ + val Ignore = Property.StickyKey[Unit] - val sortedImp = - if ctx.settings.WunusedHas.imports || ctx.settings.WunusedHas.strictNoImplicitWarn then - unusedImport.toList - .map(d => UnusedSymbol(d.selector.srcPos, d.selector.name, WarnTypes.Imports)) - else - Nil - // Partition to extract unset local variables from usedLocalDefs - val (usedLocalDefs, unusedLocalDefs) = - if ctx.settings.WunusedHas.locals then - localDefInScope.toList.partition(d => d.symbol.usedDefContains) - else - (Nil, Nil) - val sortedLocalDefs = - unusedLocalDefs - .filterNot(d => isUsedInPosition(d.symbol.name, d.span)) - .filterNot(d => containsSyntheticSuffix(d.symbol)) - .map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.LocalDefs)) - val unsetLocalDefs = usedLocalDefs.filter(isUnsetVarDef).map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.UnsetLocals)).toList - - val sortedExplicitParams = - if ctx.settings.WunusedHas.explicits then - explicitParamInScope.toList - .filterNot(d => d.symbol.usedDefContains) - .filterNot(d => isUsedInPosition(d.symbol.name, d.span)) - .filterNot(d => containsSyntheticSuffix(d.symbol)) - .map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.ExplicitParams)) - else - Nil - val sortedImplicitParams = - if ctx.settings.WunusedHas.implicits then - implicitParamInScope.toList - .filterNot(d => d.symbol.usedDefContains) - .filterNot(d => containsSyntheticSuffix(d.symbol)) - .map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.ImplicitParams)) - else - Nil - // Partition to extract unset private variables from usedPrivates - val (usedPrivates, unusedPrivates) = - if ctx.settings.WunusedHas.privates then - privateDefInScope.toList.partition(d => d.symbol.usedDefContains) - else - (Nil, Nil) - val sortedPrivateDefs = unusedPrivates.filterNot(d => containsSyntheticSuffix(d.symbol)).map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.PrivateMembers)) - val unsetPrivateDefs = usedPrivates.filter(isUnsetVarDef).map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.UnsetPrivates)) - val sortedPatVars = - if ctx.settings.WunusedHas.patvars then - patVarsInScope.toList - .filterNot(d => d.symbol.usedDefContains) - .filterNot(d => containsSyntheticSuffix(d.symbol)) - .filterNot(d => isUsedInPosition(d.symbol.name, d.span)) - .map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.PatVars)) - else - Nil - val warnings = - sortedImp ::: - sortedLocalDefs ::: - sortedExplicitParams ::: - sortedImplicitParams ::: - sortedPrivateDefs ::: - sortedPatVars ::: - unsetLocalDefs ::: - unsetPrivateDefs - UnusedResult(warnings.toSet) - end getUnused - //============================ HELPERS ==================================== - - - /** - * Checks if import selects a def that is transparent and inline - */ - private def isTransparentAndInline(imp: tpd.Import)(using Context): Boolean = - imp.selectors.exists { sel => - val qual = imp.expr - val importedMembers = qual.tpe.member(sel.name).alternatives.map(_.symbol) - importedMembers.exists(s => s.is(Transparent) && s.is(Inline)) - } - - /** - * Heuristic to detect synthetic suffixes in names of symbols - */ - private def containsSyntheticSuffix(symbol: Symbol)(using Context): Boolean = - symbol.name.mangledString.contains("$") - - /** - * Is the constructor of synthetic package object - * Should be ignored as it is always imported/used in package - * Trigger false negative on used import - * - * Without this check example: - * - * --- WITH PACKAGE : WRONG --- - * {{{ - * package a: - * val x: Int = 0 - * package b: - * import a.* // no warning - * }}} - * --- WITH OBJECT : OK --- - * {{{ - * object a: - * val x: Int = 0 - * object b: - * import a.* // unused warning - * }}} - */ - private def isConstructorOfSynth(sym: Symbol)(using Context): Boolean = - sym.exists && sym.isConstructor && sym.owner.isPackageObject && sym.owner.is(Synthetic) + class PostTyper extends CheckUnused(PhaseMode.Aggregate, "PostTyper") - /** - * This is used to avoid reporting the parameters of the synthetic main method - * generated by `@main` - */ - private def isSyntheticMainParam(sym: Symbol)(using Context): Boolean = - sym.exists && ctx.platform.isMainMethod(sym.owner) && sym.owner.is(Synthetic) + class PostInlining extends CheckUnused(PhaseMode.Report, "PostInlining") - /** - * This is used to ignore exclusion imports (i.e. import `qual`.{`member` => _}) + 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 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 + tree match + case imp: Import => + if inliners == 0 + && languageImport(imp.expr).isEmpty + && !imp.isGeneratedByEnum + && !ctx.outer.owner.name.isReplWrapperName + then + imps.put(imp, ()) + case tree: Bind => + if !tree.name.isInstanceOf[DerivedName] && !tree.name.is(WildcardParamName) && !tree.hasAttachment(NoWarn) then + pats.addOne((tree.symbol, tree.namePos)) + case tree: ValDef if tree.hasAttachment(PatternVar) => + if !tree.name.isInstanceOf[DerivedName] then + pats.addOne((tree.symbol, tree.namePos)) + case tree: NamedDefTree => + if (tree.symbol ne NoSymbol) && !tree.name.isWildcard then + defs.addOne((tree.symbol, tree.namePos)) + case _ => + //println(s"OTHER ${tree.symbol}") + if tree.symbol ne NoSymbol then + defs.addOne((tree.symbol, tree.srcPos)) + + val inlined = Stack.empty[SrcPos] // enclosing call.srcPos of inlined code (expansions) + var inliners = 0 // depth of inline def (not inlined yet) + end RefInfos + + // Symbols already resolved in the given Context (with name and prefix of lookup). + class Resolved: + import PrecedenceLevels.* + private val seen = mutable.Map.empty[Symbol, List[(Name, Type, Precedence)]].withDefaultValue(Nil) + // if a result has been recorded, return it; otherwise, NoPrecedence. + def hasRecord(symbol: Symbol, name: Name, prefix: Type)(using Context): Precedence = + seen(symbol).find((n, p, _) => n == name && p =:= prefix) match + case Some((_, _, r)) => r + case none => NoPrecedence + // "record" the look-up result, if there is not already a result for the name and prefix. + def record(symbol: Symbol, name: Name, prefix: Type, result: Precedence)(using Context): Unit = + require(NoPrecedence.weakerThan(result)) + seen.updateWith(symbol): + case svs @ Some(vs) => + if vs.exists((n, p, _) => n == name && p =:= prefix) then svs + else Some((name, prefix, result) :: vs) + case none => Some((name, prefix, result) :: Nil) + + // Names are resolved by definitions and imports, which have four precedence levels: + object PrecedenceLevels: + opaque type Precedence = Int + inline def NoPrecedence: Precedence = 5 + inline def OtherUnit: Precedence = 4 // root import or def from another compilation unit via enclosing package + inline def Wildcard: Precedence = 3 // wildcard import + inline def NamedImport: Precedence = 2 // specific import + inline def Definition: Precedence = 1 // def from this compilation unit + extension (p: Precedence) + inline def weakerThan(q: Precedence): Boolean = p > q + inline def isNone: Boolean = p == NoPrecedence + + def reportUnused()(using Context): Unit = + for (msg, pos, origin) <- warnings do + if origin.isEmpty then report.warning(msg, pos) + else report.warning(msg, pos, origin) + msg.actions.headOption.foreach(Rewrites.applyAction) + + type MessageInfo = (UnusedSymbol, SrcPos, String) // string is origin or empty + + def warnings(using Context): Array[MessageInfo] = + val actionable = ctx.settings.rewrite.value.nonEmpty + val warnings = ArrayBuilder.make[MessageInfo] + def warnAt(pos: SrcPos)(msg: UnusedSymbol, origin: String = ""): Unit = warnings.addOne((msg, pos, origin)) + val infos = refInfos + + def checkUnassigned(sym: Symbol, pos: SrcPos) = + if sym.isLocalToBlock then + if ctx.settings.WunusedHas.locals && sym.is(Mutable) && !infos.asss(sym) then + warnAt(pos)(UnusedSymbol.unsetLocals) + else if ctx.settings.WunusedHas.privates && sym.isAllOf(Private | Mutable) && !infos.asss(sym) then + warnAt(pos)(UnusedSymbol.unsetPrivates) + + def checkPrivate(sym: Symbol, pos: SrcPos) = + if ctx.settings.WunusedHas.privates + && !sym.isPrimaryConstructor + && sym.is(Private, butNot = SelfName | Synthetic | CaseAccessor) + && !sym.name.is(BodyRetainerName) + && !sym.isSerializationSupport + && !(sym.is(Mutable) && sym.isSetter && sym.owner.is(Trait)) // tracks sym.underlyingSymbol sibling getter + then + warnAt(pos)(UnusedSymbol.privateMembers) + + def checkParam(sym: Symbol, pos: SrcPos) = + val m = sym.owner + def allowed = + val dd = defn + m.isDeprecated + || m.is(Synthetic) && !m.isAnonymousFunction + || m.hasAnnotation(defn.UnusedAnnot) // param of unused method + || sym.info.isSingleton + || m.isConstructor && m.owner.thisType.baseClasses.contains(defn.AnnotationClass) + def checkExplicit(): Unit = + // A class param is unused if its param accessor is unused. + // (The class param is not assigned to a field until constructors.) + // A local param accessor warns as a param; a private accessor as a private member. + // Avoid warning for case class elements because they are aliased via unapply. + if m.isPrimaryConstructor then + val alias = m.owner.info.member(sym.name) + if alias.exists then + val aliasSym = alias.symbol + if aliasSym.isAllOf(PrivateParamAccessor, butNot = CaseAccessor) && !infos.refs(alias.symbol) then + if aliasSym.is(Local) then + if ctx.settings.WunusedHas.explicits then + warnAt(pos)(UnusedSymbol.explicitParams) + else + if ctx.settings.WunusedHas.privates then + warnAt(pos)(UnusedSymbol.privateMembers) + else if ctx.settings.WunusedHas.explicits + && !sym.is(Synthetic) // param to setter is unused bc there is no field yet + && !(sym.owner.is(ExtensionMethod) && { + m.paramSymss.dropWhile(_.exists(_.isTypeParam)) match + case (h :: Nil) :: Nil => h == sym // param is the extended receiver + case _ => false + }) + && !sym.name.isInstanceOf[DerivedName] + && !ctx.platform.isMainMethod(m) + then + warnAt(pos)(UnusedSymbol.explicitParams) + end checkExplicit + // begin + if !infos.skip(m) + && !allowed + then + checkExplicit() + end checkParam + + def checkImplicit(sym: Symbol, pos: SrcPos) = + val m = sym.owner + def allowed = + val dd = defn + m.isDeprecated + || m.is(Synthetic) + || sym.name.is(ContextFunctionParamName) // a ubiquitous parameter + || sym.name.is(ContextBoundParamName) && sym.info.typeSymbol.isMarkerTrait // a ubiquitous parameter + || m.hasAnnotation(dd.UnusedAnnot) // param of unused method + || sym.info.typeSymbol.match // more ubiquity + case dd.DummyImplicitClass | dd.SubTypeClass | dd.SameTypeClass => true + case _ => false + || sym.info.isSingleton // DSL friendly + || sym.isCanEqual + || sym.info.typeSymbol.hasAnnotation(dd.LanguageFeatureMetaAnnot) + || sym.info.isInstanceOf[RefinedType] // can't be expressed as a context bound + if ctx.settings.WunusedHas.implicits + && !infos.skip(m) + && !allowed + then + if m.isPrimaryConstructor then + val alias = m.owner.info.member(sym.name) + if alias.exists then + val aliasSym = alias.symbol + if aliasSym.is(ParamAccessor) && !infos.refs(alias.symbol) then + warnAt(pos)(UnusedSymbol.implicitParams) + else + warnAt(pos)(UnusedSymbol.implicitParams) + + def checkLocal(sym: Symbol, pos: SrcPos) = + if ctx.settings.WunusedHas.locals + && !sym.is(InlineProxy) + && !sym.isCanEqual + then + warnAt(pos)(UnusedSymbol.localDefs) + + def checkPatvars() = + // convert the one non-synthetic span so all are comparable + def uniformPos(sym: Symbol, pos: SrcPos): SrcPos = + if pos.span.isSynthetic then pos else pos.sourcePos.withSpan(pos.span.toSynthetic) + // patvars in for comprehensions share the pos of where the name was introduced + val byPos = infos.pats.groupMap(uniformPos(_, _))((sym, pos) => sym) + for (pos, syms) <- byPos if !syms.exists(_.hasAnnotation(defn.UnusedAnnot)) do + if !syms.exists(infos.refs(_)) then + if !syms.exists(v => !v.isLocal && !v.is(Private)) then + warnAt(pos)(UnusedSymbol.patVars) + else if syms.exists(_.is(Mutable)) then // check unassigned var + val sym = // recover the original + if syms.size == 1 then syms.head + else infos.pats.find((s, p) => syms.contains(s) && !p.span.isSynthetic).map(_._1).getOrElse(syms.head) + if sym.is(Mutable) && !infos.asss(sym) then + if sym.isLocalToBlock then + warnAt(pos)(UnusedSymbol.unsetLocals) + else if sym.is(Private) then + warnAt(pos)(UnusedSymbol.unsetPrivates) + + def checkImports() = + // TODO check for unused masking import + import scala.jdk.CollectionConverters.given + import Rewrites.ActionPatch + type ImpSel = (Import, ImportSelector) + def isUsable(imp: Import, sel: ImportSelector): Boolean = + sel.isImportExclusion || infos.sels.containsKey(sel) || imp.isLoose(sel) + def warnImport(warnable: ImpSel, actions: List[CodeAction] = Nil): Unit = + val (imp, sel) = warnable + val msg = UnusedSymbol.imports(actions) + // example collection.mutable.{Map as MutMap} + val origin = cpy.Import(imp)(imp.expr, List(sel)).show(using ctx.withoutColors).stripPrefix("import ") + warnAt(sel.srcPos)(msg, origin) + + if !actionable then + for imp <- infos.imps.keySet.nn.asScala; sel <- imp.selectors if !isUsable(imp, sel) do + warnImport(imp -> sel) + else + // If the rest of the line is blank, include it in the final edit position. (Delete trailing whitespace.) + // If for deletion, and the prefix of the line is also blank, then include that, too. (Del blank line.) + def editPosAt(srcPos: SrcPos, forDeletion: Boolean): SrcPos = + val start = srcPos.span.start + val end = srcPos.span.end + val content = srcPos.sourcePos.source.content() + val prev = content.lastIndexWhere(c => !isWhitespace(c), end = start - 1) + val emptyLeft = prev < 0 || isLineBreakChar(content(prev)) + val next = content.indexWhere(c => !isWhitespace(c), from = end) + val emptyRight = next < 0 || isLineBreakChar(content(next)) + val deleteLine = emptyLeft && emptyRight && forDeletion + val bump = if (deleteLine) 1 else 0 // todo improve to include offset of next line, endline + 1 + val p0 = srcPos.span + val p1 = if (next >= 0 && emptyRight) p0.withEnd(next + bump) else p0 + val p2 = if (deleteLine) p1.withStart(prev + 1) else p1 + srcPos.sourcePos.withSpan(p2) + def actionsOf(actions: (SrcPos, String)*): List[CodeAction] = + val patches = actions.map((srcPos, replacement) => ActionPatch(srcPos.sourcePos, replacement)).toList + List(CodeAction(title = "unused import", description = Some("remove import"), patches)) + def replace(editPos: SrcPos)(replacement: String): List[CodeAction] = actionsOf(editPos -> replacement) + def deletion(editPos: SrcPos): List[CodeAction] = actionsOf(editPos -> "") + def textFor(impsel: ImpSel): String = + val (imp, sel) = impsel + val content = imp.srcPos.sourcePos.source.content() + def textAt(pos: SrcPos) = String(content.slice(pos.span.start, pos.span.end)) + val qual = textAt(imp.expr.srcPos) // keep original + val selector = textAt(sel.srcPos) // keep original + s"$qual.$selector" // don't succumb to vagaries of show + // begin actionable + val sortedImps = infos.imps.keySet.nn.asScala.toArray.sortBy(_.srcPos.span.point) // sorted by pos + var index = 0 + while index < sortedImps.length do + val nextImport = sortedImps.indexSatisfying(from = index + 1)(_.isPrimaryClause) // next import statement + if sortedImps.indexSatisfying(from = index, until = nextImport): imp => + imp.selectors.exists(!isUsable(imp, _)) // check if any selector in statement was unused + < nextImport then + // if no usable selectors in the import statement, delete it entirely. + // if there is exactly one usable selector, then replace with just that selector (i.e., format it). + // else for each clause, delete it or format one selector or delete unused selectors. + // To delete a comma separated item, delete start-to-start, but for last item delete a preceding comma. + // Reminder that first clause span includes the keyword, so delete point-to-start instead. + val existing = sortedImps.slice(index, nextImport) + val (keeping, deleting) = existing.iterator.flatMap(imp => imp.selectors.map(imp -> _)).toList + .partition(isUsable(_, _)) + if keeping.isEmpty then + val editPos = existing.head.srcPos.sourcePos.withSpan: + Span(start = existing.head.srcPos.span.start, end = existing.last.srcPos.span.end) + deleting.init.foreach(warnImport(_)) + warnImport(deleting.last, deletion(editPosAt(editPos, forDeletion = true))) + else if keeping.lengthIs == 1 then + val editPos = existing.head.srcPos.sourcePos.withSpan: + Span(start = existing.head.srcPos.span.start, end = existing.last.srcPos.span.end) + deleting.init.foreach(warnImport(_)) + val text = s"import ${textFor(keeping.head)}" + warnImport(deleting.last, replace(editPosAt(editPos, forDeletion = false))(text)) + else + val lostClauses = existing.iterator.filter(imp => !keeping.exists((i, _) => imp eq i)).toList + for imp <- lostClauses do + val actions = + if imp == existing.last then + val content = imp.srcPos.sourcePos.source.content() + val prev = existing.lastIndexWhere(i0 => keeping.exists((i, _) => i == i0)) + val comma = content.indexOf(',', from = existing(prev).srcPos.span.end) + val commaPos = imp.srcPos.sourcePos.withSpan: + Span(start = comma, end = existing(prev + 1).srcPos.span.start) + val srcPos = imp.srcPos + val editPos = srcPos.sourcePos.withSpan: // exclude keyword + srcPos.span.withStart(srcPos.span.point) + actionsOf(commaPos -> "", editPos -> "") + else + val impIndex = existing.indexOf(imp) + val editPos = imp.srcPos.sourcePos.withSpan: // exclude keyword + Span(start = imp.srcPos.span.point, end = existing(impIndex + 1).srcPos.span.start) + deletion(editPos) + imp.selectors.init.foreach(sel => warnImport(imp -> sel)) + warnImport(imp -> imp.selectors.last, actions) + val singletons = existing.iterator.filter(imp => keeping.count((i, _) => imp eq i) == 1).toList + var seen = List.empty[Import] + for impsel <- deleting do + val (imp, sel) = impsel + if singletons.contains(imp) then + if seen.contains(imp) then + warnImport(impsel) + else + seen ::= imp + val editPos = imp.srcPos.sourcePos.withSpan: + Span(start = imp.srcPos.span.point, end = imp.srcPos.span.end) // exclude keyword + val text = textFor(keeping.find((i, _) => imp eq i).get) + warnImport(impsel, replace(editPosAt(editPos, forDeletion = false))(text)) + else if !lostClauses.contains(imp) then + val actions = + if sel == imp.selectors.last then + val content = sel.srcPos.sourcePos.source.content() + val prev = imp.selectors.lastIndexWhere(s0 => keeping.exists((_, s) => s == s0)) + val comma = content.indexOf(',', from = imp.selectors(prev).srcPos.span.end) + val commaPos = sel.srcPos.sourcePos.withSpan: + Span(start = comma, end = imp.selectors(prev + 1).srcPos.span.start) + val editPos = sel.srcPos + actionsOf(commaPos -> "", editPos -> "") + else + val selIndex = imp.selectors.indexOf(sel) + val editPos = sel.srcPos.sourcePos.withSpan: + sel.srcPos.span.withEnd(imp.selectors(selIndex + 1).srcPos.span.start) + deletion(editPos) + warnImport(impsel, actions) + end if + index = nextImport + end while + + // begin + for (sym, pos) <- infos.defs.iterator if !sym.hasAnnotation(defn.UnusedAnnot) do + if infos.refs(sym) then + checkUnassigned(sym, pos) + else if sym.is(Private, butNot = ParamAccessor) then + checkPrivate(sym, pos) + else if sym.is(Param, butNot = Given | Implicit) then + checkParam(sym, pos) + else if sym.is(Param) then // Given | Implicit + checkImplicit(sym, pos) + else if sym.isLocalToBlock then + checkLocal(sym, pos) + + if ctx.settings.WunusedHas.patvars then + checkPatvars() + + if ctx.settings.WunusedHas.imports || ctx.settings.WunusedHas.strictNoImplicitWarn then + checkImports() + + warnings.result().sortBy(_._2.span.point) + end warnings + + // Specific exclusions + def ignoreTree(tree: Tree): Boolean = + tree.hasAttachment(ForArtifact) || tree.hasAttachment(Ignore) + + // The RHS of a def is too trivial to warn about unused params, e.g. def f(x: Int) = ??? + def isUnconsuming(rhs: Tree)(using Context): Boolean = + rhs.symbol == defn.Predef_undefined + || rhs.tpe =:= defn.NothingType // compiletime.error + || rhs.isInstanceOf[Literal] // 42 + || rhs.tpe.match + case ConstantType(_) => true + case tp: TermRef => tp.underlying.classSymbol.is(Module) // Scala 2 SingleType + case _ => false + //|| isPurePath(rhs) // a bit strong + || rhs.match + case Block((dd @ DefDef(anonfun, paramss, _, _)) :: Nil, Closure(Nil, Ident(nm), _)) => + anonfun == nm // isAnonymousFunctionName(anonfun) + && paramss.match + case (ValDef(contextual, _, _) :: Nil) :: Nil => + contextual.is(ContextFunctionParamName) + && isUnconsuming(dd.rhs) // rhs was wrapped in a context function + case _ => false + case Block(Nil, Literal(u)) => u.tpe =:= defn.UnitType // def f(x: X) = {} + case This(_) => true + case Ident(_) => rhs.symbol.is(ParamAccessor) + case Typed(rhs, _) => isUnconsuming(rhs) + case _ => false + + def allowVariableBindings(ok: List[Name], args: List[Tree]): Unit = + ok.zip(args).foreach: + case (param, arg @ Bind(p, _)) if param == p => arg.withAttachment(NoWarn, ()) + case _ => + + // NoWarn Binds if the name matches a "canonical" name, e.g. case element name + val nowarner = new TreeTraverser: + def traverse(tree: Tree)(using Context) = tree match + case UnApply(fun, _, args) => + val unapplied = tree.tpe.finalResultType.dealias.typeSymbol + if unapplied.is(CaseClass) then + allowVariableBindings(unapplied.primaryConstructor.info.firstParamNames, args) + else if fun.symbol == defn.PairClass_unapply then + val ok = fun.symbol.info match + case PolyType(tycon, MethodTpe(_, _, AppliedType(_, tprefs))) => + tprefs.collect: + case ref: TypeParamRef => termName(ref.binder.paramNames(ref.paramNum).toString.toLowerCase.nn) + case _ => Nil + allowVariableBindings(ok, args) + else if fun.symbol == defn.TypeTest_unapply then + () // just recurse into args + else + if unapplied.exists && unapplied.owner == defn.Quotes_reflectModule then + // cheapy search for parameter names via java reflection of Trees + // in lieu of drilling into requiredClass("scala.quoted.runtime.impl.QuotesImpl") + // ...member("reflect")...member(unapplied.name.toTypeName) + // with aliases into requiredModule("dotty.tools.dotc.ast.tpd") + val implName = s"dotty.tools.dotc.ast.Trees$$${unapplied.name}" + try + import scala.language.unsafeNulls + val clz = Class.forName(implName) // TODO improve to use class path or reflect + val ok = clz.getConstructors.head.getParameters.map(p => termName(p.getName)).toList.init + allowVariableBindings(ok, args) + catch case _: ClassNotFoundException => () + args.foreach(traverse) + case tree => traverseChildren(tree) + + extension (nm: Name) + inline def exists(p: Name => Boolean): Boolean = nm.ne(nme.NO_NAME) && p(nm) + inline def isWildcard: Boolean = nm == nme.WILDCARD || nm.is(WildcardParamName) + + extension (tp: Type) + def importPrefix(using Context): Type = tp match + case tp: NamedType => tp.prefix + case tp: ClassInfo => tp.prefix + case tp: TypeProxy => tp.superType.normalizedPrefix + case _ => NoType + def underlyingPrefix(using Context): Type = tp match + case tp: NamedType => tp.prefix + case tp: ClassInfo => tp.prefix + case tp: TypeProxy => tp.underlying.underlyingPrefix + case _ => NoType + def skipPackageObject(using Context): Type = + if tp.typeSymbol.isPackageObject then tp.underlyingPrefix else tp + def underlying(using Context): Type = tp match + case tp: TypeProxy => tp.underlying + case _ => tp + + private val serializationNames: Set[TermName] = + Set("readResolve", "readObject", "readObjectNoData", "writeObject", "writeReplace").map(termName(_)) + + extension (sym: Symbol) + def isSerializationSupport(using Context): Boolean = + sym.is(Method) && serializationNames(sym.name.toTermName) && sym.owner.isClass + && sym.owner.derivesFrom(defn.JavaSerializableClass) + def isCanEqual(using Context): Boolean = + sym.isOneOf(GivenOrImplicit) && sym.info.finalResultType.baseClasses.exists(_.derivesFrom(defn.CanEqualClass)) + def isMarkerTrait(using Context): Boolean = + sym.isClass && sym.info.allMembers.forall: d => + val m = d.symbol + !m.isTerm || m.isSelfSym || m.is(Method) && (m.owner == defn.AnyClass || m.owner == defn.ObjectClass) + + extension (sel: ImportSelector) + def boundTpe: Type = sel.bound match + case untpd.TypedSplice(tree) => tree.tpe + case _ => NoType + /** This is used to ignore exclusion imports of the form import `qual.member as _` + * because `sel.isUnimport` is too broad for old style `import concurrent._`. */ - private def isImportExclusion(sel: ImportSelector): Boolean = sel.renamed match - case untpd.Ident(name) => name == StdNames.nme.WILDCARD + def isImportExclusion: Boolean = sel.renamed match + case untpd.Ident(nme.WILDCARD) => true case _ => false - /** - * If -Wunused:strict-no-implicit-warn import and this import selector could potentially import implicit. - * return true - */ - private def shouldSelectorBeReported(imp: tpd.Import, sel: ImportSelector)(using Context): Boolean = - ctx.settings.WunusedHas.strictNoImplicitWarn && ( - sel.isWildcard || - imp.expr.tpe.member(sel.name.toTermName).alternatives.exists(_.symbol.isOneOf(GivenOrImplicit)) || - imp.expr.tpe.member(sel.name.toTypeName).alternatives.exists(_.symbol.isOneOf(GivenOrImplicit)) - ) - - /** - * Ignore CanEqual imports - */ - private def isImportIgnored(imp: tpd.Import, sel: ImportSelector)(using Context): Boolean = - (sel.isWildcard && sel.isGiven && imp.expr.tpe.allMembers.exists(p => p.symbol.typeRef.baseClasses.exists(_.derivesFrom(defn.CanEqualClass)) && p.symbol.isOneOf(GivenOrImplicit))) || - (imp.expr.tpe.member(sel.name.toTermName).alternatives - .exists(p => p.symbol.isOneOf(GivenOrImplicit) && p.symbol.typeRef.baseClasses.exists(_.derivesFrom(defn.CanEqualClass)))) + extension (imp: Import) + /** Is it the first import clause in a statement? `a.x` in `import a.x, b.{y, z}` */ + def isPrimaryClause(using Context): Boolean = + val span = imp.srcPos.span + span.start != span.point // primary clause starts at `import` keyword - /** - * Ignore definitions of CanEqual given - */ - private def isDefIgnored(memDef: tpd.MemberDef)(using Context): Boolean = - memDef.symbol.isOneOf(GivenOrImplicit) && memDef.symbol.typeRef.baseClasses.exists(_.derivesFrom(defn.CanEqualClass)) - - extension (tree: ImportSelector) - def boundTpe: Type = tree.bound match { - case untpd.TypedSplice(tree1) => tree1.tpe - case _ => NoType - } - - extension (sym: Symbol) - /** is accessible without import in current context */ - private def isAccessibleAsIdent(using Context): Boolean = - ctx.outersIterator.exists{ c => - c.owner == sym.owner - || sym.owner.isClass && c.owner.isClass - && c.owner.thisType.baseClasses.contains(sym.owner) - && c.owner.thisType.member(sym.name).alternatives.contains(sym) - } - - /** Given an import and accessibility, return selector that matches import<->symbol */ - private def isInImport(selData: ImportSelectorData, altName: Option[Name], isDerived: Boolean)(using Context): Boolean = - assert(sym.exists, s"Symbol $sym does not exist") - - val selector = selData.selector - - if !selector.isWildcard then - if altName.exists(explicitName => selector.rename != explicitName.toTermName) then - // if there is an explicit name, it must match - false - else - if isDerived then - // See i15503i.scala, grep for "package foo.test.i17156" - selData.allSymbolsDealiasedForNamed.contains(dealias(sym)) - else - selData.allSymbolsForNamed.contains(sym) - else - // Wildcard - if selData.excludedMembers.contains(altName.getOrElse(sym.name).toTermName) then - // Wildcard with exclusions that match the symbol - false - else if !selData.qualTpe.member(sym.name).hasAltWith(_.symbol == sym) then - // The qualifier does not have the target symbol as a member - false - else - if selector.isGiven then - // Further check that the symbol is a given or implicit and conforms to the bound - sym.isOneOf(Given | Implicit) - && (selector.bound.isEmpty || sym.info.finalResultType <:< selector.boundTpe) - else - // Normal wildcard, check that the symbol is not a given (but can be implicit) - !sym.is(Given) - end if - end isInImport - - /** Annotated with @unused */ - private def isUnusedAnnot(using Context): Boolean = - sym.annotations.exists(a => a.symbol == ctx.definitions.UnusedAnnot) - - private def shouldNotReportParamOwner(using Context): Boolean = - if sym.exists then - val owner = sym.owner - trivialDefs(owner) || // is a trivial def - owner.isPrimaryConstructor || - owner.annotations.exists ( // @depreacated - _.symbol == ctx.definitions.DeprecatedAnnot - ) || - owner.isAllOf(Synthetic | PrivateLocal) || - owner.is(Accessor) || - owner.isOverriden - else - false - - private def usedDefContains(using Context): Boolean = - sym.everySymbol.exists(usedDef.apply) - - private def everySymbol(using Context): List[Symbol] = - List(sym, sym.companionClass, sym.companionModule, sym.moduleClass).filter(_.exists) - - /** A function is overriden. Either has `override flags` or parent has a matching member (type and name) */ - private def isOverriden(using Context): Boolean = - sym.is(Flags.Override) || (sym.exists && sym.owner.thisType.parents.exists(p => sym.matchingMember(p).exists)) - - end extension - - extension (defdef: tpd.DefDef) - // so trivial that it never consumes params - private def isTrivial(using Context): Boolean = - val rhs = defdef.rhs - rhs.symbol == ctx.definitions.Predef_undefined || - rhs.tpe =:= ctx.definitions.NothingType || - defdef.symbol.is(Deferred) || - (rhs match { - case _: tpd.Literal => true - case _ => rhs.tpe match - case ConstantType(_) => true - case tp: TermRef => - // Detect Scala 2 SingleType - tp.underlying.classSymbol.is(Flags.Module) - case _ => - false - }) - def registerTrivial(using Context): Unit = - if defdef.isTrivial then - trivialDefs += defdef.symbol - - extension (memDef: tpd.MemberDef) - private def isValidMemberDef(using Context): Boolean = - memDef.symbol.exists - && !memDef.symbol.isUnusedAnnot - && !memDef.symbol.isAllOf(Flags.AccessorCreationFlags) - && !memDef.name.isWildcard - && !memDef.symbol.owner.is(ExtensionMethod) - - private def isValidParam(using Context): Boolean = - val sym = memDef.symbol - (sym.is(Param) || sym.isAllOf(PrivateParamAccessor | Local, butNot = CaseAccessor)) && - !isSyntheticMainParam(sym) && - !sym.shouldNotReportParamOwner - - private def shouldReportPrivateDef(using Context): Boolean = - currScopeType.top == ScopeType.Template && !memDef.symbol.isConstructor && memDef.symbol.is(Private, butNot = SelfName | Synthetic | CaseAccessor) - - private def isUnsetVarDef(using Context): Boolean = - val sym = memDef.symbol - sym.is(Mutable) && !setVars(sym) - - extension (imp: tpd.Import) - /** Enum generate an import for its cases (but outside them), which should be ignored */ - def isGeneratedByEnum(using Context): Boolean = - imp.symbol.exists && imp.symbol.owner.is(Flags.Enum, butNot = Flags.Case) - - extension (thisName: Name) - private def isWildcard: Boolean = - thisName == StdNames.nme.WILDCARD || thisName.is(WildcardParamName) - - end UnusedData - - private object UnusedData: - enum ScopeType: - case Local - case Template - case ReplWrapper - case Other - - object ScopeType: - /** return the scope corresponding to the enclosing scope of the given tree */ - def fromTree(tree: tpd.Tree)(using Context): ScopeType = tree match - case tree: tpd.Template => if tree.symbol.name.isReplWrapperName then ReplWrapper else Template - case _:tpd.Block => Local - case _ => Other - - final class ImportSelectorData(val qualTpe: Type, val selector: ImportSelector): - private var myUsed: Boolean = false - var excludedMembers: Set[TermName] = Set.empty - - def markUsed(): Unit = myUsed = true - - def isUsed: Boolean = myUsed - - def markExcluded(excluded: Set[TermName]): Unit = excludedMembers ++= excluded - - private var myAllSymbols: Set[Symbol] | Null = null - - def allSymbolsForNamed(using Context): Set[Symbol] = - if myAllSymbols == null then - val allDenots = qualTpe.member(selector.name).alternatives ::: qualTpe.member(selector.name.toTypeName).alternatives - myAllSymbols = allDenots.map(_.symbol).toSet - myAllSymbols.uncheckedNN - - private var myAllSymbolsDealiased: Set[Symbol] | Null = null - - def allSymbolsDealiasedForNamed(using Context): Set[Symbol] = - if myAllSymbolsDealiased == null then - myAllSymbolsDealiased = allSymbolsForNamed.map(sym => dealias(sym)) - myAllSymbolsDealiased.uncheckedNN - end ImportSelectorData - - case class UnusedSymbol(pos: SrcPos, name: Name, warnType: WarnTypes) - /** A container for the results of the used elements analysis */ - case class UnusedResult(warnings: Set[UnusedSymbol]) - object UnusedResult: - val Empty = UnusedResult(Set.empty) - end UnusedData - - private def dealias(symbol: Symbol)(using Context): Symbol = - if symbol.isType && symbol.asType.denot.isAliasType then - symbol.asType.typeRef.dealias.typeSymbol - else - symbol + /** Generated import of cases from enum companion. */ + def isGeneratedByEnum(using Context): Boolean = + imp.symbol.exists && imp.symbol.owner.is(Enum, butNot = Case) + /** Under -Wunused:strict-no-implicit-warn, avoid false positives + * if this selector is a wildcard that might import implicits or + * specifically does import an implicit. + * Similarly, import of CanEqual must not warn, as it is always witness. + */ + def isLoose(sel: ImportSelector)(using Context): Boolean = + if ctx.settings.WunusedHas.strictNoImplicitWarn then + if sel.isWildcard + || imp.expr.tpe.member(sel.name.toTermName).hasAltWith(_.symbol.isOneOf(GivenOrImplicit)) + || imp.expr.tpe.member(sel.name.toTypeName).hasAltWith(_.symbol.isOneOf(GivenOrImplicit)) + then return true + if sel.isWildcard && sel.isGiven + then imp.expr.tpe.allMembers.exists(_.symbol.isCanEqual) + else imp.expr.tpe.member(sel.name.toTermName).hasAltWith(_.symbol.isCanEqual) + + extension (pos: SrcPos) + def isZeroExtentSynthetic: Boolean = pos.span.isSynthetic && pos.span.start == pos.span.end + + extension [A <: AnyRef](arr: Array[A]) + // returns `until` if not satisfied + def indexSatisfying(from: Int, until: Int = arr.length)(p: A => Boolean): Int = + var i = from + while i < until && !p(arr(i)) do + i += 1 + i end CheckUnused diff --git a/compiler/src/dotty/tools/dotc/transform/ContextFunctionResults.scala b/compiler/src/dotty/tools/dotc/transform/ContextFunctionResults.scala index c31b2673e04a..1c045288c94a 100644 --- a/compiler/src/dotty/tools/dotc/transform/ContextFunctionResults.scala +++ b/compiler/src/dotty/tools/dotc/transform/ContextFunctionResults.scala @@ -101,7 +101,7 @@ object ContextFunctionResults: def contextFunctionResultTypeAfter(meth: Symbol, depth: Int)(using Context) = def recur(tp: Type, n: Int): Type = if n == 0 then tp - else tp match + else tp.dealias match case defn.FunctionTypeOfMethod(mt) => recur(mt.resType, n - 1) recur(meth.info.finalResultType, depth) diff --git a/compiler/src/dotty/tools/dotc/transform/Dependencies.scala b/compiler/src/dotty/tools/dotc/transform/Dependencies.scala index 1270c13460f4..9084930b6815 100644 --- a/compiler/src/dotty/tools/dotc/transform/Dependencies.scala +++ b/compiler/src/dotty/tools/dotc/transform/Dependencies.scala @@ -137,6 +137,7 @@ abstract class Dependencies(root: ast.tpd.Tree, @constructorOnly rootContext: Co if !enclosure.exists then throw NoPath() if enclosure == sym.enclosure then NoSymbol else + /** is sym a constructor or a term that is nested in a constructor? */ def nestedInConstructor(sym: Symbol): Boolean = sym.isConstructor || sym.isTerm && nestedInConstructor(sym.enclosure) @@ -237,6 +238,10 @@ abstract class Dependencies(root: ast.tpd.Tree, @constructorOnly rootContext: Co captureImplicitThis(tree.tpe) case tree: Select => if isExpr(sym) && isLocal(sym) then markCalled(sym, enclosure) + case tree: New => + val constr = tree.tpe.typeSymbol.primaryConstructor + if constr.exists then + symSet(called, enclosure) += constr case tree: This => narrowTo(tree.symbol.asClass) case tree: MemberDef if isExpr(sym) && sym.owner.isTerm => @@ -291,7 +296,6 @@ abstract class Dependencies(root: ast.tpd.Tree, @constructorOnly rootContext: Co val calleeOwner = normalizedCallee.owner if calleeOwner.isTerm then narrowLogicOwner(caller, logicOwner(normalizedCallee)) else - assert(calleeOwner.is(Trait)) // methods nested inside local trait methods cannot be lifted out // beyond the trait. Note that we can also call a trait method through // a qualifier; in that case no restriction to lifted owner arises. diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index 7414ca7e69c6..25239aee59cf 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -24,7 +24,6 @@ import typer.NoChecking import inlines.Inlines import typer.ProtoTypes.* import typer.ErrorReporting.errorTree -import typer.Checking.checkValue import core.TypeErasure.* import core.Decorators.* import dotty.tools.dotc.ast.{tpd, untpd} @@ -676,7 +675,7 @@ object Erasure { if tree.name == nme.apply && integrateSelect(tree) then return typed(tree.qualifier, pt) - val qual1 = typed(tree.qualifier, AnySelectionProto) + var qual1 = typed(tree.qualifier, AnySelectionProto) def mapOwner(sym: Symbol): Symbol = if !sym.exists && tree.name == nme.apply then @@ -725,7 +724,8 @@ object Erasure { assert(sym.exists, i"no owner from $owner/${origSym.showLocated} in $tree") - if owner == defn.ObjectClass then checkValue(qual1) + if owner == defn.ObjectClass then + qual1 = checkValue(qual1) def select(qual: Tree, sym: Symbol): Tree = untpd.cpy.Select(tree)(qual, sym.name).withType(NamedType(qual.tpe, sym)) diff --git a/compiler/src/dotty/tools/dotc/transform/ExpandPrivate.scala b/compiler/src/dotty/tools/dotc/transform/ExpandPrivate.scala index 9a6a04621074..0f7dde993b17 100644 --- a/compiler/src/dotty/tools/dotc/transform/ExpandPrivate.scala +++ b/compiler/src/dotty/tools/dotc/transform/ExpandPrivate.scala @@ -20,11 +20,14 @@ import ValueClasses.* * Make private accessor in value class not-private. This is necessary to unbox * the value class when accessing it from separate compilation units * - * Also, make non-private any private parameter forwarders that forward to an inherited + * Make non-private any private parameter forwarders that forward to an inherited * public or protected parameter accessor with the same name as the forwarder. * This is necessary since private methods are not allowed to have the same name * as inherited public ones. * + * Also, make non-private any private constructor that is annotated with `@publicInBinary`. + * (See SIP-52) + * * See discussion in https://github.com/scala/scala3/pull/784 * and https://github.com/scala/scala3/issues/783 */ @@ -102,6 +105,8 @@ class ExpandPrivate extends MiniPhase with IdentityDenotTransformer { thisPhase override def transformDefDef(tree: DefDef)(using Context): DefDef = { val sym = tree.symbol tree.rhs match { + case _ if sym.isConstructor && sym.hasPublicInBinary => + sym.ensureNotPrivate.installAfter(thisPhase) case Apply(sel @ Select(_: Super, _), _) if sym.isAllOf(PrivateParamAccessor) && sel.symbol.is(ParamAccessor) && sym.name == sel.symbol.name => sym.ensureNotPrivate.installAfter(thisPhase) diff --git a/compiler/src/dotty/tools/dotc/transform/ExtensionMethods.scala b/compiler/src/dotty/tools/dotc/transform/ExtensionMethods.scala index f0d1c687df8e..cd78e6da36d7 100644 --- a/compiler/src/dotty/tools/dotc/transform/ExtensionMethods.scala +++ b/compiler/src/dotty/tools/dotc/transform/ExtensionMethods.scala @@ -74,12 +74,10 @@ class ExtensionMethods extends MiniPhase with DenotTransformer with FullParamete sym.validFor = thisPhase.validFor } - // Create extension methods, except if the class comes from Scala 2 - // because it adds extension methods before pickling. - if !valueClass.is(Scala2x, butNot = Scala2Tasty) then - for (decl <- valueClass.classInfo.decls) - if isMethodWithExtension(decl) then - enterInModuleClass(createExtensionMethod(decl, moduleClassSym.symbol)) + // Create extension methods + for (decl <- valueClass.classInfo.decls) + if isMethodWithExtension(decl) then + enterInModuleClass(createExtensionMethod(decl, moduleClassSym.symbol)) // Create synthetic methods to cast values between the underlying type // and the ErasedValueType. These methods are removed in ElimErasedValueType. diff --git a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala index c66e6b9471cb..8d01d2415340 100644 --- a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala +++ b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala @@ -62,7 +62,7 @@ class FirstTransform extends MiniPhase with SymTransformer { thisPhase => case Select(qual, name) if !name.is(OuterSelectName) && tree.symbol.exists => val qualTpe = qual.tpe assert( - qualTpe.isErasedValueType || qualTpe.derivesFrom(tree.symbol.owner) || + qualTpe.widenDealias.isErasedValueType || qualTpe.derivesFrom(tree.symbol.owner) || tree.symbol.is(JavaStatic) && qualTpe.derivesFrom(tree.symbol.enclosingClass), i"non member selection of ${tree.symbol.showLocated} from ${qualTpe} in $tree") case _: TypeTree => diff --git a/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala b/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala index c43392f14f06..af168b563048 100644 --- a/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala +++ b/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala @@ -36,8 +36,8 @@ object LambdaLift: val liftedDefs: HashMap[Symbol, ListBuffer[Tree]] = new HashMap val deps = new Dependencies(ctx.compilationUnit.tpdTree, ctx.withPhase(thisPhase)): - def isExpr(sym: Symbol)(using Context): Boolean = sym.is(Method) - def enclosure(using Context) = ctx.owner.enclosingMethod + def isExpr(sym: Symbol)(using Context): Boolean = sym.is(Method) || sym.hasAnnotation(defn.ScalaStaticAnnot) + def enclosure(using Context) = ctx.owner.enclosingMethodOrStatic override def process(tree: Tree)(using Context): Unit = super.process(tree) @@ -129,9 +129,7 @@ object LambdaLift: private def proxy(sym: Symbol)(using Context): Symbol = { def liftedEnclosure(sym: Symbol) = - if sym.is(Method) - then deps.logicalOwner.getOrElse(sym, sym.enclosure) - else sym.enclosure + deps.logicalOwner.getOrElse(sym, sym.enclosure) def searchIn(enclosure: Symbol): Symbol = { if (!enclosure.exists) { def enclosures(encl: Symbol): List[Symbol] = diff --git a/compiler/src/dotty/tools/dotc/transform/Pickler.scala b/compiler/src/dotty/tools/dotc/transform/Pickler.scala index c8c071064ab8..fcf1b384fda1 100644 --- a/compiler/src/dotty/tools/dotc/transform/Pickler.scala +++ b/compiler/src/dotty/tools/dotc/transform/Pickler.scala @@ -25,10 +25,10 @@ import scala.annotation.constructorOnly import scala.concurrent.Promise import dotty.tools.dotc.transform.Pickler.writeSigFilesAsync -import scala.util.chaining.given import dotty.tools.io.FileWriters.{EagerReporter, BufferingReporter} import dotty.tools.dotc.sbt.interfaces.IncrementalCallback import dotty.tools.dotc.sbt.asyncZincPhasesCompleted +import dotty.tools.dotc.util.chaining.* import scala.concurrent.ExecutionContext import scala.util.control.NonFatal import java.util.concurrent.atomic.AtomicBoolean diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 02f5434aa549..df74e102f693 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -246,7 +246,9 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => else if sym.is(Param) then registerIfUnrolledParam(sym) - sym.keepAnnotationsCarrying(thisPhase, Set(defn.ParamMetaAnnot), orNoneOf = defn.NonBeanMetaAnnots) + // @unused is getter/setter but we want it on ordinary method params + if !sym.owner.is(Method) || sym.owner.isConstructor then + sym.keepAnnotationsCarrying(thisPhase, Set(defn.ParamMetaAnnot), orNoneOf = defn.NonBeanMetaAnnots) else if sym.is(ParamAccessor) then // @publicInBinary is not a meta-annotation and therefore not kept by `keepAnnotationsCarrying` val publicInBinaryAnnotOpt = sym.getAnnotation(defn.PublicInBinaryAnnot) @@ -254,7 +256,10 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => for publicInBinaryAnnot <- publicInBinaryAnnotOpt do sym.addAnnotation(publicInBinaryAnnot) else sym.keepAnnotationsCarrying(thisPhase, Set(defn.GetterMetaAnnot, defn.FieldMetaAnnot), orNoneOf = defn.NonBeanMetaAnnots) - if sym.isScala2Macro && !ctx.settings.XignoreScala2Macros.value then + if sym.isScala2Macro && !ctx.settings.XignoreScala2Macros.value && + sym != defn.StringContext_raw && + sym != defn.StringContext_f && + sym != defn.StringContext_s then if !sym.owner.unforcedDecls.exists(p => !p.isScala2Macro && p.name == sym.name && p.signature == sym.signature) // Allow scala.reflect.materializeClassTag to be able to compile scala/reflect/package.scala // This should be removed on Scala 3.x diff --git a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala index 45606b0dbef5..926a19224e79 100644 --- a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala +++ b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala @@ -78,7 +78,13 @@ class SyntheticMembers(thisPhase: DenotTransformer) { private def existingDef(sym: Symbol, clazz: ClassSymbol)(using Context): Symbol = val existing = sym.matchingMember(clazz.thisType) - if existing != sym && !existing.is(Deferred) then existing else NoSymbol + if ctx.settings.YcompileScala2Library.value && clazz.isValueClass && (sym == defn.Any_equals || sym == defn.Any_hashCode) then + NoSymbol + else if existing != sym && !existing.is(Deferred) then + existing + else + NoSymbol + end existingDef private def synthesizeDef(sym: TermSymbol, rhsFn: List[List[Tree]] => Context ?=> Tree)(using Context): Tree = DefDef(sym, rhsFn(_)(using ctx.withOwner(sym))).withSpan(ctx.owner.span.focus) @@ -498,53 +504,103 @@ class SyntheticMembers(thisPhase: DenotTransformer) { /** The class * * ``` - * case class C[T <: U](x: T, y: String*) + * trait U: + * type Elem + * + * case class C[T <: U](a: T, b: a.Elem, c: String*) * ``` * * gets the `fromProduct` method: * * ``` * def fromProduct(x$0: Product): MirroredMonoType = - * new C[U]( - * x$0.productElement(0).asInstanceOf[U], - * x$0.productElement(1).asInstanceOf[Seq[String]]: _*) + * val a$1 = x$0.productElement(0).asInstanceOf[U] + * val b$1 = x$0.productElement(1).asInstanceOf[a$1.Elem] + * val c$1 = x$0.productElement(2).asInstanceOf[Seq[String]] + * new C[U](a$1, b$1, c$1*) * ``` * where * ``` * type MirroredMonoType = C[?] * ``` + * + * However, if the last parameter is annotated `@unroll` then we generate: + * + * def fromProduct(x$0: Product): MirroredMonoType = + * val arity = x$0.productArity + * val a$1 = x$0.productElement(0).asInstanceOf[U] + * val b$1 = x$0.productElement(1).asInstanceOf[a$1.Elem] + * val c$1 = ( + * if arity > 2 then + * x$0.productElement(2) + * else + * + * ).asInstanceOf[Seq[String]] + * new C[U](a$1, b$1, c$1*) */ - def fromProductBody(caseClass: Symbol, param: Tree, optInfo: Option[MirrorImpl.OfProduct])(using Context): Tree = - def extractParams(tpe: Type): List[Type] = - tpe.asInstanceOf[MethodType].paramInfos - - def computeFromCaseClass: (Type, List[Type]) = - val (baseRef, baseInfo) = - val rawRef = caseClass.typeRef - val rawInfo = caseClass.primaryConstructor.info - optInfo match - case Some(info) => - (rawRef.asSeenFrom(info.pre, caseClass.owner), rawInfo.asSeenFrom(info.pre, caseClass.owner)) - case _ => - (rawRef, rawInfo) - baseInfo match + def fromProductBody(caseClass: Symbol, productParam: Tree, optInfo: Option[MirrorImpl.OfProduct])(using Context): Tree = + val classRef = optInfo match + case Some(info) => TypeRef(info.pre, caseClass) + case _ => caseClass.typeRef + val (newPrefix, constrMeth, constrSyms) = + val constr = TermRef(classRef, caseClass.primaryConstructor) + val symss = caseClass.primaryConstructor.paramSymss + (constr.info: @unchecked) match case tl: PolyType => val tvars = constrained(tl) val targs = for tvar <- tvars yield tvar.instantiate(fromBelow = false) - (baseRef.appliedTo(targs), extractParams(tl.instantiate(targs))) - case methTpe => - (baseRef, extractParams(methTpe)) - end computeFromCaseClass - - val (classRefApplied, paramInfos) = computeFromCaseClass - val elems = - for ((formal, idx) <- paramInfos.zipWithIndex) yield - val elem = - param.select(defn.Product_productElement).appliedTo(Literal(Constant(idx))) - .ensureConforms(formal.translateFromRepeated(toArray = false)) - if (formal.isRepeatedParam) ctx.typer.seqToRepeated(elem) else elem - New(classRefApplied, elems) + (AppliedType(classRef, targs), tl.instantiate(targs).asInstanceOf[MethodType], symss(1)) + case mt: MethodType => + (classRef, mt, symss.head) + + // Index of the first parameter marked `@unroll` or -1 + val unrolledFrom = + constrSyms.indexWhere(_.hasAnnotation(defn.UnrollAnnot)) + + // `val arity = x$0.productArity` + val arityDef: Option[ValDef] = + if unrolledFrom != -1 then + Some(SyntheticValDef(nme.arity, productParam.select(defn.Product_productArity).withSpan(ctx.owner.span.focus))) + else None + val arityRefTree = arityDef.map(vd => ref(vd.symbol)) + + // Create symbols for the vals corresponding to each parameter + // If there are dependent parameters, the infos won't be correct yet. + val bindingSyms = constrMeth.paramRefs.map: pref => + newSymbol(ctx.owner, pref.paramName.freshened, Synthetic, + pref.underlying.translateFromRepeated(toArray = false), coord = ctx.owner.span.focus) + val bindingRefs = bindingSyms.map(TermRef(NoPrefix, _)) + // Fix the infos for dependent parameters + if constrMeth.isParamDependent then + bindingSyms.foreach: bindingSym => + bindingSym.info = bindingSym.info.substParams(constrMeth, bindingRefs) + + def defaultGetterAtIndex(idx: Int): Tree = + val defaultGetterPrefix = caseClass.primaryConstructor.name.toTermName + ref(caseClass.companionModule).select(NameKinds.DefaultGetterName(defaultGetterPrefix, idx)) + + val bindingDefs = bindingSyms.zipWithIndex.map: (bindingSym, idx) => + val selection = productParam.select(defn.Product_productElement).appliedTo(Literal(Constant(idx))) + val rhs = ( + if unrolledFrom != -1 && idx >= unrolledFrom then + If(arityRefTree.get.select(defn.Int_>).appliedTo(Literal(Constant(idx))), + thenp = + selection, + elsep = + defaultGetterAtIndex(idx)) + else + selection + ).ensureConforms(bindingSym.info) + ValDef(bindingSym, rhs) + + val newArgs = bindingRefs.lazyZip(constrMeth.paramInfos).map: (bindingRef, paramInfo) => + val refTree = ref(bindingRef) + if paramInfo.isRepeatedParam then ctx.typer.seqToRepeated(refTree) else refTree + Block( + arityDef.toList ::: bindingDefs, + New(newPrefix, newArgs) + ) end fromProductBody /** For an enum T: diff --git a/compiler/src/dotty/tools/dotc/transform/UnrollDefinitions.scala b/compiler/src/dotty/tools/dotc/transform/UnrollDefinitions.scala index b431a81afeac..44379b88bf16 100644 --- a/compiler/src/dotty/tools/dotc/transform/UnrollDefinitions.scala +++ b/compiler/src/dotty/tools/dotc/transform/UnrollDefinitions.scala @@ -228,46 +228,9 @@ class UnrollDefinitions extends MacroTransform, IdentityDenotTransformer { forwarderDef } - private def generateFromProduct(startParamIndices: List[Int], paramCount: Int, defdef: DefDef)(using Context) = { - cpy.DefDef(defdef)( - name = defdef.name, - paramss = defdef.paramss, - tpt = defdef.tpt, - rhs = Match( - ref(defdef.paramss.head.head.asInstanceOf[ValDef].symbol).select(termName("productArity")), - startParamIndices.map { paramIndex => - val Apply(select, args) = defdef.rhs: @unchecked - CaseDef( - Literal(Constant(paramIndex)), - EmptyTree, - Apply( - select, - args.take(paramIndex) ++ - Range(paramIndex, paramCount).map(n => - ref(defdef.symbol.owner.companionModule) - .select(DefaultGetterName(defdef.symbol.owner.primaryConstructor.name.toTermName, n)) - ) - ) - ) - } :+ CaseDef( - Underscore(defn.IntType), - EmptyTree, - defdef.rhs - ) - ) - ).setDefTree - } - - private enum Gen: - case Substitute(origin: Symbol, newDef: DefDef) - case Forwarders(origin: Symbol, forwarders: List[DefDef]) + case class Forwarders(origin: Symbol, forwarders: List[DefDef]) - def origin: Symbol - def extras: List[DefDef] = this match - case Substitute(_, d) => d :: Nil - case Forwarders(_, ds) => ds - - private def generateSyntheticDefs(tree: Tree, compute: ComputeIndices)(using Context): Option[Gen] = tree match { + private def generateSyntheticDefs(tree: Tree, compute: ComputeIndices)(using Context): Option[Forwarders] = tree match { case defdef: DefDef if defdef.paramss.nonEmpty => import dotty.tools.dotc.core.NameOps.isConstructorName @@ -277,38 +240,29 @@ class UnrollDefinitions extends MacroTransform, IdentityDenotTransformer { val isCaseApply = defdef.name == nme.apply && defdef.symbol.owner.companionClass.is(CaseClass) - val isCaseFromProduct = defdef.name == nme.fromProduct && defdef.symbol.owner.companionClass.is(CaseClass) - val annotated = if (isCaseCopy) defdef.symbol.owner.primaryConstructor else if (isCaseApply) defdef.symbol.owner.companionClass.primaryConstructor - else if (isCaseFromProduct) defdef.symbol.owner.companionClass.primaryConstructor else defdef.symbol compute(annotated) match { case Nil => None case (paramClauseIndex, annotationIndices) :: Nil => val paramCount = annotated.paramSymss(paramClauseIndex).size - if isCaseFromProduct then - Some(Gen.Substitute( - origin = defdef.symbol, - newDef = generateFromProduct(annotationIndices, paramCount, defdef) - )) - else - val generatedDefs = - val indices = (annotationIndices :+ paramCount).sliding(2).toList.reverse - indices.foldLeft(List.empty[DefDef]): - case (defdefs, paramIndex :: nextParamIndex :: Nil) => - generateSingleForwarder( - defdef, - paramIndex, - paramCount, - nextParamIndex, - paramClauseIndex, - isCaseApply - ) :: defdefs - case _ => unreachable("sliding with at least 2 elements") - Some(Gen.Forwarders(origin = defdef.symbol, forwarders = generatedDefs)) + val generatedDefs = + val indices = (annotationIndices :+ paramCount).sliding(2).toList.reverse + indices.foldLeft(List.empty[DefDef]): + case (defdefs, paramIndex :: nextParamIndex :: Nil) => + generateSingleForwarder( + defdef, + paramIndex, + paramCount, + nextParamIndex, + paramClauseIndex, + isCaseApply + ) :: defdefs + case _ => unreachable("sliding with at least 2 elements") + Some(Forwarders(origin = defdef.symbol, forwarders = generatedDefs)) case multiple => report.error("Cannot have multiple parameter lists containing `@unroll` annotation", defdef.srcPos) @@ -323,14 +277,12 @@ class UnrollDefinitions extends MacroTransform, IdentityDenotTransformer { val generatedBody = tmpl.body.flatMap(generateSyntheticDefs(_, compute)) val generatedConstr0 = generateSyntheticDefs(tmpl.constr, compute) val allGenerated = generatedBody ++ generatedConstr0 - val bodySubs = generatedBody.collect({ case s: Gen.Substitute => s.origin }).toSet - val otherDecls = tmpl.body.filterNot(d => d.symbol.exists && bodySubs(d.symbol)) if allGenerated.nonEmpty then - val byName = (tmpl.constr :: otherDecls).groupMap(_.symbol.name.toString)(_.symbol) + val byName = (tmpl.constr :: tmpl.body).groupMap(_.symbol.name.toString)(_.symbol) for syntheticDefs <- allGenerated - dcl <- syntheticDefs.extras + dcl <- syntheticDefs.forwarders do val replaced = dcl.symbol byName.get(dcl.name.toString).foreach { syms => @@ -348,7 +300,7 @@ class UnrollDefinitions extends MacroTransform, IdentityDenotTransformer { tmpl.parents, tmpl.derived, tmpl.self, - otherDecls ++ allGenerated.flatMap(_.extras) + tmpl.body ++ allGenerated.flatMap(_.forwarders) ) } diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/DropForMap.scala b/compiler/src/dotty/tools/dotc/transform/localopt/DropForMap.scala new file mode 100644 index 000000000000..f7594f041204 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/localopt/DropForMap.scala @@ -0,0 +1,54 @@ +package dotty.tools.dotc +package transform.localopt + +import dotty.tools.dotc.ast.tpd.* +import dotty.tools.dotc.core.Decorators.* +import dotty.tools.dotc.core.Contexts.* +import dotty.tools.dotc.core.StdNames.* +import dotty.tools.dotc.core.Symbols.* +import dotty.tools.dotc.core.Types.* +import dotty.tools.dotc.transform.MegaPhase.MiniPhase +import dotty.tools.dotc.ast.desugar + +/** Drop unused trailing map calls in for comprehensions. + * We can drop the map call if: + * - it won't change the type of the expression, and + * - the function is an identity function or a const function to unit. + * + * The latter condition is checked in [[Desugar.scala#makeFor]] + */ +class DropForMap extends MiniPhase: + import DropForMap.* + + override def phaseName: String = DropForMap.name + + override def description: String = DropForMap.description + + override def transformApply(tree: Apply)(using Context): Tree = + if !tree.hasAttachment(desugar.TrailingForMap) then tree + else tree match + case aply @ Apply(MapCall(f), List(Lambda(List(param), body))) + if f.tpe =:= aply.tpe => // make sure that the type of the expression won't change + f // drop the map call + case _ => + tree.removeAttachment(desugar.TrailingForMap) + tree + + private object Lambda: + def unapply(tree: Tree)(using Context): Option[(List[ValDef], Tree)] = + tree match + case Block(List(defdef: DefDef), Closure(Nil, ref, _)) + if ref.symbol == defdef.symbol && !defdef.paramss.exists(_.forall(_.isType)) => + Some((defdef.termParamss.flatten, defdef.rhs)) + case _ => None + + private object MapCall: + def unapply(tree: Tree)(using Context): Option[Tree] = tree match + case Select(f, nme.map) => Some(f) + case Apply(fn, _) => unapply(fn) + case TypeApply(fn, _) => unapply(fn) + case _ => None + +object DropForMap: + val name: String = "dropForMap" + val description: String = "Drop unused trailing map calls in for comprehensions" diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/FormatChecker.scala b/compiler/src/dotty/tools/dotc/transform/localopt/FormatChecker.scala index 9e40792895c0..4922024b6c35 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/FormatChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/FormatChecker.scala @@ -3,10 +3,8 @@ package transform.localopt import scala.annotation.tailrec import scala.collection.mutable.ListBuffer -import scala.util.chaining.* import scala.util.matching.Regex.Match - import PartialFunction.cond import dotty.tools.dotc.ast.tpd.{Match => _, *} @@ -15,6 +13,7 @@ import dotty.tools.dotc.core.Symbols.* import dotty.tools.dotc.core.Types.* import dotty.tools.dotc.core.Phases.typerPhase import dotty.tools.dotc.util.Spans.Span +import dotty.tools.dotc.util.chaining.* /** Formatter string checker. */ class TypedFormatChecker(partsElems: List[Tree], parts: List[String], args: List[Tree])(using Context): diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index 1ee402deded0..612b1e06f1c6 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -674,7 +674,7 @@ object SpaceEngine { val superType = child.typeRef.superType if typeArgs.exists(_.isBottomType) && superType.isInstanceOf[ClassInfo] then val parentClass = superType.asInstanceOf[ClassInfo].declaredParents.find(_.classSymbol == parent).get - val paramTypeMap = Map.from(parentClass.argTypes.map(_.typeSymbol).zip(typeArgs)) + val paramTypeMap = Map.from(parentClass.argInfos.map(_.typeSymbol).zip(typeArgs)) val substArgs = child.typeRef.typeParamSymbols.map(param => paramTypeMap.getOrElse(param, WildcardType)) substArgs else Nil diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 98bfbe69ff8c..96590cb84544 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -694,9 +694,11 @@ trait Applications extends Compatibility { sym.is(JavaDefined) && sym.isConstructor && sym.owner.is(JavaAnnotation) - /** Is `sym` a constructor of an annotation? */ - def isAnnotConstr(sym: Symbol): Boolean = - sym.isConstructor && sym.owner.isAnnotation + /** Is `sym` a constructor of an annotation class, and are we in an + * annotation? If so, we don't lift arguments. See [[Mode.InAnnotation]]. + */ + protected final def isAnnotConstr(sym: Symbol): Boolean = + ctx.mode.is(Mode.InAnnotation) && sym.isConstructor && sym.owner.isAnnotation /** Match re-ordered arguments against formal parameters * @param n The position of the first parameter in formals in `methType`. @@ -994,9 +996,7 @@ trait Applications extends Compatibility { case (arg: NamedArg, _) => arg case (arg, name) => NamedArg(name, arg) } - else if isAnnotConstr(methRef.symbol) then - typedArgs - else if !sameSeq(args, orderedArgs) && !typedArgs.forall(isSafeArg) then + else if !isAnnotConstr(methRef.symbol) && !sameSeq(args, orderedArgs) && !typedArgs.forall(isSafeArg) then // need to lift arguments to maintain evaluation order in the // presence of argument reorderings. // (never do this for Java annotation constructors, hence the 'else if') @@ -1198,9 +1198,8 @@ trait Applications extends Compatibility { // // summonFrom { // case given A[t] => - // summonFrom + // summonFrom: // case given `t` => ... - // } // } // // the second `summonFrom` should expand only once the first `summonFrom` is diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index e8d3a75b4dec..ec07fefc64ab 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -804,24 +804,6 @@ object Checking { else "Cannot override non-inline parameter with an inline parameter", p1.srcPos) - def checkValue(tree: Tree)(using Context): Unit = - val sym = tree.tpe.termSymbol - if sym.isNoValue && !ctx.isJava then - report.error(JavaSymbolIsNotAValue(sym), tree.srcPos) - - /** Check that `tree` refers to a value, unless `tree` is selected or applied - * (singleton types x.type don't count as selections). - */ - def checkValue(tree: Tree, proto: Type)(using Context): tree.type = - tree match - case tree: RefTree if tree.name.isTermName => - proto match - case _: SelectionProto if proto ne SingletonTypeProto => // no value check - case _: FunOrPolyProto => // no value check - case _ => checkValue(tree) - case _ => - tree - /** Check that experimental language imports in `trees` * are done only in experimental scopes. For top-level * experimental imports, all top-level definitions are transformed diff --git a/compiler/src/dotty/tools/dotc/typer/Deriving.scala b/compiler/src/dotty/tools/dotc/typer/Deriving.scala index 60148319a61c..ef77adf18626 100644 --- a/compiler/src/dotty/tools/dotc/typer/Deriving.scala +++ b/compiler/src/dotty/tools/dotc/typer/Deriving.scala @@ -10,7 +10,7 @@ import Contexts.*, Symbols.*, Types.*, SymDenotations.*, Names.*, NameOps.*, Fla import ProtoTypes.*, ContextOps.* import util.Spans.* import util.SrcPos -import collection.mutable +import collection.mutable.ListBuffer import ErrorReporting.errorTree /** A typer mixin that implements type class derivation functionality */ @@ -25,8 +25,8 @@ trait Deriving { */ class Deriver(cls: ClassSymbol, codePos: SrcPos)(using Context) { - /** A buffer for synthesized symbols for type class instances */ - private var synthetics = new mutable.ListBuffer[Symbol] + /** A buffer for synthesized symbols for type class instances, with what user asked to synthesize. */ + private val synthetics = ListBuffer.empty[(tpd.Tree, Symbol)] /** A version of Type#underlyingClassRef that works also for higher-kinded types */ private def underlyingClassRef(tp: Type): Type = tp match { @@ -41,7 +41,7 @@ trait Deriving { * an instance with the same name does not exist already. * @param reportErrors Report an error if an instance with the same name exists already */ - private def addDerivedInstance(clsName: Name, info: Type, pos: SrcPos): Unit = { + private def addDerivedInstance(derived: tpd.Tree, clsName: Name, info: Type, pos: SrcPos): Unit = { val instanceName = "derived$".concat(clsName) if (ctx.denotNamed(instanceName).exists) report.error(em"duplicate type class derivation for $clsName", pos) @@ -50,9 +50,8 @@ trait Deriving { // derived instance will have too low a priority to be selected over a freshly // derived instance at the summoning site. val flags = if info.isInstanceOf[MethodOrPoly] then GivenMethod else Given | Lazy - synthetics += - newSymbol(ctx.owner, instanceName, flags, info, coord = pos.span) - .entered + val sym = newSymbol(ctx.owner, instanceName, flags, info, coord = pos.span).entered + synthetics += derived -> sym } /** Check derived type tree `derived` for the following well-formedness conditions: @@ -77,7 +76,8 @@ trait Deriving { * that have the same name but different prefixes through selective aliasing. */ private def processDerivedInstance(derived: untpd.Tree): Unit = { - val originalTypeClassType = typedAheadType(derived, AnyTypeConstructorProto).tpe + val originalTypeClassTree = typedAheadType(derived, AnyTypeConstructorProto) + val originalTypeClassType = originalTypeClassTree.tpe val underlyingClassType = underlyingClassRef(originalTypeClassType) val typeClassType = checkClassType( underlyingClassType.orElse(originalTypeClassType), @@ -100,7 +100,7 @@ trait Deriving { val derivedInfo = if derivedParams.isEmpty then monoInfo else PolyType.fromParams(derivedParams, monoInfo) - addDerivedInstance(originalTypeClassType.typeSymbol.name, derivedInfo, derived.srcPos) + addDerivedInstance(originalTypeClassTree, originalTypeClassType.typeSymbol.name, derivedInfo, derived.srcPos) } def deriveSingleParameter: Unit = { @@ -312,7 +312,7 @@ trait Deriving { else tpd.ValDef(sym.asTerm, typeclassInstance(sym)(Nil)) } - synthetics.map(syntheticDef).toList + synthetics.map((t, s) => syntheticDef(s).withAttachment(Deriving.OriginalTypeClass, t)).toList } def finalize(stat: tpd.TypeDef): tpd.Tree = { @@ -321,3 +321,8 @@ trait Deriving { } } } +object Deriving: + import dotty.tools.dotc.util.Property + + /** Attachment holding the name of a type class as written by the user. */ + val OriginalTypeClass = Property.StickyKey[tpd.Tree] diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 9d273ebca866..66e54cde33b3 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -928,8 +928,8 @@ trait Implicits: /** Find an implicit argument for parameter `formal`. * Return a failure as a SearchFailureType in the type of the returned tree. */ - def inferImplicitArg(formal: Type, span: Span)(using Context): Tree = - inferImplicit(formal, EmptyTree, span) match + def inferImplicitArg(formal: Type, span: Span, ignored: Set[Symbol] = Set.empty)(using Context): Tree = + inferImplicit(formal, EmptyTree, span, ignored) match case SearchSuccess(arg, _, _, _) => arg case fail @ SearchFailure(failed) => if fail.isAmbiguous then failed @@ -1082,7 +1082,7 @@ trait Implicits: * it should be applied, EmptyTree otherwise. * @param span The position where errors should be reported. */ - def inferImplicit(pt: Type, argument: Tree, span: Span)(using Context): SearchResult = ctx.profiler.onImplicitSearch(pt): + def inferImplicit(pt: Type, argument: Tree, span: Span, ignored: Set[Symbol] = Set.empty)(using Context): SearchResult = ctx.profiler.onImplicitSearch(pt): trace(s"search implicit ${pt.show}, arg = ${argument.show}: ${argument.tpe.show}", implicits, show = true) { record("inferImplicit") assert(ctx.phase.allowsImplicitSearch, @@ -1110,7 +1110,7 @@ trait Implicits: else i"conversion from ${argument.tpe} to $pt" CyclicReference.trace(i"searching for an implicit $searchStr"): - try ImplicitSearch(pt, argument, span)(using searchCtx).bestImplicit + try ImplicitSearch(pt, argument, span, ignored)(using searchCtx).bestImplicit catch case ce: CyclicReference => ce.inImplicitSearch = true throw ce @@ -1130,9 +1130,9 @@ trait Implicits: result case result: SearchFailure if result.isAmbiguous => val deepPt = pt.deepenProto - if (deepPt ne pt) inferImplicit(deepPt, argument, span) + if (deepPt ne pt) inferImplicit(deepPt, argument, span, ignored) else if (migrateTo3 && !ctx.mode.is(Mode.OldImplicitResolution)) - withMode(Mode.OldImplicitResolution)(inferImplicit(pt, argument, span)) match { + withMode(Mode.OldImplicitResolution)(inferImplicit(pt, argument, span, ignored)) match { case altResult: SearchSuccess => report.migrationWarning( result.reason.msg @@ -1243,7 +1243,7 @@ trait Implicits: } /** An implicit search; parameters as in `inferImplicit` */ - class ImplicitSearch(protected val pt: Type, protected val argument: Tree, span: Span)(using Context): + class ImplicitSearch(protected val pt: Type, protected val argument: Tree, span: Span, ignored: Set[Symbol])(using Context): assert(argument.isEmpty || argument.tpe.isValueType || argument.tpe.isInstanceOf[ExprType], em"found: $argument: ${argument.tpe}, expected: $pt") @@ -1670,7 +1670,7 @@ trait Implicits: SearchFailure(TooUnspecific(pt), span) else val contextual = ctxImplicits != null - val preEligible = // the eligible candidates, ignoring positions + var preEligible = // the eligible candidates, ignoring positions if ctxImplicits != null then if ctx.gadt.isNarrowing then withoutMode(Mode.ImplicitsEnabled) { @@ -1678,6 +1678,9 @@ trait Implicits: } else ctxImplicits.eligible(wildProto) else implicitScope(wildProto).eligible + if !ignored.isEmpty then + preEligible = + preEligible.filter(candidate => !ignored.contains(candidate.implicitRef.underlyingRef.symbol)) /** Does candidate `cand` come too late for it to be considered as an * eligible candidate? This is the case if `cand` appears in the same diff --git a/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala b/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala index 3ae533d58b2e..98fbede5f5ba 100644 --- a/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala +++ b/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala @@ -69,7 +69,7 @@ trait ImportSuggestions: && !(root.name == nme.raw.BAR && ctx.settings.scalajs.value && root == JSDefinitions.jsdefn.PseudoUnionModule) } - def nestedRoots(site: Type)(using Context): List[Symbol] = + def nestedRoots(site: Type, parentSymbols: Set[Symbol])(using Context): List[Symbol] = val seenNames = mutable.Set[Name]() site.baseClasses.flatMap { bc => bc.info.decls.filter { dcl => @@ -79,34 +79,37 @@ trait ImportSuggestions: } } - def rootsStrictlyIn(ref: Type)(using Context): List[TermRef] = + def rootsStrictlyIn(ref: Type, parentSymbols: Set[Symbol] = Set())(using Context): List[TermRef] = val site = ref.widen val refSym = site.typeSymbol - val nested = - if refSym.is(Package) then - if refSym == defn.EmptyPackageClass // Don't search the empty package - || refSym == defn.JavaPackageClass // As an optimization, don't search java... - || refSym == defn.JavaLangPackageClass // ... or java.lang. - then Nil - else refSym.info.decls.filter(lookInside) - else if refSym.infoOrCompleter.isInstanceOf[StubInfo] then - Nil // Don't chase roots that do not exist - else - if !refSym.is(Touched) then - refSym.ensureCompleted() // JavaDefined is reliably known only after completion - if refSym.is(JavaDefined) then Nil - else nestedRoots(site) - nested - .map(mbr => TermRef(ref, mbr.asTerm)) - .flatMap(rootsIn) - .toList + if parentSymbols.contains(refSym) then Nil + else + val nested = + if refSym.is(Package) then + if refSym == defn.EmptyPackageClass // Don't search the empty package + || refSym == defn.JavaPackageClass // As an optimization, don't search java... + || refSym == defn.JavaLangPackageClass // ... or java.lang. + then Nil + else refSym.info.decls.filter(lookInside) + else if refSym.infoOrCompleter.isInstanceOf[StubInfo] then + Nil // Don't chase roots that do not exist + else + if !refSym.is(Touched) then + refSym.ensureCompleted() // JavaDefined is reliably known only after completion + if refSym.is(JavaDefined) then Nil + else nestedRoots(site, parentSymbols) + val newParentSymbols = parentSymbols + refSym + nested + .map(mbr => TermRef(ref, mbr.asTerm)) + .flatMap(rootsIn(_, newParentSymbols)) + .toList - def rootsIn(ref: TermRef)(using Context): List[TermRef] = + def rootsIn(ref: TermRef, parentSymbols: Set[Symbol] = Set())(using Context): List[TermRef] = if seen.contains(ref) then Nil else implicitsDetailed.println(i"search for suggestions in ${ref.symbol.fullName}") seen += ref - ref :: rootsStrictlyIn(ref) + ref :: rootsStrictlyIn(ref, parentSymbols) def rootsOnPath(tp: Type)(using Context): List[TermRef] = tp match case ref: TermRef => rootsIn(ref) ::: rootsOnPath(ref.prefix) diff --git a/compiler/src/dotty/tools/dotc/typer/Migrations.scala b/compiler/src/dotty/tools/dotc/typer/Migrations.scala index f0d1d235a19c..0e6dc27ecf7f 100644 --- a/compiler/src/dotty/tools/dotc/typer/Migrations.scala +++ b/compiler/src/dotty/tools/dotc/typer/Migrations.scala @@ -126,4 +126,19 @@ trait Migrations: patch(Span(pt.args.head.span.start), "using ") end contextBoundParams + /** Report implicit parameter lists and rewrite implicit parameter list to contextual params */ + def implicitParams(tree: Tree, tp: MethodOrPoly, pt: FunProto)(using Context): Unit = + val mversion = mv.ImplicitParamsWithoutUsing + if tp.companion == ImplicitMethodType && pt.applyKind != ApplyKind.Using && pt.args.nonEmpty then + val rewriteMsg = Message.rewriteNotice("This code", mversion.patchFrom) + report.errorOrMigrationWarning( + em"""Implicit parameters should be provided with a `using` clause.$rewriteMsg + |To disable the warning, please use the following option: + | "-Wconf:msg=Implicit parameters should be provided with a `using` clause:s" + |""", + pt.args.head.srcPos, mversion) + if mversion.needsPatch then + patch(Span(pt.args.head.span.start), "using ") + end implicitParams + end Migrations diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 21ef0fc5d123..197604c3aca3 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -246,7 +246,9 @@ class Namer { typer: Typer => tree match { case tree: TypeDef if tree.isClassDef => - val flags = checkFlags(tree.mods.flags) + var flags = checkFlags(tree.mods.flags) + if ctx.settings.YcompileScala2Library.value then + flags |= Scala2x val name = checkNoConflict(tree.name, flags.is(Private), tree.span).asTypeName val cls = createOrRefine[ClassSymbol](tree, name, flags, ctx.owner, @@ -1175,11 +1177,26 @@ class Namer { typer: Typer => def canForward(mbr: SingleDenotation, alias: TermName): CanForward = { import CanForward.* val sym = mbr.symbol + /** + * The export selects a member of the current class (issue #22147). + * Assumes that cls.classInfo.selfType.derivesFrom(sym.owner) is true. + */ + def isCurrentClassMember: Boolean = expr match + case id: (Ident | This) => // Access through self type or this + /* Given the usage context below, where cls's self type is a subtype of sym.owner, + it suffices to check if symbol is the same class. */ + cls == id.symbol + case _ => false if !sym.isAccessibleFrom(pathType) then No("is not accessible") else if sym.isConstructor || sym.is(ModuleClass) || sym.is(Bridge) || sym.is(ConstructorProxy) || sym.isAllOf(JavaModule) then Skip - else if cls.derivesFrom(sym.owner) && (sym.owner == cls || !sym.is(Deferred)) then + // if the cls is a subclass or mixes in the owner of the symbol + // and either + // * the symbols owner is the cls itself + // * the symbol is not a deferred symbol + // * the symbol is a member of the current class (#22147) + else if cls.classInfo.selfType.derivesFrom(sym.owner) && (sym.owner == cls || !sym.is(Deferred) || isCurrentClassMember) then No(i"is already a member of $cls") else if pathMethod.exists && mbr.isType then No("is a type, so it cannot be exported as extension method") @@ -1940,9 +1957,7 @@ class Namer { typer: Typer => if isConstructor then // set result type tree to unit, but take the current class as result type of the symbol typedAheadType(ddef.tpt, defn.UnitType) - val mt = wrapMethType(effectiveResultType(sym, paramSymss)) - if sym.isPrimaryConstructor then checkCaseClassParamDependencies(mt, sym.owner) - mt + wrapMethType(effectiveResultType(sym, paramSymss)) else val paramFn = if Feature.enabled(Feature.modularity) && sym.isAllOf(Given | Method) then wrapRefinedMethType else wrapMethType valOrDefDefSig(ddef, sym, paramSymss, paramFn) @@ -1984,16 +1999,6 @@ class Namer { typer: Typer => ddef.trailingParamss.foreach(completeParams) end completeTrailingParamss - /** Checks an implementation restriction on case classes. */ - def checkCaseClassParamDependencies(mt: Type, cls: Symbol)(using Context): Unit = - mt.stripPoly match - case mt: MethodType if cls.is(Case) && mt.isParamDependent => - // See issue #8073 for background - report.error( - em"""Implementation restriction: case classes cannot have dependencies between parameters""", - cls.srcPos) - case _ => - private def setParamTrackedWithAccessors(psym: Symbol, ownerTpe: Type)(using Context): Unit = for acc <- ownerTpe.decls.lookupAll(psym.name) if acc.is(ParamAccessor) do acc.resetFlag(PrivateLocal) diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 85f44ead5f28..cf3867701c7f 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -536,8 +536,8 @@ object ProtoTypes { def typedArg(arg: untpd.Tree, formal: Type)(using Context): Tree = { val wideFormal = formal.widenExpr val argCtx = - if wideFormal eq formal then ctx - else ctx.withNotNullInfos(ctx.notNullInfos.retractMutables) + if wideFormal eq formal then ctx.retractMode(Mode.InAnnotation) + else ctx.retractMode(Mode.InAnnotation).withNotNullInfos(ctx.notNullInfos.retractMutables) val locked = ctx.typerState.ownedVars val targ = cacheTypedArg(arg, typer.typedUnadapted(_, wideFormal, locked)(using argCtx), diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 0ff36d8ec629..51929bf4fbc9 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -1166,22 +1166,21 @@ object RefChecks { * that are either both opaque types or both not. */ def checkExtensionMethods(sym: Symbol)(using Context): Unit = - if sym.is(Extension) && !sym.nextOverriddenSymbol.exists then + if sym.is(Extension) then extension (tp: Type) - def strippedResultType = Applications.stripImplicit(tp.stripPoly, wildcardOnly = true).resultType - def firstExplicitParamTypes = Applications.stripImplicit(tp.stripPoly, wildcardOnly = true).firstParamTypes + def explicit = Applications.stripImplicit(tp.stripPoly, wildcardOnly = true) def hasImplicitParams = tp.stripPoly match { case mt: MethodType => mt.isImplicitMethod case _ => false } - val target = sym.info.firstExplicitParamTypes.head // required for extension method, the putative receiver - val methTp = sym.info.strippedResultType // skip leading implicits and the "receiver" parameter + val explicitInfo = sym.info.explicit // consider explicit value params + val target = explicitInfo.firstParamTypes.head // required for extension method, the putative receiver + val methTp = explicitInfo.resultType // skip leading implicits and the "receiver" parameter def hidden = target.nonPrivateMember(sym.name) - .filterWithPredicate: - member => + .filterWithPredicate: member => member.symbol.isPublic && { val memberIsImplicit = member.info.hasImplicitParams val paramTps = if memberIsImplicit then methTp.stripPoly.firstParamTypes - else methTp.firstExplicitParamTypes + else methTp.explicit.firstParamTypes paramTps.isEmpty || memberIsImplicit && !methTp.hasImplicitParams || { val memberParamTps = member.info.stripPoly.firstParamTypes @@ -1193,7 +1192,15 @@ object RefChecks { } } .exists - if !target.typeSymbol.denot.isAliasType && !target.typeSymbol.denot.isOpaqueAlias && hidden + if sym.is(HasDefaultParams) then + val getterDenot = + val receiverName = explicitInfo.firstParamNames.head + val num = sym.info.paramNamess.flatten.indexWhere(_ == receiverName) + val getterName = DefaultGetterName(sym.name.toTermName, num = num) + sym.owner.info.member(getterName) + if getterDenot.exists + then report.warning(ExtensionHasDefault(sym), getterDenot.symbol.srcPos) + if !target.typeSymbol.isOpaqueAlias && !sym.nextOverriddenSymbol.exists && hidden then report.warning(ExtensionNullifiedByMember(sym, target.typeSymbol), sym.srcPos) end checkExtensionMethods diff --git a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala index c935e8d6b3cf..5111a9517fab 100644 --- a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala @@ -322,6 +322,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): case ClassSymbol(pre: Type, cls: Symbol) case Singleton(src: Symbol, tref: TermRef) case GenericTuple(tps: List[Type]) + case NamedTuple(nameTypePairs: List[(TermName, Type)]) /** Tests that both sides are tuples of the same arity */ infix def sameTuple(that: MirrorSource)(using Context): Boolean = @@ -351,6 +352,11 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): val arity = tps.size if arity <= Definitions.MaxTupleArity then s"class Tuple$arity" else s"trait Tuple { def size: $arity }" + case NamedTuple(nameTypePairs) => + val (names, types) = nameTypePairs.unzip + val namesStr = names.map(_.show).mkString("(\"", "\", \"", "\")") + val typesStr = types.map(_.show).mkString("(", ", ", ")") + s"NamedTuple.NamedTuple[${namesStr}, ${typesStr}]" private[Synthesizer] object MirrorSource: @@ -398,6 +404,8 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): // avoid type aliases for tuples Right(MirrorSource.GenericTuple(types)) case _ => reduce(tp.underlying) + case defn.NamedTupleDirect(_, _) => + Right(MirrorSource.NamedTuple(tp.namedTupleElementTypes(derived = false))) case tp: MatchType => val n = tp.tryNormalize if n.exists then reduce(n) else Left(i"its subpart `$tp` is an unreducible match type.") @@ -428,10 +436,25 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): def newTupleMirror(arity: Int): Tree = New(defn.RuntimeTupleMirrorTypeRef, Literal(Constant(arity)) :: Nil) - def makeProductMirror(pre: Type, cls: Symbol, tps: Option[List[Type]]): TreeWithErrors = + def makeNamedTupleProductMirror(nameTypePairs: List[(TermName, Type)]): TreeWithErrors = + val (labels, typeElems) = nameTypePairs.unzip + val elemLabels = labels.map(label => ConstantType(Constant(label.toString))) + val mirrorRef: Type => Tree = _ => newTupleMirror(typeElems.size) + makeProductMirror(typeElems, elemLabels, tpnme.NamedTuple, mirrorRef) + end makeNamedTupleProductMirror + + def makeClassProductMirror(pre: Type, cls: Symbol, tps: Option[List[Type]]) = val accessors = cls.caseAccessors val elemLabels = accessors.map(acc => ConstantType(Constant(acc.name.toString))) val typeElems = tps.getOrElse(accessors.map(mirroredType.resultType.memberInfo(_).widenExpr)) + val mirrorRef = (monoType: Type) => + if cls.useCompanionAsProductMirror then companionPath(pre, cls, span) + else if defn.isTupleClass(cls) then newTupleMirror(typeElems.size) // TODO: cls == defn.PairClass when > 22 + else anonymousMirror(monoType, MirrorImpl.OfProduct(pre), span) + makeProductMirror(typeElems, elemLabels, cls.name, mirrorRef) + end makeClassProductMirror + + def makeProductMirror(typeElems: List[Type], elemLabels: List[Type], label: Name, mirrorRef: Type => Tree): TreeWithErrors = val nestedPairs = TypeOps.nestedPairs(typeElems) val (monoType, elemsType) = mirroredType match case mirroredType: HKTypeLambda => @@ -442,15 +465,11 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): checkRefinement(formal, tpnme.MirroredElemTypes, elemsType, span) checkRefinement(formal, tpnme.MirroredElemLabels, elemsLabels, span) val mirrorType = formal.constrained_& { - mirrorCore(defn.Mirror_ProductClass, monoType, mirroredType, cls.name) + mirrorCore(defn.Mirror_ProductClass, monoType, mirroredType, label) .refinedWith(tpnme.MirroredElemTypes, TypeAlias(elemsType)) .refinedWith(tpnme.MirroredElemLabels, TypeAlias(elemsLabels)) } - val mirrorRef = - if cls.useCompanionAsProductMirror then companionPath(pre, cls, span) - else if defn.isTupleClass(cls) then newTupleMirror(typeElems.size) // TODO: cls == defn.PairClass when > 22 - else anonymousMirror(monoType, MirrorImpl.OfProduct(pre), span) - withNoErrors(mirrorRef.cast(mirrorType).withSpan(span)) + withNoErrors(mirrorRef(monoType).cast(mirrorType).withSpan(span)) end makeProductMirror MirrorSource.reduce(mirroredType) match @@ -474,10 +493,12 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): val arity = tps.size if tps.size <= maxArity then val tupleCls = defn.TupleType(arity).nn.classSymbol - makeProductMirror(tupleCls.owner.reachableThisType, tupleCls, Some(tps)) + makeClassProductMirror(tupleCls.owner.reachableThisType, tupleCls, Some(tps)) else val reason = s"it reduces to a tuple with arity $arity, expected arity <= $maxArity" withErrors(i"${defn.PairClass} is not a generic product because $reason") + case MirrorSource.NamedTuple(nameTypePairs) => + makeNamedTupleProductMirror(nameTypePairs) case MirrorSource.ClassSymbol(pre, cls) => if cls.isGenericProduct then if ctx.runZincPhases then @@ -486,7 +507,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): val rec = ctx.compilationUnit.depRecorder rec.addClassDependency(cls, DependencyByMemberRef) rec.addUsedName(cls.primaryConstructor) - makeProductMirror(pre, cls, None) + makeClassProductMirror(pre, cls, None) else withErrors(i"$cls is not a generic product because ${cls.whyNotGenericProduct}") case Left(msg) => withErrors(i"type `$mirroredType` is not a generic product because $msg") @@ -501,6 +522,8 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): val arity = tps.size val cls = if arity <= Definitions.MaxTupleArity then defn.TupleType(arity).nn.classSymbol else defn.PairClass ("", NoType, cls) + case Right(MirrorSource.NamedTuple(_)) => + ("named tuples are not sealed classes", NoType, NoSymbol) case Left(msg) => (msg, NoType, NoSymbol) val clsIsGenericSum = cls.isGenericSum(pre) diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index 6ce0f5f2517c..8448017cbada 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -163,7 +163,7 @@ trait TypeAssigner { else qualType.findMember(name, pre) - if reallyExists(mbr) then qualType.select(name, mbr) + if reallyExists(mbr) && NamedType.validPrefix(qualType) then qualType.select(name, mbr) else if qualType.isErroneous || name.toTermName == nme.ERROR then UnspecifiedErrorType else NoType end selectionType diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index b503a25d2624..2c901cfba059 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -84,6 +84,9 @@ object Typer { /** Indicates that an expression is explicitly ascribed to [[Unit]] type. */ val AscribedToUnit = new Property.StickyKey[Unit] + /** Tree adaptation lost fidelity; this attachment preserves the original tree. */ + val AdaptedTree = new Property.StickyKey[tpd.Tree] + /** An attachment on a Select node with an `apply` field indicating that the `apply` * was inserted by the Typer. */ @@ -615,10 +618,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer // Shortcut for the root package, this is not just a performance // optimization, it also avoids forcing imports thus potentially avoiding // cyclic references. - if (name == nme.ROOTPKG) - val tree2 = tree.withType(defn.RootPackage.termRef) - checkLegalValue(tree2, pt) - return tree2 + if name == nme.ROOTPKG then + return checkLegalValue(tree.withType(defn.RootPackage.termRef), pt) val rawType = val saved1 = unimported @@ -678,9 +679,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer cpy.Ident(tree)(tree.name.unmangleClassName).withType(checkedType) else tree.withType(checkedType) - val tree2 = toNotNullTermRef(tree1, pt) - checkLegalValue(tree2, pt) - tree2 + checkLegalValue(toNotNullTermRef(tree1, pt), pt) def isLocalExtensionMethodRef: Boolean = rawType match case rawType: TermRef => @@ -720,21 +719,47 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer errorTree(tree, MissingIdent(tree, kind, name, pt)) end typedIdent + def checkValue(tree: Tree)(using Context): Tree = + val sym = tree.tpe.termSymbol + if sym.isNoValue && !ctx.isJava then + if sym.is(Package) + && Feature.enabled(Feature.packageObjectValues) + && tree.tpe.member(nme.PACKAGE).hasAltWith(_.symbol.isPackageObject) + then + typed(untpd.Select(untpd.TypedSplice(tree), nme.PACKAGE)) + else + report.error(SymbolIsNotAValue(sym), tree.srcPos) + tree + else tree + + /** Check that `tree` refers to a value, unless `tree` is selected or applied + * (singleton types x.type don't count as selections). + */ + def checkValue(tree: Tree, proto: Type)(using Context): Tree = + tree match + case tree: RefTree if tree.name.isTermName => + proto match + case _: SelectionProto if proto ne SingletonTypeProto => tree // no value check + case _: FunOrPolyProto => tree // no value check + case _ => checkValue(tree) + case _ => tree + /** (1) If this reference is neither applied nor selected, check that it does * not refer to a package or Java companion object. * (2) Check that a stable identifier pattern is indeed stable (SLS 8.1.5) */ - private def checkLegalValue(tree: Tree, pt: Type)(using Context): Unit = - checkValue(tree, pt) + private def checkLegalValue(tree: Tree, pt: Type)(using Context): Tree = + val tree1 = checkValue(tree, pt) if ctx.mode.is(Mode.Pattern) - && !tree.isType + && !tree1.isType && !pt.isInstanceOf[ApplyingProto] - && !tree.tpe.match + && !tree1.tpe.match case tp: NamedType => tp.denot.hasAltWith(_.symbol.isStableMember && tp.prefix.isStable || tp.info.isStable) case tp => tp.isStable - && !isWildcardArg(tree) + && !isWildcardArg(tree1) then - report.error(StableIdentPattern(tree, pt), tree.srcPos) + report.error(StableIdentPattern(tree1, pt), tree1.srcPos) + tree1 def typedSelectWithAdapt(tree0: untpd.Select, pt: Type, qual: Tree)(using Context): Tree = val selName = tree0.name @@ -748,8 +773,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer if checkedType.exists then val select = toNotNullTermRef(assignType(tree, checkedType), pt) if selName.isTypeName then checkStable(qual.tpe, qual.srcPos, "type prefix") - checkLegalValue(select, pt) - ConstFold(select) + ConstFold(checkLegalValue(select, pt)) else EmptyTree // Otherwise, simplify `m.apply(...)` to `m(...)` @@ -2776,7 +2800,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer def isInner(owner: Symbol) = owner == sym || sym.is(Param) && owner == sym.owner val outer = ctx.outersIterator.dropWhile(c => isInner(c.owner)).next() def local: FreshContext = outer.fresh.setOwner(newLocalDummy(sym.owner)) - sym.owner.infoOrCompleter match + val ctx0 = sym.owner.infoOrCompleter match case completer: Namer#Completer if sym.is(Param) && completer.completerTypeParams(sym).nonEmpty => // Create a new local context with a dummy owner and a scope containing the @@ -2785,6 +2809,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer local.setScope(newScopeWith(completer.completerTypeParams(sym)*)) case _ => if outer.owner.isClass then local else outer + ctx0.addMode(Mode.InAnnotation) def completeAnnotations(mdef: untpd.MemberDef, sym: Symbol)(using Context): Unit = { // necessary to force annotation trees to be computed. @@ -2799,7 +2824,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer } def typedAnnotation(annot: untpd.Tree)(using Context): Tree = - checkAnnotClass(checkAnnotArgs(typed(annot))) + val typedAnnot = withMode(Mode.InAnnotation)(typed(annot)) + checkAnnotClass(checkAnnotArgs(typedAnnot)) def registerNowarn(tree: Tree, mdef: untpd.Tree)(using Context): Unit = val annot = Annotations.Annotation(tree) @@ -3193,7 +3219,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer .withType(dummy.termRef) if (!cls.isOneOf(AbstractOrTrait) && !ctx.isAfterTyper) checkRealizableBounds(cls, cdef.sourcePos.withSpan(cdef.nameSpan)) - if cls.isEnum || firstParentTpe.classSymbol.isEnum then + if cls.isEnum || !cls.isRefinementClass && firstParentTpe.classSymbol.isEnum then checkEnum(cdef, cls, firstParent) val cdef1 = assignType(cpy.TypeDef(cdef)(name, impl1), cls) @@ -3332,7 +3358,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer end typedPackageDef def typedAnnotated(tree: untpd.Annotated, pt: Type)(using Context): Tree = { - val annot1 = checkAnnotClass(typedExpr(tree.annot)) + val annot0 = withMode(Mode.InAnnotation)(typedExpr(tree.annot)) + val annot1 = checkAnnotClass(annot0) val annotCls = Annotations.annotClass(annot1) if annotCls == defn.NowarnAnnot then registerNowarn(annot1, tree) @@ -3422,7 +3449,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer /** Translate tuples of all arities */ def typedTuple(tree: untpd.Tuple, pt: Type)(using Context): Tree = - val tree1 = desugar.tuple(tree, pt) + val tree1 = desugar.tuple(tree, pt).withAttachmentsFrom(tree) checkDeprecatedAssignmentSyntax(tree) if tree1 ne tree then typed(tree1, pt) else @@ -4161,6 +4188,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer def methodStr = methPart(tree).symbol.showLocated if matchingApply(wtp, pt) then migrate(contextBoundParams(tree, wtp, pt)) + migrate(implicitParams(tree, wtp, pt)) if needsTupledDual(wtp, pt) then adapt(tree, pt.tupledDual, locked) else tree else if wtp.isContextualMethod then @@ -4169,9 +4197,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer readapt(tree.appliedToNone) // insert () to primary constructors else errorTree(tree, em"Missing arguments for $methodStr") - case _ => tryInsertApplyOrImplicit(tree, pt, locked) { - errorTree(tree, MethodDoesNotTakeParameters(tree)) - } + case _ => + tryInsertApplyOrImplicit(tree, pt, locked): + errorTree(tree, MethodDoesNotTakeParameters(tree)) } def adaptNoArgsImplicitMethod(wtp: MethodType): Tree = { @@ -4568,12 +4596,12 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer /** Adapt an expression of constant type to a different constant type `tpe`. */ def adaptConstant(tree: Tree, tpe: ConstantType): Tree = { - def lit = Literal(tpe.value).withSpan(tree.span) + def lit = Literal(tpe.value).withSpan(tree.span).withAttachment(AdaptedTree, tree) tree match { case Literal(c) => lit case tree @ Block(stats, expr) => tpd.cpy.Block(tree)(stats, adaptConstant(expr, tpe)) case tree => - if (isIdempotentExpr(tree)) lit // See discussion in phase Literalize why we demand isIdempotentExpr + if isIdempotentExpr(tree) then lit // See discussion in phase FirstTransform why we demand isIdempotentExpr else Block(tree :: Nil, lit) } } diff --git a/compiler/src/dotty/tools/dotc/util/ReusableInstance.scala b/compiler/src/dotty/tools/dotc/util/ReusableInstance.scala index d7837d9763fe..d132940dd03c 100644 --- a/compiler/src/dotty/tools/dotc/util/ReusableInstance.scala +++ b/compiler/src/dotty/tools/dotc/util/ReusableInstance.scala @@ -1,7 +1,7 @@ package dotty.tools.dotc.util import scala.collection.mutable.ArrayBuffer -import scala.util.chaining.* +import dotty.tools.dotc.util.chaining.* /** A wrapper for a list of cached instances of a type `T`. * The wrapper is recursion-reentrant: several instances are kept, so diff --git a/compiler/src/dotty/tools/dotc/util/SourceFile.scala b/compiler/src/dotty/tools/dotc/util/SourceFile.scala index 3ea43d16a7c8..1c264b395689 100644 --- a/compiler/src/dotty/tools/dotc/util/SourceFile.scala +++ b/compiler/src/dotty/tools/dotc/util/SourceFile.scala @@ -14,7 +14,7 @@ import scala.annotation.internal.sharable import scala.collection.mutable import scala.collection.mutable.ArrayBuffer import scala.compiletime.uninitialized -import scala.util.chaining.given +import dotty.tools.dotc.util.chaining.* import java.io.File.separator import java.net.URI @@ -276,12 +276,11 @@ object SourceFile { def apply(file: AbstractFile | Null, codec: Codec): SourceFile = // Files.exists is slow on Java 8 (https://rules.sonarsource.com/java/tag/performance/RSPEC-3725), - // so cope with failure; also deal with path prefix "Not a directory". + // so cope with failure. val chars = try new String(file.toByteArray, codec.charSet).toCharArray catch - case _: NoSuchFileException => Array.empty[Char] - case fse: FileSystemException if fse.getMessage.endsWith("Not a directory") => Array.empty[Char] + case _: FileSystemException => Array.empty[Char] if isScript(file, chars) then ScriptSourceFile(file, chars) diff --git a/compiler/src/dotty/tools/dotc/util/Spans.scala b/compiler/src/dotty/tools/dotc/util/Spans.scala index e1487408f36b..7d4bbe0e8180 100644 --- a/compiler/src/dotty/tools/dotc/util/Spans.scala +++ b/compiler/src/dotty/tools/dotc/util/Spans.scala @@ -59,6 +59,9 @@ object Spans { if (poff == SyntheticPointDelta) start else start + poff } + def pointMayBeIncorrect = + pointDelta == 0 && end - start >= SyntheticPointDelta + /** The difference between point and start in this span */ def pointDelta: Int = (coords >>> (StartEndBits * 2)).toInt diff --git a/compiler/src/dotty/tools/dotc/util/StackTraceOps.scala b/compiler/src/dotty/tools/dotc/util/StackTraceOps.scala index f991005f0c43..bd5c031a65e0 100644 --- a/compiler/src/dotty/tools/dotc/util/StackTraceOps.scala +++ b/compiler/src/dotty/tools/dotc/util/StackTraceOps.scala @@ -15,7 +15,7 @@ package dotty.tools.dotc.util import scala.language.unsafeNulls import collection.mutable, mutable.ListBuffer -import scala.util.chaining.given +import dotty.tools.dotc.util.chaining.* import java.lang.System.lineSeparator object StackTraceOps: diff --git a/compiler/src/dotty/tools/dotc/util/chaining.scala b/compiler/src/dotty/tools/dotc/util/chaining.scala new file mode 100644 index 000000000000..0c61ab6e73e9 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/util/chaining.scala @@ -0,0 +1,8 @@ + +package dotty.tools.dotc.util + +object chaining: + + extension [A](x: A) + inline def tap(inline f: A => Unit): x.type = { f(x): Unit; x } + inline def pipe[B](inline f: A => B): B = f(x) diff --git a/compiler/src/dotty/tools/io/Jar.scala b/compiler/src/dotty/tools/io/Jar.scala index dd33b1229610..eadc38e93007 100644 --- a/compiler/src/dotty/tools/io/Jar.scala +++ b/compiler/src/dotty/tools/io/Jar.scala @@ -50,7 +50,7 @@ class Jar(file: File) { def mainClass: Option[String] = manifest.map(_(Name.MAIN_CLASS)) /** The manifest-defined classpath String if available. */ def classPathString: Option[String] = - for (m <- manifest ; cp <- m.attrs.get(Name.CLASS_PATH)) yield cp + for (m <- manifest ; cp <- m.attrs.get(Name.CLASS_PATH) if !cp.trim().isEmpty()) yield cp def classPathElements: List[String] = classPathString match { case Some(s) => s.split("\\s+").toList case _ => Nil diff --git a/compiler/src/dotty/tools/repl/ParseResult.scala b/compiler/src/dotty/tools/repl/ParseResult.scala index 6c9f95d4dca2..2674a385a10c 100644 --- a/compiler/src/dotty/tools/repl/ParseResult.scala +++ b/compiler/src/dotty/tools/repl/ParseResult.scala @@ -52,6 +52,13 @@ object Load { val command: String = ":load" } +/** `:kind ` display the kind of a type. see also :help kind + */ +case class KindOf(expr: String) extends Command +object KindOf { + val command: String = ":kind" +} + /** To find out the type of an expression you may simply do: * * ``` @@ -138,6 +145,7 @@ object ParseResult { Help.command -> (_ => Help), Reset.command -> (arg => Reset(arg)), Imports.command -> (_ => Imports), + KindOf.command -> (arg => KindOf(arg)), Load.command -> (arg => Load(arg)), TypeOf.command -> (arg => TypeOf(arg)), DocOf.command -> (arg => DocOf(arg)), diff --git a/compiler/src/dotty/tools/repl/ReplCompiler.scala b/compiler/src/dotty/tools/repl/ReplCompiler.scala index f909abfc129a..087eb836dfcb 100644 --- a/compiler/src/dotty/tools/repl/ReplCompiler.scala +++ b/compiler/src/dotty/tools/repl/ReplCompiler.scala @@ -12,16 +12,16 @@ import dotty.tools.dotc.core.Phases.Phase import dotty.tools.dotc.core.StdNames.* import dotty.tools.dotc.core.Symbols.* import dotty.tools.dotc.reporting.Diagnostic -import dotty.tools.dotc.transform.PostTyper +import dotty.tools.dotc.transform.{CheckUnused, CheckShadowing, PostTyper} import dotty.tools.dotc.typer.ImportInfo.{withRootImports, RootRef} import dotty.tools.dotc.typer.TyperPhase import dotty.tools.dotc.util.Spans.* import dotty.tools.dotc.util.{ParsedComment, Property, SourceFile} import dotty.tools.dotc.{CompilationUnit, Compiler, Run} import dotty.tools.repl.results.* +import dotty.tools.dotc.util.chaining.* import scala.collection.mutable -import scala.util.chaining.given /** This subclass of `Compiler` is adapted for use in the REPL. * @@ -37,6 +37,7 @@ class ReplCompiler extends Compiler: List(Parser()), List(ReplPhase()), List(TyperPhase(addRootImports = false)), + List(CheckUnused.PostTyper(), CheckShadowing()), List(CollectTopLevelImports()), List(PostTyper()), ) diff --git a/compiler/src/dotty/tools/repl/ReplDriver.scala b/compiler/src/dotty/tools/repl/ReplDriver.scala index 96aa31b8753b..0f2921fd736c 100644 --- a/compiler/src/dotty/tools/repl/ReplDriver.scala +++ b/compiler/src/dotty/tools/repl/ReplDriver.scala @@ -510,6 +510,10 @@ class ReplDriver(settings: Array[String], state } + case KindOf(expr) => + out.println(s"""The :kind command is not currently supported.""") + state + case TypeOf(expr) => expr match { case "" => out.println(s":type ") diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index fd5b635e11e2..bd94df123929 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -1694,6 +1694,8 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler end SimpleSelectorTypeTest object SimpleSelector extends SimpleSelectorModule: + def apply(name: String): SimpleSelector = + withDefaultPos(untpd.ImportSelector(untpd.Ident(name.toTermName))) def unapply(x: SimpleSelector): Some[String] = Some(x.name.toString) end SimpleSelector @@ -1713,6 +1715,8 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler end RenameSelectorTypeTest object RenameSelector extends RenameSelectorModule: + def apply(fromName: String, toName: String): RenameSelector = + withDefaultPos(untpd.ImportSelector(untpd.Ident(fromName.toTermName), untpd.Ident(toName.toTermName))) def unapply(x: RenameSelector): (String, String) = (x.fromName, x.toName) end RenameSelector @@ -1738,6 +1742,8 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler end OmitSelectorTypeTest object OmitSelector extends OmitSelectorModule: + def apply(name: String): OmitSelector = + withDefaultPos(untpd.ImportSelector(untpd.Ident(name.toTermName), untpd.Ident(nme.WILDCARD))) def unapply(x: OmitSelector): Some[String] = Some(x.imported.name.toString) end OmitSelector @@ -1758,6 +1764,11 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler end GivenSelectorTypeTest object GivenSelector extends GivenSelectorModule: + def apply(bound: Option[TypeTree]): GivenSelector = + withDefaultPos(untpd.ImportSelector( + untpd.Ident(nme.EMPTY), + bound = bound.map(tpt => untpd.TypedSplice(tpt)).getOrElse(EmptyTree) + )) def unapply(x: GivenSelector): Some[Option[TypeTree]] = Some(GivenSelectorMethods.bound(x)) end GivenSelector @@ -2519,7 +2530,17 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler object ClassOfConstant extends ClassOfConstantModule: def apply(x: TypeRepr): ClassOfConstant = - // TODO check that the type is a valid class when creating this constant or let Ycheck do it? + // We only check if the supplied TypeRepr is valid if it contains an Array, + // as so far only that Array could cause issues + def correctTypeApplicationForArray(typeRepr: TypeRepr): Boolean = + val isArray = typeRepr.typeSymbol != dotc.core.Symbols.defn.ArrayClass + typeRepr match + case AppliedType(_, targs) if !targs.isEmpty => true + case _ => isArray + xCheckMacroAssert( + correctTypeApplicationForArray(x), + "Illegal empty Array type constructor. Please supply a type parameter." + ) dotc.core.Constants.Constant(x) def unapply(constant: ClassOfConstant): Some[TypeRepr] = Some(constant.typeValue) end ClassOfConstant @@ -2533,6 +2554,14 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler // See tests/pos-macros/exprSummonWithTypeVar with -Xcheck-macros. dotc.typer.Inferencing.fullyDefinedType(implicitTree.tpe, "", implicitTree) implicitTree + def searchIgnoring(tpe: TypeRepr)(ignored: Symbol*): ImplicitSearchResult = + import tpd.TreeOps + val implicitTree = ctx.typer.inferImplicitArg(tpe, Position.ofMacroExpansion.span, ignored.toSet) + // Make sure that we do not have any uninstantiated type variables. + // See tests/pos-macros/i16636. + // See tests/pos-macros/exprSummonWithTypeVar with -Xcheck-macros. + dotc.typer.Inferencing.fullyDefinedType(implicitTree.tpe, "", implicitTree) + implicitTree end Implicits type ImplicitSearchResult = Tree diff --git a/compiler/test-resources/repl/i21655 b/compiler/test-resources/repl/i21655 new file mode 100644 index 000000000000..57b09bfad32d --- /dev/null +++ b/compiler/test-resources/repl/i21655 @@ -0,0 +1,2 @@ +scala>:kind +The :kind command is not currently supported. \ No newline at end of file diff --git a/compiler/test/dotc/neg-best-effort-pickling.excludelist b/compiler/test/dotc/neg-best-effort-pickling.excludelist index 99a83a467f08..13fd5669dd8a 100644 --- a/compiler/test/dotc/neg-best-effort-pickling.excludelist +++ b/compiler/test/dotc/neg-best-effort-pickling.excludelist @@ -13,11 +13,11 @@ curried-dependent-ift.scala i17121.scala illegal-match-types.scala i13780-1.scala -i20317a.scala -i11226.scala -i974.scala -i13864.scala + +i20317a.scala # recursion limit exceeded +i11226.scala # missing type +i974.scala # cyclic reference +i13864.scala # missing symbol in pickling # semantic db generation fails in the first compilation -i1642.scala -i15158.scala +i15158.scala # cyclic reference - stack overflow diff --git a/compiler/test/dotc/neg-best-effort-unpickling.excludelist b/compiler/test/dotc/neg-best-effort-unpickling.excludelist index 1e22d919f25a..d57f7e0176e8 100644 --- a/compiler/test/dotc/neg-best-effort-unpickling.excludelist +++ b/compiler/test/dotc/neg-best-effort-unpickling.excludelist @@ -15,3 +15,6 @@ overrideClass.scala # repeating on a top level type definition i18750.scala + +# Crash on invalid prefix ([A] =>> Int) +i22357a.scala diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 3783ee97e4b9..b170d4be77bb 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -80,8 +80,10 @@ class CompilationTests { compileDir("tests/rewrites/annotation-named-pararamters", defaultOptions.and("-rewrite", "-source:3.6-migration")), compileFile("tests/rewrites/i21418.scala", unindentOptions.and("-rewrite", "-source:3.5-migration")), compileFile("tests/rewrites/infix-named-args.scala", defaultOptions.and("-rewrite", "-source:3.6-migration")), - compileFile("tests/rewrites/ambigious-named-tuple-assignment.scala", defaultOptions.and("-rewrite", "-source:3.6-migration")), + compileFile("tests/rewrites/ambiguous-named-tuple-assignment.scala", defaultOptions.and("-rewrite", "-source:3.6-migration")), compileFile("tests/rewrites/i21382.scala", defaultOptions.and("-indent", "-rewrite")), + compileFile("tests/rewrites/unused.scala", defaultOptions.and("-rewrite", "-Wunused:all")), + compileFile("tests/rewrites/i22440.scala", defaultOptions.and("-rewrite")) ).checkRewrites() } diff --git a/compiler/test/dotty/tools/dotc/config/PropertiesTest.scala b/compiler/test/dotty/tools/dotc/config/PropertiesTest.scala new file mode 100644 index 000000000000..e45ac1f3983f --- /dev/null +++ b/compiler/test/dotty/tools/dotc/config/PropertiesTest.scala @@ -0,0 +1,45 @@ +package dotty.tools.dotc.config + +import org.junit.Before +import org.junit.Test +import org.junit.Assert._ +import scala.language.unsafeNulls + +class PropertiesTest { + final val TestProperty = "dotty.tools.dotc.config.PropertiesTest.__test_property__" + + @Before + def beforeEach(): Unit = { + Properties.clearProp(TestProperty) + } + + @Test + def testPropOrNone(): Unit = { + assertEquals(Properties.propOrNone(TestProperty), None) + + Properties.setProp(TestProperty, "foo") + + assertEquals(Properties.propOrNone(TestProperty), Some("foo")) + } + + @Test + def testPropOrElse(): Unit = { + assertEquals(Properties.propOrElse(TestProperty, "bar"), "bar") + + Properties.setProp(TestProperty, "foo") + + var done = false + assertEquals(Properties.propOrElse(TestProperty, { done = true; "bar" }), "foo") + assertFalse("Does not evaluate alt if not needed", done) + } + + @Test + def testEnvOrElse(): Unit = { + assertEquals(Properties.envOrElse("_PropertiesTest_NOT_DEFINED", "test"), "test") + + var done = false + val envName = System.getenv().keySet().iterator().next() + assertNotEquals(Properties.envOrElse(envName, {done = true; "bar"}), "bar") + assertFalse("Does not evaluate alt if not needed", done) + } +} diff --git a/compiler/test/dotty/tools/dotc/printing/PrintingTest.scala b/compiler/test/dotty/tools/dotc/printing/PrintingTest.scala index 8a80a6978bdb..15522d61e31f 100644 --- a/compiler/test/dotty/tools/dotc/printing/PrintingTest.scala +++ b/compiler/test/dotty/tools/dotc/printing/PrintingTest.scala @@ -69,6 +69,9 @@ class PrintingTest { @Test def printing: Unit = testIn("tests/printing", "typer") + @Test + def posttyper: Unit = testIn("tests/printing/posttyper", "posttyper") + @Test def untypedPrinting: Unit = testIn("tests/printing/untyped", "parser") diff --git a/compiler/test/dotty/tools/repl/TabcompleteTests.scala b/compiler/test/dotty/tools/repl/TabcompleteTests.scala index 01b9d1109994..66c0fd8a9ce7 100644 --- a/compiler/test/dotty/tools/repl/TabcompleteTests.scala +++ b/compiler/test/dotty/tools/repl/TabcompleteTests.scala @@ -213,6 +213,7 @@ class TabcompleteTests extends ReplTest { ":exit", ":help", ":imports", + ":kind", ":load", ":quit", ":reset", diff --git a/compiler/test/dotty/tools/utils.scala b/compiler/test/dotty/tools/utils.scala index a5ebf0d59ec0..c33310acf06e 100644 --- a/compiler/test/dotty/tools/utils.scala +++ b/compiler/test/dotty/tools/utils.scala @@ -87,7 +87,7 @@ def toolArgsFor(files: List[JPath], charset: Charset = UTF_8): ToolArgs = /** Take a prefix of each file, extract tool args, parse, and combine. * Arg parsing respects quotation marks. Result is a map from ToolName to the combined tokens. - * If the ToolName is Target, then also accumulate the file name associated with the given platform. + * If the ToolName is Target, then also accumulate the file name associated with the given platform. */ def platformAndToolArgsFor(files: List[JPath], charset: Charset = UTF_8): (PlatformFiles, ToolArgs) = files.foldLeft(Map.empty[TestPlatform, List[String]] -> Map.empty[ToolName, List[String]]) { (res, path) => diff --git a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala index 67a7e8a4b00d..3508e38bb30c 100644 --- a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala +++ b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala @@ -12,7 +12,7 @@ import java.nio.file.{Files, NoSuchFileException, Path, Paths} import java.nio.charset.{Charset, StandardCharsets} import java.text.SimpleDateFormat import java.util.{HashMap, Timer, TimerTask} -import java.util.concurrent.{ExecutionException, TimeUnit, TimeoutException, Executors => JExecutors} +import java.util.concurrent.{TimeUnit, TimeoutException, Executors => JExecutors} import scala.collection.mutable import scala.io.{Codec, Source} @@ -526,12 +526,6 @@ trait ParallelTesting extends RunnerOrchestration { self => .and("-d", targetDir.getPath) .withClasspath(targetDir.getPath) - def waitForJudiciously(process: Process): Int = - try process.waitFor() - catch case _: InterruptedException => - try if process.waitFor(5L, TimeUnit.MINUTES) then process.exitValue() else -2 - finally Thread.currentThread.interrupt() - def compileWithJavac(fs: Array[String]) = if (fs.nonEmpty) { val fullArgs = Array( "-encoding", StandardCharsets.UTF_8.name, @@ -540,7 +534,7 @@ trait ParallelTesting extends RunnerOrchestration { self => val process = Runtime.getRuntime.exec("javac" +: fullArgs) val output = Source.fromInputStream(process.getErrorStream).mkString - if waitForJudiciously(process) != 0 then Some(output) + if process.waitFor() != 0 then Some(output) else None } else None @@ -775,11 +769,7 @@ trait ParallelTesting extends RunnerOrchestration { self => for fut <- eventualResults do try fut.get() - catch - case ee: ExecutionException if ee.getCause.isInstanceOf[InterruptedException] => - System.err.println("Interrupted (probably running after shutdown)") - ee.printStackTrace() - case ex: Exception => + catch case ex: Exception => System.err.println(ex.getMessage) ex.printStackTrace() @@ -817,13 +807,14 @@ trait ParallelTesting extends RunnerOrchestration { self => diffCheckfile(testSource, reporters, logger) override def maybeFailureMessage(testSource: TestSource, reporters: Seq[TestReporter]): Option[String] = - lazy val (map, expCount) = getWarnMapAndExpectedCount(testSource.sourceFiles.toIndexedSeq) + lazy val (expected, expCount) = getWarnMapAndExpectedCount(testSource.sourceFiles.toIndexedSeq) lazy val obtCount = reporters.foldLeft(0)(_ + _.warningCount) - lazy val (expected, unexpected) = getMissingExpectedWarnings(map, reporters.iterator.flatMap(_.diagnostics)) - lazy val diagnostics = reporters.flatMap(_.diagnostics.toSeq.sortBy(_.pos.line).map(e => s" at ${e.pos.line + 1}: ${e.message}")) - def showLines(title: String, lines: Seq[String]) = if lines.isEmpty then "" else title + lines.mkString("\n", "\n", "") - def hasMissingAnnotations = expected.nonEmpty || unexpected.nonEmpty - def showDiagnostics = showLines("-> following the diagnostics:", diagnostics) + lazy val (unfulfilled, unexpected) = getMissingExpectedWarnings(expected, diagnostics.iterator) + lazy val diagnostics = reporters.flatMap(_.diagnostics.toSeq.sortBy(_.pos.line)) + lazy val messages = diagnostics.map(d => s" at ${d.pos.line + 1}: ${d.message}") + def showLines(title: String, lines: Seq[String]) = if lines.isEmpty then "" else lines.mkString(s"$title\n", "\n", "") + def hasMissingAnnotations = unfulfilled.nonEmpty || unexpected.nonEmpty + def showDiagnostics = showLines("-> following the diagnostics:", messages) Option: if reporters.exists(_.errorCount > 0) then s"""Compilation failed for: ${testSource.title} @@ -832,58 +823,63 @@ trait ParallelTesting extends RunnerOrchestration { self => else if expCount != obtCount then s"""|Wrong number of warnings encountered when compiling $testSource |expected: $expCount, actual: $obtCount - |${showLines("Unfulfilled expectations:", expected)} + |${showLines("Unfulfilled expectations:", unfulfilled)} + |${showLines("Unexpected warnings:", unexpected)} + |$showDiagnostics + |""".stripMargin.trim.linesIterator.mkString("\n", "\n", "") + else if hasMissingAnnotations then + s"""|Warnings found on incorrect row numbers when compiling $testSource + |${showLines("Unfulfilled expectations:", unfulfilled)} |${showLines("Unexpected warnings:", unexpected)} |$showDiagnostics |""".stripMargin.trim.linesIterator.mkString("\n", "\n", "") - else if hasMissingAnnotations then s"\nWarnings found on incorrect row numbers when compiling $testSource\n$showDiagnostics" - else if !map.isEmpty then s"\nExpected warnings(s) have {=}: $map" + else if !expected.isEmpty then s"\nExpected warnings(s) have {=}: $expected" else null end maybeFailureMessage def getWarnMapAndExpectedCount(files: Seq[JFile]): (HashMap[String, Integer], Int) = - val comment = raw"//( *)(nopos-)?warn".r - val map = new HashMap[String, Integer]() + val comment = raw"//(?: *)(nopos-)?warn".r + val map = HashMap[String, Integer]() var count = 0 def bump(key: String): Unit = map.get(key) match case null => map.put(key, 1) case n => map.put(key, n+1) count += 1 - files.filter(isSourceFile).foreach { file => - Using(Source.fromFile(file, StandardCharsets.UTF_8.name)) { source => - source.getLines.zipWithIndex.foreach { case (line, lineNbr) => - comment.findAllMatchIn(line).foreach { m => - m.group(2) match - case "nopos-" => - bump("nopos") - case _ => bump(s"${file.getPath}:${lineNbr+1}") - } - } - }.get - } + for file <- files if isSourceFile(file) do + Using.resource(Source.fromFile(file, StandardCharsets.UTF_8.name)) { source => + source.getLines.zipWithIndex.foreach: (line, lineNbr) => + comment.findAllMatchIn(line).foreach: + case comment("nopos-") => bump("nopos") + case _ => bump(s"${file.getPath}:${lineNbr+1}") + } + end for (map, count) - def getMissingExpectedWarnings(map: HashMap[String, Integer], reporterWarnings: Iterator[Diagnostic]): (List[String], List[String]) = - val unexpected, unpositioned = ListBuffer.empty[String] + // return unfulfilled expected warnings and unexpected diagnostics + def getMissingExpectedWarnings(expected: HashMap[String, Integer], reporterWarnings: Iterator[Diagnostic]): (List[String], List[String]) = + val unexpected = ListBuffer.empty[String] def relativize(path: String): String = path.split(JFile.separatorChar).dropWhile(_ != "tests").mkString(JFile.separator) def seenAt(key: String): Boolean = - map.get(key) match + expected.get(key) match case null => false - case 1 => map.remove(key) ; true - case n => map.put(key, n - 1) ; true + case 1 => expected.remove(key); true + case n => expected.put(key, n - 1); true def sawDiagnostic(d: Diagnostic): Unit = val srcpos = d.pos.nonInlined if srcpos.exists then val key = s"${relativize(srcpos.source.file.toString())}:${srcpos.line + 1}" if !seenAt(key) then unexpected += key else - if(!seenAt("nopos")) unpositioned += relativize(srcpos.source.file.toString()) + if !seenAt("nopos") then unexpected += relativize(srcpos.source.file.toString) reporterWarnings.foreach(sawDiagnostic) - (map.asScala.keys.toList, (unexpected ++ unpositioned).toList) + val splitter = raw"(?:[^:]*):(\d+)".r + val unfulfilled = expected.asScala.keys.toList.sortBy { case splitter(n) => n.toInt case _ => -1 } + (unfulfilled, unexpected.toList) end getMissingExpectedWarnings + end WarnTest private final class RewriteTest(testSources: List[TestSource], checkFiles: Map[JFile, JFile], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean)(implicit summaryReport: SummaryReporting) extends Test(testSources, times, threadLimit, suppressAllOutput) { @@ -1018,8 +1014,8 @@ trait ParallelTesting extends RunnerOrchestration { self => def seenAt(key: String): Boolean = errorMap.get(key) match case null => false - case 1 => errorMap.remove(key) ; true - case n => errorMap.put(key, n - 1) ; true + case 1 => errorMap.remove(key); true + case n => errorMap.put(key, n - 1); true def sawDiagnostic(d: Diagnostic): Unit = d.pos.nonInlined match case srcpos if srcpos.exists => @@ -1206,7 +1202,7 @@ trait ParallelTesting extends RunnerOrchestration { self => * of betasty files. */ def checkNoBestEffortError()(implicit summaryReport: SummaryReporting): this.type = { - val test = new NoBestEffortErrorsTest(targets, times, threadLimit, shouldFail || shouldSuppressOutput).executeTestSuite() + val test = new NoBestEffortErrorsTest(targets, times, threadLimit, shouldFail).executeTestSuite() cleanup() @@ -1755,7 +1751,7 @@ trait ParallelTesting extends RunnerOrchestration { self => val bestEffortDir = new JFile(step1OutDir, s"META-INF${JFile.separator}best-effort") val step2Compilation = JointCompilationSource( - testGroup.name, step2SourceFiles, flags.and(withBetastyFlag).and(semanticDbFlag), step2OutDir, fromTasty = WithBestEffortTasty(bestEffortDir) + testGroup.name, step2SourceFiles, flags.and(bestEffortFlag).and(withBetastyFlag).and(semanticDbFlag), step2OutDir, fromTasty = WithBestEffortTasty(bestEffortDir) ) (step1Compilation, step2Compilation, bestEffortDir) }.unzip3 @@ -1764,7 +1760,7 @@ trait ParallelTesting extends RunnerOrchestration { self => new CompilationTest(step1Targets).keepOutput, new CompilationTest(step2Targets).keepOutput, bestEffortDirs, - true + shouldDelete = true ) } @@ -1818,7 +1814,7 @@ trait ParallelTesting extends RunnerOrchestration { self => def noCrashWithCompilingDependencies()(implicit summaryReport: SummaryReporting): this.type = { step1.checkNoBestEffortError() // Compile all files to generate the class files with best effort tasty - step2.checkCompile() // Compile with best effort tasty + step2.checkNoBestEffortError() // Compile with best effort tasty this } diff --git a/docs/_docs/reference/dropped-features/this-qualifier.md b/docs/_docs/reference/dropped-features/this-qualifier.md index f75d19356696..541c91e5cdfa 100644 --- a/docs/_docs/reference/dropped-features/this-qualifier.md +++ b/docs/_docs/reference/dropped-features/this-qualifier.md @@ -29,3 +29,15 @@ This can cause problems if a program tries to access the missing private field v // [C] needed if `field` is to be accessed through reflection val retained = field * field ``` + +Class parameters are normally inferred object-private, +so that members introduced by explicitly declaring them `val` or `var` are exempt from the rule described here. + +In particular, the following field is not excluded from variance checking: +```scala + class C[-T](private val t: T) // error +``` +And in contrast to the private field shown above, this field is not eliminated: +```scala + class C(private val c: Int) +``` diff --git a/docs/_docs/reference/experimental/better-fors.md b/docs/_docs/reference/experimental/better-fors.md index a4c42c9fb380..4f910259aab2 100644 --- a/docs/_docs/reference/experimental/better-fors.md +++ b/docs/_docs/reference/experimental/better-fors.md @@ -60,7 +60,7 @@ Additionally this extension changes the way `for`-comprehensions are desugared. This change makes the desugaring more intuitive and avoids unnecessary `map` calls, when an alias is not followed by a guard. 2. **Avoiding Redundant `map` Calls**: - When the result of the `for`-comprehension is the same expression as the last generator pattern, the desugaring avoids an unnecessary `map` call. but th eequality of the last pattern and the result has to be able to be checked syntactically, so it is either a variable or a tuple of variables. + When the result of the `for`-comprehension is the same expression as the last generator pattern, the desugaring avoids an unnecessary `map` call. But the equality of the last pattern and the result has to be able to be checked syntactically, so it is either a variable or a tuple of variables. There is also a special case for dropping the `map`, if its body is a constant function, that returns `()` (`Unit` constant). **Current Desugaring**: ```scala for { diff --git a/docs/_docs/reference/experimental/package-object-values.md b/docs/_docs/reference/experimental/package-object-values.md new file mode 100644 index 000000000000..1ca9c701970a --- /dev/null +++ b/docs/_docs/reference/experimental/package-object-values.md @@ -0,0 +1,40 @@ +--- +layout: doc-page +title: "Reference-able Package Objects" +redirectFrom: /docs/reference/experimental/package-object-values.html +nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/package-object-values.html +--- + +One limitation with `package object`s is that we cannot currently assign them to values: `a.b` fails to compile when `b` is a `package object`, even though it succeeds when `b` is a normal `object`. The workaround is to call +```scala + a.b.`package` +``` +But this is ugly and non-obvious. Or one could use a normal `object`, which is not always possible. + +The `packageObjectValues` language extension drops this limitation. The extension is enabled by the language import `import scala.language.experimental.packageObjectValues` or by setting the command line option `-language:experimental.packageObjectValues`. + +The extension, turns the following into valid code: + +```scala +package a +package object b + +val z = a.b // Currently fails with "package is not a value" +``` + +Currently the workaround is to use a `.package` suffix: + +```scala +val z = a.b.`package` +``` + +With the extension, a reference such as `a.b` where `b` is a `package` containing a `package object`, expands to `a.b.package` automatically + +## Limitations + +* `a.b` only expands to `a.b.package` when used "standalone", i.e. not when part of a larger select chain `a.b.c` or equivalent postfix expression `a.b c`, prefix expression `!a.b`, or infix expression `a.b c d`. + +* `a.b` expands to `a.b.package` of the type `a.b.package.type`, and only contains the contents of the `package object`. It does not contain other things in the `package` `a.b` that are outside of the `package object` + +Both these requirements are necessary for backwards compatibility, and anyway do not impact the main goal of removing the irregularity between `package object`s and normal `object`s. + diff --git a/docs/_docs/reference/other-new-features/creator-applications.md b/docs/_docs/reference/other-new-features/creator-applications.md index 8b1de02b2f25..f3b58b87bc48 100644 --- a/docs/_docs/reference/other-new-features/creator-applications.md +++ b/docs/_docs/reference/other-new-features/creator-applications.md @@ -39,8 +39,11 @@ The precise rules are as follows: - the class has a companion object (which might have been generated in step 1), and - that companion object does not already define a member named `apply`. - Each generated `apply` method forwards to one constructor of the class. It has the - same type and value parameters as the constructor. + Each generated `apply` method forwards to one constructor of the class. + It has the same type and value parameters as the constructor, + as well as the same access restriction as the class. + If the class is `protected`, then either the companion object must be `protected` + or the `apply` method will be made `protected`. Constructor proxy companions cannot be used as values by themselves. A proxy companion object must be selected with `apply` (or be applied to arguments, in which case the `apply` is implicitly diff --git a/docs/_docs/reference/other-new-features/matchable.md b/docs/_docs/reference/other-new-features/matchable.md index 234fdf03220c..f6c7673416eb 100644 --- a/docs/_docs/reference/other-new-features/matchable.md +++ b/docs/_docs/reference/other-new-features/matchable.md @@ -12,7 +12,7 @@ The Scala 3 standard library has a type [`IArray`](https://scala-lang.org/api/3. arrays that is defined like this: ```scala - opaque type IArray[+T] = Array[_ <: T] + opaque type IArray[+T] = Array[? <: T] ``` The `IArray` type offers extension methods for `length` and `apply`, but not for `update`; hence it seems values of type `IArray` cannot be updated. diff --git a/docs/_docs/reference/other-new-features/toplevel-definitions.md b/docs/_docs/reference/other-new-features/toplevel-definitions.md new file mode 100644 index 000000000000..b1793bd1941c --- /dev/null +++ b/docs/_docs/reference/other-new-features/toplevel-definitions.md @@ -0,0 +1,41 @@ +--- +layout: doc-page +title: "Toplevel Definitions" +nightlyOf: https://docs.scala-lang.org/scala3/reference/dropped-features/toplevel-definitions.html +--- + +All kind of definitions can now be written at the top-level. +Example: +```scala +package p +type Labelled[T] = (String, T) +val a: Labelled[Int] = ("count", 1) +def b = a._2 + +case class C() + +extension (x: C) def pair(y: C) = (x, y) +``` +Previously, `type`, `val` or `def` definitions had to be wrapped in a package object. Now, +there may be several source files in a package containing such top-level definitions, and source files can freely mix top-level value, method, and type definitions with classes and objects. + +The compiler generates synthetic objects that wrap top-level definitions falling into one of the following categories: + + - all pattern, value, method, and type definitions, + - implicit classes and objects, + - companion objects of opaque type aliases. + +If a source file `src.scala` contains such top-level definitions, they will be put in a synthetic object named `src$package`. The wrapping is transparent, however. The definitions in `src` can still be accessed as members of the enclosing package. The synthetic object will be placed last in the file, +after any other package clauses, imports, or object and class definitions. + +**Note:** This means that +1. The name of a source file containing wrapped top-level definitions is relevant for binary compatibility. If the name changes, so does the name of the generated object and its class. + +2. A top-level main method `def main(args: Array[String]): Unit = ...` is wrapped as any other method. If it appears +in a source file `src.scala`, it could be invoked from the command line using a command like `scala src$package`. Since the +"program name" is mangled it is recommended to always put `main` methods in explicitly named objects. + +3. The notion of `private` is independent of whether a definition is wrapped or not. A `private` top-level definition is always visible from everywhere in the enclosing package. + +4. If several top-level definitions are overloaded variants with the same name, +they must all come from the same source file. diff --git a/docs/_spec/03-types.md b/docs/_spec/03-types.md index 4b1293258495..5d4e205aace9 100644 --- a/docs/_spec/03-types.md +++ b/docs/_spec/03-types.md @@ -1071,7 +1071,7 @@ The following properties hold about ´⌈X⌉´ (we have paper proofs for those) The "lower-bound rule" states that ´S <: T´ if ´T = q.X´ and ´q.X´ is a non-class type designator and ´S <: L´ where ´L´ is the lower bound of the underlying type definition of ´q.X´". That rule is known to break transitivy of subtyping in Scala already. -Second, we define the relation ´⋔´ on *classes* (including traits and hidden classes of objects) as: +Second, we define the relation ´⋔´ on *classes* (including traits, hidden classes of objects, and enum terms) as: - ´C ⋔ D´ if `´C ∉´ baseClasses´(D)´` and ´D´ is `final` - ´C ⋔ D´ if `´D ∉´ baseClasses´(C)´` and ´C´ is `final` diff --git a/docs/sidebar.yml b/docs/sidebar.yml index 6f2154287091..edfa86554d7f 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -124,7 +124,6 @@ subsection: - page: reference/dropped-features/type-projection.md - page: reference/dropped-features/do-while.md - page: reference/dropped-features/procedure-syntax.md - - page: reference/dropped-features/package-objects.md - page: reference/dropped-features/early-initializers.md - page: reference/dropped-features/class-shadowing.md - page: reference/dropped-features/class-shadowing-spec.md @@ -165,6 +164,7 @@ subsection: - page: reference/experimental/runtimeChecked.md - page: reference/experimental/better-fors.md - page: reference/experimental/unrolled-defs.md + - page: reference/experimental/package-object-values.md - page: reference/syntax.md - title: Language Versions index: reference/language-versions/language-versions.md diff --git a/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala b/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala index e878866be81e..9821822f6d66 100644 --- a/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala +++ b/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala @@ -611,7 +611,7 @@ class DottyLanguageServer extends LanguageServer private def inProjectsSeeing(baseDriver: InteractiveDriver, definitions: List[SourceTree], symbols: List[Symbol]): List[(InteractiveDriver, Context, List[Symbol])] = { - val projects = projectsSeeing(definitions)(baseDriver.currentCtx) + val projects = projectsSeeing(definitions)(using baseDriver.currentCtx) projects.toList.map { config => val remoteDriver = drivers(config) val ctx = remoteDriver.currentCtx diff --git a/language-server/src/dotty/tools/languageserver/worksheet/WorksheetService.scala b/language-server/src/dotty/tools/languageserver/worksheet/WorksheetService.scala index 601bf7e71557..53c7a180c406 100644 --- a/language-server/src/dotty/tools/languageserver/worksheet/WorksheetService.scala +++ b/language-server/src/dotty/tools/languageserver/worksheet/WorksheetService.scala @@ -24,7 +24,7 @@ trait WorksheetService { thisServer: DottyLanguageServer => val sendMessage = (pos: SourcePosition, msg: String) => client.publishOutput(WorksheetRunOutput(params.textDocument, range(pos).get, msg)) - runWorksheet(driver, uri, sendMessage, cancelChecker)(driver.currentCtx) + runWorksheet(driver, uri, sendMessage, cancelChecker)(using driver.currentCtx) cancelChecker.checkCanceled() WorksheetRunResult(success = true) } catch { diff --git a/library/src/scala/annotation/publicInBinary.scala b/library/src/scala/annotation/publicInBinary.scala index 4990d266f892..a517f085dc7a 100644 --- a/library/src/scala/annotation/publicInBinary.scala +++ b/library/src/scala/annotation/publicInBinary.scala @@ -16,5 +16,4 @@ package scala.annotation * Adding this annotation to a non-public definition can also cause binary incompatibilities * if the definition is accessed in an inline definition (these can be checked using `-WunstableInlineAccessors`). */ -@experimental final class publicInBinary extends scala.annotation.StaticAnnotation diff --git a/library/src/scala/deriving/Mirror.scala b/library/src/scala/deriving/Mirror.scala index 57453a516567..a7477cf0fb2d 100644 --- a/library/src/scala/deriving/Mirror.scala +++ b/library/src/scala/deriving/Mirror.scala @@ -52,7 +52,7 @@ object Mirror { extension [T](p: ProductOf[T]) /** Create a new instance of type `T` with elements taken from product `a`. */ - def fromProductTyped[A <: scala.Product, Elems <: p.MirroredElemTypes](a: A)(using m: ProductOf[A] { type MirroredElemTypes = Elems }): T = + def fromProductTyped[A <: scala.Product, Elems <: p.MirroredElemTypes](a: A)(using ProductOf[A] { type MirroredElemTypes = Elems }): T = p.fromProduct(a) /** Create a new instance of type `T` with elements taken from tuple `t`. */ diff --git a/library/src/scala/quoted/Expr.scala b/library/src/scala/quoted/Expr.scala index d1385a0193d6..bfb26023e2c8 100644 --- a/library/src/scala/quoted/Expr.scala +++ b/library/src/scala/quoted/Expr.scala @@ -280,4 +280,23 @@ object Expr { } } + /** Find a given instance of type `T` in the current scope, + * while excluding certain symbols from the initial implicit search. + * Return `Some` containing the expression of the implicit or + * `None` if implicit resolution failed. + * + * @tparam T type of the implicit parameter + * @param ignored Symbols ignored during the initial implicit search + * + * @note if the found given requires additional search for other given instances, + * this additional search will NOT exclude the symbols from the `ignored` list. + */ + def summonIgnoring[T](using Type[T])(using quotes: Quotes)(ignored: quotes.reflect.Symbol*): Option[Expr[T]] = { + import quotes.reflect._ + Implicits.searchIgnoring(TypeRepr.of[T])(ignored*) match { + case iss: ImplicitSearchSuccess => Some(iss.tree.asExpr.asInstanceOf[Expr[T]]) + case isf: ImplicitSearchFailure => None + } + } + } diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index 7a98d6f6f761..5ada585c23a1 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -1,7 +1,6 @@ package scala.quoted -import scala.annotation.experimental -import scala.annotation.implicitNotFound +import scala.annotation.{experimental, implicitNotFound, unused} import scala.reflect.TypeTest /** Current Quotes in scope @@ -2545,6 +2544,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => /** Methods of the module object `val SimpleSelector` */ trait SimpleSelectorModule { this: SimpleSelector.type => + @experimental def apply(name: String): SimpleSelector def unapply(x: SimpleSelector): Some[String] } @@ -2570,6 +2570,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => /** Methods of the module object `val RenameSelector` */ trait RenameSelectorModule { this: RenameSelector.type => + @experimental def apply(fromName: String, toName: String): RenameSelector def unapply(x: RenameSelector): (String, String) } @@ -2597,6 +2598,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => /** Methods of the module object `val OmitSelector` */ trait OmitSelectorModule { this: OmitSelector.type => + @experimental def apply(name: String): OmitSelector def unapply(x: OmitSelector): Some[String] } @@ -2621,6 +2623,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => /** Methods of the module object `val GivenSelector` */ trait GivenSelectorModule { this: GivenSelector.type => + @experimental def apply(bound: Option[TypeTree]): GivenSelector def unapply(x: GivenSelector): Some[Option[TypeTree]] } @@ -3705,6 +3708,18 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => * @param tpe type of the implicit parameter */ def search(tpe: TypeRepr): ImplicitSearchResult + + /** Find a given instance of type `T` in the current scope provided by the current enclosing splice, + * while excluding certain symbols from the initial implicit search. + * Return an `ImplicitSearchResult`. + * + * @param tpe type of the implicit parameter + * @param ignored Symbols ignored during the initial implicit search + * + * @note if an found given requires additional search for other given instances, + * this additional search will NOT exclude the symbols from the `ignored` list. + */ + def searchIgnoring(tpe: TypeRepr)(ignored: Symbol*): ImplicitSearchResult } /** Result of a given instance search */ @@ -4941,7 +4956,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => foldTree(foldTree(foldTree(x, cond)(owner), thenp)(owner), elsep)(owner) case While(cond, body) => foldTree(foldTree(x, cond)(owner), body)(owner) - case Closure(meth, tpt) => + case Closure(meth, _) => foldTree(x, meth)(owner) case Match(selector, cases) => foldTrees(foldTree(x, selector)(owner), cases)(owner) @@ -5019,7 +5034,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => def traverseTree(tree: Tree)(owner: Symbol): Unit = traverseTreeChildren(tree)(owner) - def foldTree(x: Unit, tree: Tree)(owner: Symbol): Unit = traverseTree(tree)(owner) + def foldTree(@unused x: Unit, tree: Tree)(owner: Symbol): Unit = traverseTree(tree)(owner) protected def traverseTreeChildren(tree: Tree)(owner: Symbol): Unit = foldOverTree((), tree)(owner) diff --git a/library/src/scala/runtime/Arrays.scala b/library/src/scala/runtime/Arrays.scala index 9f5bdd99a5f4..085b36c08a1f 100644 --- a/library/src/scala/runtime/Arrays.scala +++ b/library/src/scala/runtime/Arrays.scala @@ -1,5 +1,6 @@ package scala.runtime +import scala.annotation.unused import scala.reflect.ClassTag import java.lang.{reflect => jlr} @@ -26,6 +27,6 @@ object Arrays { /** Create an array of a reference type T. */ - def newArray[Arr](componentType: Class[?], returnType: Class[Arr], dimensions: Array[Int]): Arr = + def newArray[Arr](componentType: Class[?], @unused returnType: Class[Arr], dimensions: Array[Int]): Arr = jlr.Array.newInstance(componentType, dimensions*).asInstanceOf[Arr] } diff --git a/library/src/scala/runtime/LazyVals.scala b/library/src/scala/runtime/LazyVals.scala index 15220ea2410a..9959f99f6e17 100644 --- a/library/src/scala/runtime/LazyVals.scala +++ b/library/src/scala/runtime/LazyVals.scala @@ -96,13 +96,13 @@ object LazyVals { println(s"CAS($t, $offset, $e, $v, $ord)") val mask = ~(LAZY_VAL_MASK << ord * BITS_PER_LAZY_VAL) val n = (e & mask) | (v.toLong << (ord * BITS_PER_LAZY_VAL)) - unsafe.compareAndSwapLong(t, offset, e, n) + unsafe.compareAndSwapLong(t, offset, e, n): @nowarn("cat=deprecation") } def objCAS(t: Object, offset: Long, exp: Object, n: Object): Boolean = { if (debug) println(s"objCAS($t, $exp, $n)") - unsafe.compareAndSwapObject(t, offset, exp, n) + unsafe.compareAndSwapObject(t, offset, exp, n): @nowarn("cat=deprecation") } def setFlag(t: Object, offset: Long, v: Int, ord: Int): Unit = { @@ -147,7 +147,7 @@ object LazyVals { def get(t: Object, off: Long): Long = { if (debug) println(s"get($t, $off)") - unsafe.getLongVolatile(t, off) + unsafe.getLongVolatile(t, off): @nowarn("cat=deprecation") } // kept for backward compatibility diff --git a/library/src/scala/runtime/stdLibPatches/language.scala b/library/src/scala/runtime/stdLibPatches/language.scala index 402e5af1735f..fc7082698f21 100644 --- a/library/src/scala/runtime/stdLibPatches/language.scala +++ b/library/src/scala/runtime/stdLibPatches/language.scala @@ -140,6 +140,11 @@ object language: */ @compileTimeOnly("`betterFors` can only be used at compile time in import statements") object betterFors + + /** Experimental support for package object values + */ + @compileTimeOnly("`packageObjectValues` can only be used at compile time in import statements") + object packageObjectValues end experimental /** The deprecated object contains features that are no longer officially suypported in Scala. diff --git a/presentation-compiler/src/main/dotty/tools/pc/HoverProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/HoverProvider.scala index 6aa0c3d7dc4d..3b2f4d2aa9b0 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/HoverProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/HoverProvider.scala @@ -25,6 +25,8 @@ import dotty.tools.dotc.util.SourcePosition import dotty.tools.pc.printer.ShortenedTypePrinter import dotty.tools.pc.printer.ShortenedTypePrinter.IncludeDefaultParam import dotty.tools.pc.utils.InteractiveEnrichments.* +import dotty.tools.dotc.ast.untpd.InferredTypeTree +import dotty.tools.dotc.core.StdNames object HoverProvider: @@ -131,7 +133,12 @@ object HoverProvider: .flatMap(symTpe => search.symbolDocumentation(symTpe._1, contentType)) .map(_.docstring()) .mkString("\n") - printer.expressionType(exprTpw) match + + val expresionTypeOpt = + if symbol.name == StdNames.nme.??? then + InferExpectedType(search, driver, params).infer() + else printer.expressionType(exprTpw) + expresionTypeOpt match case Some(expressionType) => val forceExpressionType = !pos.span.isZeroExtent || ( diff --git a/presentation-compiler/src/main/dotty/tools/pc/InferredTypeProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/InferredTypeProvider.scala index d8cdbcd8fe69..a0d726d5f382 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/InferredTypeProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/InferredTypeProvider.scala @@ -112,8 +112,8 @@ final class InferredTypeProvider( def imports: List[TextEdit] = printer.imports(autoImportsGen) - def printType(tpe: Type): String = - printer.tpe(tpe) + def printTypeAscription(tpe: Type, spaceBefore: Boolean = false): String = + (if spaceBefore then " : " else ": ") + printer.tpe(tpe) path.headOption match /* `val a = 1` or `var b = 2` @@ -124,7 +124,7 @@ final class InferredTypeProvider( * turns into * `.map((a: Int) => a + a)` */ - case Some(vl @ ValDef(sym, tpt, rhs)) => + case Some(vl @ ValDef(name, tpt, rhs)) => val isParam = path match case head :: next :: _ if next.symbol.isAnonymousFunction => true case head :: (b @ Block(stats, expr)) :: next :: _ @@ -136,9 +136,11 @@ final class InferredTypeProvider( val endPos = findNamePos(sourceText, vl, keywordOffset).endPos.toLsp adjustOpt.foreach(adjust => endPos.setEnd(adjust.adjustedEndPos)) + val spaceBefore = name.isOperatorName + new TextEdit( endPos, - ": " + printType(optDealias(tpt.typeOpt)) + { + printTypeAscription(optDealias(tpt.typeOpt), spaceBefore) + { if withParens then ")" else "" } ) @@ -197,7 +199,7 @@ final class InferredTypeProvider( * turns into * `def a[T](param : Int): Int = param` */ - case Some(df @ DefDef(name, _, tpt, rhs)) => + case Some(df @ DefDef(name, paramss, tpt, rhs)) => def typeNameEdit = /* NOTE: In Scala 3.1.3, `List((1,2)).map((<>,b) => ...)` * turns into `List((1,2)).map((:Inta,b) => ...)`, @@ -208,10 +210,12 @@ final class InferredTypeProvider( if tpt.endPos.end > df.namePos.end then tpt.endPos.toLsp else df.namePos.endPos.toLsp + val spaceBefore = name.isOperatorName && paramss.isEmpty + adjustOpt.foreach(adjust => end.setEnd(adjust.adjustedEndPos)) new TextEdit( end, - ": " + printType(optDealias(tpt.typeOpt)) + printTypeAscription(optDealias(tpt.typeOpt), spaceBefore) ) end typeNameEdit @@ -239,9 +243,10 @@ final class InferredTypeProvider( */ case Some(bind @ Bind(name, body)) => def baseEdit(withParens: Boolean) = + val spaceBefore = name.isOperatorName new TextEdit( bind.endPos.toLsp, - ": " + printType(optDealias(body.typeOpt)) + { + printTypeAscription(optDealias(body.typeOpt), spaceBefore) + { if withParens then ")" else "" } ) @@ -272,9 +277,10 @@ final class InferredTypeProvider( * `for(t: Int <- 0 to 10)` */ case Some(i @ Ident(name)) => + val spaceBefore = name.isOperatorName val typeNameEdit = new TextEdit( i.endPos.toLsp, - ": " + printType(optDealias(i.typeOpt.widen)) + printTypeAscription(optDealias(i.typeOpt.widen), spaceBefore) ) typeNameEdit :: imports diff --git a/presentation-compiler/src/main/dotty/tools/pc/PcDefinitionProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/PcDefinitionProvider.scala index 7d8e4dfad081..8ff43ba07358 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/PcDefinitionProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/PcDefinitionProvider.scala @@ -1,5 +1,6 @@ package dotty.tools.pc +import java.net.URI import java.nio.file.Paths import java.util.ArrayList @@ -16,6 +17,7 @@ import dotty.tools.dotc.core.Contexts.Context import dotty.tools.dotc.core.Flags.{Exported, ModuleClass} import dotty.tools.dotc.core.Symbols.* import dotty.tools.dotc.interactive.Interactive +import dotty.tools.dotc.interactive.Interactive.Include import dotty.tools.dotc.interactive.InteractiveDriver import dotty.tools.dotc.util.SourceFile import dotty.tools.dotc.util.SourcePosition @@ -51,10 +53,10 @@ class PcDefinitionProvider( given ctx: Context = driver.localContext(params) val indexedContext = IndexedContext(ctx) val result = - if findTypeDef then findTypeDefinitions(path, pos, indexedContext) - else findDefinitions(path, pos, indexedContext) + if findTypeDef then findTypeDefinitions(path, pos, indexedContext, uri) + else findDefinitions(path, pos, indexedContext, uri) - if result.locations().nn.isEmpty() then fallbackToUntyped(pos)(using ctx) + if result.locations().nn.isEmpty() then fallbackToUntyped(pos, uri)(using ctx) else result end definitions @@ -70,24 +72,26 @@ class PcDefinitionProvider( * @param pos cursor position * @return definition result */ - private def fallbackToUntyped(pos: SourcePosition)( + private def fallbackToUntyped(pos: SourcePosition, uri: URI)( using ctx: Context ) = lazy val untpdPath = NavigateAST .untypedPath(pos.span) .collect { case t: untpd.Tree => t } - definitionsForSymbol(untpdPath.headOption.map(_.symbol).toList, pos) + definitionsForSymbol(untpdPath.headOption.map(_.symbol).toList, uri, pos) end fallbackToUntyped private def findDefinitions( path: List[Tree], pos: SourcePosition, - indexed: IndexedContext + indexed: IndexedContext, + uri: URI, ): DefinitionResult = import indexed.ctx definitionsForSymbol( MetalsInteractive.enclosingSymbols(path, pos, indexed), + uri, pos ) end findDefinitions @@ -95,7 +99,8 @@ class PcDefinitionProvider( private def findTypeDefinitions( path: List[Tree], pos: SourcePosition, - indexed: IndexedContext + indexed: IndexedContext, + uri: URI, ): DefinitionResult = import indexed.ctx val enclosing = path.expandRangeToEnclosingApply(pos) @@ -108,24 +113,25 @@ class PcDefinitionProvider( case Nil => path.headOption match case Some(value: Literal) => - definitionsForSymbol(List(value.typeOpt.widen.typeSymbol), pos) + definitionsForSymbol(List(value.typeOpt.widen.typeSymbol), uri, pos) case _ => DefinitionResultImpl.empty case _ => - definitionsForSymbol(typeSymbols, pos) + definitionsForSymbol(typeSymbols, uri, pos) end findTypeDefinitions private def definitionsForSymbol( symbols: List[Symbol], + uri: URI, pos: SourcePosition )(using ctx: Context): DefinitionResult = symbols match case symbols @ (sym :: other) => val isLocal = sym.source == pos.source if isLocal then + val include = Include.definitions | Include.local val (exportedDefs, otherDefs) = - Interactive.findDefinitions(List(sym), driver, false, false) - .filter(_.source == sym.source) + Interactive.findTreesMatching(driver.openedTrees(uri), include, sym) .partition(_.tree.symbol.is(Exported)) otherDefs.headOption.orElse(exportedDefs.headOption) match diff --git a/presentation-compiler/src/main/dotty/tools/pc/PcInlayHintsProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/PcInlayHintsProvider.scala index 4b1b3f5fe7ba..cf4929dfc91d 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/PcInlayHintsProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/PcInlayHintsProvider.scala @@ -57,7 +57,7 @@ class PcInlayHintsProvider( .headOption .getOrElse(unit.tpdTree) .enclosedChildren(pos.span) - .flatMap(tpdTree => deepFolder(InlayHints.empty, tpdTree).result()) + .flatMap(tpdTree => deepFolder(InlayHints.empty(params.uri()), tpdTree).result()) private def adjustPos(pos: SourcePosition): SourcePosition = pos.adjust(text)._1 diff --git a/presentation-compiler/src/main/dotty/tools/pc/ScalaPresentationCompiler.scala b/presentation-compiler/src/main/dotty/tools/pc/ScalaPresentationCompiler.scala index a44c16ecc748..dc53525480c3 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/ScalaPresentationCompiler.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/ScalaPresentationCompiler.scala @@ -16,6 +16,7 @@ import scala.language.unsafeNulls import scala.meta.internal.metals.CompilerVirtualFileParams import scala.meta.internal.metals.EmptyCancelToken import scala.meta.internal.metals.EmptyReportContext +import scala.meta.internal.metals.PcQueryContext import scala.meta.internal.metals.ReportContext import scala.meta.internal.metals.ReportLevel import scala.meta.internal.metals.StdReportContext @@ -143,18 +144,18 @@ case class ScalaPresentationCompiler( override def semanticTokens( params: VirtualFileParams ): CompletableFuture[ju.List[Node]] = - compilerAccess.withInterruptableCompiler(Some(params))( + compilerAccess.withInterruptableCompiler( new ju.ArrayList[Node](), params.token() ) { access => val driver = access.compiler() new PcSemanticTokensProvider(driver, params).provide().asJava - } + }(params.toQueryContext) override def inlayHints( params: InlayHintsParams ): ju.concurrent.CompletableFuture[ju.List[l.InlayHint]] = - compilerAccess.withInterruptableCompiler(Some(params))( + compilerAccess.withInterruptableCompiler( new ju.ArrayList[l.InlayHint](), params.token(), ) { access => @@ -162,7 +163,7 @@ case class ScalaPresentationCompiler( new PcInlayHintsProvider(driver, params, search) .provide() .asJava - } + }(params.toQueryContext) override def getTasty( targetUri: URI, @@ -173,7 +174,7 @@ case class ScalaPresentationCompiler( } def complete(params: OffsetParams): CompletableFuture[l.CompletionList] = - compilerAccess.withInterruptableCompiler(Some(params))( + compilerAccess.withInterruptableCompiler( EmptyCompletionList(), params.token() ) { access => @@ -188,44 +189,43 @@ case class ScalaPresentationCompiler( folderPath, completionItemPriority ).completions() - - } + }(params.toQueryContext) def definition(params: OffsetParams): CompletableFuture[DefinitionResult] = - compilerAccess.withInterruptableCompiler(Some(params))( + compilerAccess.withInterruptableCompiler( DefinitionResultImpl.empty, params.token() ) { access => val driver = access.compiler() PcDefinitionProvider(driver, params, search).definitions() - } + }(params.toQueryContext) override def typeDefinition( params: OffsetParams ): CompletableFuture[DefinitionResult] = - compilerAccess.withInterruptableCompiler(Some(params))( + compilerAccess.withInterruptableCompiler( DefinitionResultImpl.empty, params.token() ) { access => val driver = access.compiler() PcDefinitionProvider(driver, params, search).typeDefinitions() - } + }(params.toQueryContext) def documentHighlight( params: OffsetParams ): CompletableFuture[ju.List[DocumentHighlight]] = - compilerAccess.withInterruptableCompiler(Some(params))( + compilerAccess.withInterruptableCompiler( List.empty[DocumentHighlight].asJava, params.token() ) { access => val driver = access.compiler() PcDocumentHighlightProvider(driver, params).highlights.asJava - } + }(params.toQueryContext) override def references( params: ReferencesRequest ): CompletableFuture[ju.List[ReferencesResult]] = - compilerAccess.withNonInterruptableCompiler(Some(params.file()))( + compilerAccess.withNonInterruptableCompiler( List.empty[ReferencesResult].asJava, params.file().token, ) { access => @@ -233,16 +233,16 @@ case class ScalaPresentationCompiler( PcReferencesProvider(driver, params) .references() .asJava - } + }(params.file().toQueryContext) def inferExpectedType(params: OffsetParams): CompletableFuture[ju.Optional[String]] = - compilerAccess.withInterruptableCompiler(Some(params))( + compilerAccess.withInterruptableCompiler( Optional.empty(), params.token, ) { access => val driver = access.compiler() new InferExpectedType(search, driver, params).infer().asJava - } + }(params.toQueryContext) def shutdown(): Unit = compilerAccess.shutdown() @@ -257,8 +257,6 @@ case class ScalaPresentationCompiler( symbol: String ): CompletableFuture[Optional[IPcSymbolInformation]] = compilerAccess.withNonInterruptableCompiler[Optional[IPcSymbolInformation]]( - None - )( Optional.empty(), EmptyCancelToken, ) { access => @@ -266,27 +264,27 @@ case class ScalaPresentationCompiler( .info(symbol) .map(_.asJava) .asJava - } + }(emptyQueryContext) def semanticdbTextDocument( filename: URI, code: String ): CompletableFuture[Array[Byte]] = val virtualFile = CompilerVirtualFileParams(filename, code) - compilerAccess.withNonInterruptableCompiler(Some(virtualFile))( + compilerAccess.withNonInterruptableCompiler( Array.empty[Byte], EmptyCancelToken ) { access => val driver = access.compiler() val provider = SemanticdbTextDocumentProvider(driver, folderPath) provider.textDocument(filename, code) - } + }(virtualFile.toQueryContext) def completionItemResolve( item: l.CompletionItem, symbol: String ): CompletableFuture[l.CompletionItem] = - compilerAccess.withNonInterruptableCompiler(None)( + compilerAccess.withNonInterruptableCompiler( item, EmptyCancelToken ) { access => @@ -294,7 +292,7 @@ case class ScalaPresentationCompiler( CompletionItemResolver.resolve(item, symbol, search, config)(using driver.currentCtx ) - } + }(emptyQueryContext) def autoImports( name: String, @@ -303,7 +301,7 @@ case class ScalaPresentationCompiler( ): CompletableFuture[ ju.List[scala.meta.pc.AutoImportsResult] ] = - compilerAccess.withNonInterruptableCompiler(Some(params))( + compilerAccess.withNonInterruptableCompiler( List.empty[scala.meta.pc.AutoImportsResult].asJava, params.token() ) { access => @@ -318,13 +316,13 @@ case class ScalaPresentationCompiler( ) .autoImports(isExtension) .asJava - } + }(params.toQueryContext) def implementAbstractMembers( params: OffsetParams ): CompletableFuture[ju.List[l.TextEdit]] = val empty: ju.List[l.TextEdit] = new ju.ArrayList[l.TextEdit]() - compilerAccess.withNonInterruptableCompiler(Some(params))( + compilerAccess.withNonInterruptableCompiler( empty, params.token() ) { pc => @@ -335,31 +333,31 @@ case class ScalaPresentationCompiler( search, config ) - } + }(params.toQueryContext) end implementAbstractMembers override def insertInferredType( params: OffsetParams ): CompletableFuture[ju.List[l.TextEdit]] = val empty: ju.List[l.TextEdit] = new ju.ArrayList[l.TextEdit]() - compilerAccess.withNonInterruptableCompiler(Some(params))( + compilerAccess.withNonInterruptableCompiler( empty, params.token() ) { pc => new InferredTypeProvider(params, pc.compiler(), config, search) .inferredTypeEdits() .asJava - } + }(params.toQueryContext) override def inlineValue( params: OffsetParams ): CompletableFuture[ju.List[l.TextEdit]] = val empty: Either[String, List[l.TextEdit]] = Right(List()) (compilerAccess - .withInterruptableCompiler(Some(params))(empty, params.token()) { pc => + .withInterruptableCompiler(empty, params.token()) { pc => new PcInlineValueProviderImpl(pc.compiler(), params) .getInlineTextEdits() - }) + }(params.toQueryContext)) .thenApply { case Right(edits: List[TextEdit]) => edits.asJava case Left(error: String) => throw new DisplayableException(error) @@ -371,7 +369,7 @@ case class ScalaPresentationCompiler( extractionPos: OffsetParams ): CompletableFuture[ju.List[l.TextEdit]] = val empty: ju.List[l.TextEdit] = new ju.ArrayList[l.TextEdit]() - compilerAccess.withInterruptableCompiler(Some(range))(empty, range.token()) { + compilerAccess.withInterruptableCompiler(empty, range.token()) { pc => new ExtractMethodProvider( range, @@ -382,7 +380,7 @@ case class ScalaPresentationCompiler( ) .extractMethod() .asJava - } + }(range.toQueryContext) end extractMethod override def convertToNamedArguments( @@ -397,13 +395,13 @@ case class ScalaPresentationCompiler( ): CompletableFuture[ju.List[l.TextEdit]] = val empty: Either[String, List[l.TextEdit]] = Right(List()) (compilerAccess - .withNonInterruptableCompiler(Some(params))(empty, params.token()) { pc => + .withNonInterruptableCompiler(empty, params.token()) { pc => new ConvertToNamedArgumentsProvider( pc.compiler(), params, argIndices ).convertToNamedArguments - }) + }(params.toQueryContext)) .thenApplyAsync { case Left(error: String) => throw new DisplayableException(error) case Right(edits: List[l.TextEdit]) => edits.asJava @@ -413,33 +411,33 @@ case class ScalaPresentationCompiler( params: ju.List[OffsetParams] ): CompletableFuture[ju.List[l.SelectionRange]] = CompletableFuture.completedFuture { - compilerAccess.withSharedCompiler(params.asScala.headOption)( + compilerAccess.withSharedCompiler( List.empty[l.SelectionRange].asJava ) { pc => new SelectionRangeProvider( pc.compiler(), params, ).selectionRange().asJava - } + }(params.asScala.headOption.map(_.toQueryContext).getOrElse(emptyQueryContext)) } end selectionRange def hover( params: OffsetParams ): CompletableFuture[ju.Optional[HoverSignature]] = - compilerAccess.withNonInterruptableCompiler(Some(params))( + compilerAccess.withNonInterruptableCompiler( ju.Optional.empty[HoverSignature](), params.token() ) { access => val driver = access.compiler() HoverProvider.hover(params, driver, search, config.hoverContentType()) - } + }(params.toQueryContext) end hover def prepareRename( params: OffsetParams ): CompletableFuture[ju.Optional[l.Range]] = - compilerAccess.withNonInterruptableCompiler(Some(params))( + compilerAccess.withNonInterruptableCompiler( Optional.empty[l.Range](), params.token() ) { access => @@ -447,19 +445,19 @@ case class ScalaPresentationCompiler( Optional.ofNullable( PcRenameProvider(driver, params, None).prepareRename().orNull ) - } + }(params.toQueryContext) def rename( params: OffsetParams, name: String ): CompletableFuture[ju.List[l.TextEdit]] = - compilerAccess.withNonInterruptableCompiler(Some(params))( + compilerAccess.withNonInterruptableCompiler( List[l.TextEdit]().asJava, params.token() ) { access => val driver = access.compiler() PcRenameProvider(driver, params, Some(name)).rename().asJava - } + }(params.toQueryContext) def newInstance( buildTargetIdentifier: String, @@ -473,13 +471,13 @@ case class ScalaPresentationCompiler( ) def signatureHelp(params: OffsetParams): CompletableFuture[l.SignatureHelp] = - compilerAccess.withNonInterruptableCompiler(Some(params))( + compilerAccess.withNonInterruptableCompiler( new l.SignatureHelp(), params.token() ) { access => val driver = access.compiler() SignatureHelpProvider.signatureHelp(driver, params, search) - } + }(params.toQueryContext) override def didChange( params: VirtualFileParams @@ -487,10 +485,10 @@ case class ScalaPresentationCompiler( CompletableFuture.completedFuture(Nil.asJava) override def didClose(uri: URI): Unit = - compilerAccess.withNonInterruptableCompiler(None)( + compilerAccess.withNonInterruptableCompiler( (), EmptyCancelToken - ) { access => access.compiler().close(uri) } + ) { access => access.compiler().close(uri) }(emptyQueryContext) override def withExecutorService( executorService: ExecutorService @@ -515,4 +513,19 @@ case class ScalaPresentationCompiler( override def isLoaded() = compilerAccess.isLoaded() + def additionalReportData() = + s"""|Scala version: $scalaVersion + |Classpath: + |${classpath + .map(path => s"$path [${if path.exists then "exists" else "missing"} ]") + .mkString(", ")} + |Options: + |${options.mkString(" ")} + |""".stripMargin + + extension (params: VirtualFileParams) + def toQueryContext = PcQueryContext(Some(params), additionalReportData) + + def emptyQueryContext = PcQueryContext(None, additionalReportData) + end ScalaPresentationCompiler diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionPos.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionPos.scala index 6d89cb663b9c..40f1ccd2e797 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionPos.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionPos.scala @@ -23,7 +23,9 @@ case class CompletionPos( query: String, originalCursorPosition: SourcePosition, sourceUri: URI, - withCURSOR: Boolean + withCURSOR: Boolean, + hasLeadingBacktick: Boolean, + hasTrailingBacktick: Boolean ): def queryEnd: Int = originalCursorPosition.point def stripSuffixEditRange: l.Range = new l.Range(originalCursorPosition.offsetToPos(queryStart), originalCursorPosition.offsetToPos(identEnd)) @@ -38,16 +40,35 @@ object CompletionPos: adjustedPath: List[Tree], wasCursorApplied: Boolean )(using Context): CompletionPos = - val identEnd = adjustedPath match + def hasBacktickAt(offset: Int): Boolean = + sourcePos.source.content().lift(offset).contains('`') + + val (identEnd, hasTrailingBacktick) = adjustedPath match case (refTree: RefTree) :: _ if refTree.name.toString.contains(Cursor.value) => - refTree.span.end - Cursor.value.length - case (refTree: RefTree) :: _ => refTree.span.end - case _ => sourcePos.end + val refTreeEnd = refTree.span.end + val hasTrailingBacktick = hasBacktickAt(refTreeEnd - 1) + val identEnd = refTreeEnd - Cursor.value.length + (if hasTrailingBacktick then identEnd - 1 else identEnd, hasTrailingBacktick) + case (refTree: RefTree) :: _ => + val refTreeEnd = refTree.span.end + val hasTrailingBacktick = hasBacktickAt(refTreeEnd - 1) + (if hasTrailingBacktick then refTreeEnd - 1 else refTreeEnd, hasTrailingBacktick) + case _ => (sourcePos.end, false) val query = Completion.completionPrefix(adjustedPath, sourcePos) val start = sourcePos.end - query.length() + val hasLeadingBacktick = hasBacktickAt(start - 1) - CompletionPos(start, identEnd, query.nn, sourcePos, offsetParams.uri.nn, wasCursorApplied) + CompletionPos( + start, + identEnd, + query.nn, + sourcePos, + offsetParams.uri.nn, + wasCursorApplied, + hasLeadingBacktick, + hasTrailingBacktick + ) /** * Infer the indentation by counting the number of spaces in the given line. diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionProvider.scala index 28519debec63..2a63d6a92a81 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionProvider.scala @@ -99,9 +99,9 @@ class CompletionProvider( * 4| $1$.sliding@@[Int](size, step) * */ - if qual.symbol.is(Flags.Synthetic) && qual.symbol.name.isInstanceOf[DerivedName] => + if qual.symbol.is(Flags.Synthetic) && qual.span.isZeroExtent && qual.symbol.name.isInstanceOf[DerivedName] => qual.symbol.defTree match - case valdef: ValDef => Select(valdef.rhs, name) :: tail + case valdef: ValDef if !valdef.rhs.isEmpty => Select(valdef.rhs, name) :: tail case _ => tpdPath0 case _ => tpdPath0 @@ -248,10 +248,17 @@ class CompletionProvider( range: Option[LspRange] = None ): CompletionItem = val oldText = params.text().nn.substring(completionPos.queryStart, completionPos.identEnd) - val editRange = if newText.startsWith(oldText) then completionPos.stripSuffixEditRange + val trimmedNewText = { + var nt = newText + if (completionPos.hasLeadingBacktick) nt = nt.stripPrefix("`") + if (completionPos.hasTrailingBacktick) nt = nt.stripSuffix("`") + nt + } + + val editRange = if trimmedNewText.startsWith(oldText) then completionPos.stripSuffixEditRange else completionPos.toEditRange - val textEdit = new TextEdit(range.getOrElse(editRange), wrapInBracketsIfRequired(newText)) + val textEdit = new TextEdit(range.getOrElse(editRange), wrapInBracketsIfRequired(trimmedNewText)) val item = new CompletionItem(label) item.setSortText(f"${idx}%05d") diff --git a/presentation-compiler/test/dotty/tools/pc/tests/CompilerCachingSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/CompilerCachingSuite.scala index 5e13c07b9e5f..b2d837e2ff50 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/CompilerCachingSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/CompilerCachingSuite.scala @@ -6,8 +6,10 @@ import dotty.tools.pc.ScalaPresentationCompiler import org.junit.{Before, Test} import scala.language.unsafeNulls -import scala.meta.internal.metals.EmptyCancelToken import scala.meta.internal.metals.CompilerOffsetParams +import scala.meta.internal.metals.EmptyCancelToken +import scala.meta.internal.metals.EmptyReportContext +import scala.meta.internal.metals.PcQueryContext import scala.meta.pc.OffsetParams import scala.concurrent.Future import scala.concurrent.Await @@ -26,20 +28,22 @@ class CompilerCachingSuite extends BasePCSuite: private def checkCompilationCount(expected: Int): Unit = presentationCompiler match case pc: ScalaPresentationCompiler => - val compilations = pc.compilerAccess.withNonInterruptableCompiler(None)(-1, EmptyCancelToken) { driver => + val compilations = pc.compilerAccess.withNonInterruptableCompiler(-1, EmptyCancelToken) { driver => driver.compiler().currentCtx.runId - }.get(timeout.length, timeout.unit) + }(emptyQueryContext).get(timeout.length, timeout.unit) assertEquals(expected, compilations, s"Expected $expected compilations but got $compilations") case _ => throw IllegalStateException("Presentation compiler should always be of type of ScalaPresentationCompiler") private def getContext(): Context = presentationCompiler match case pc: ScalaPresentationCompiler => - pc.compilerAccess.withNonInterruptableCompiler(None)(null, EmptyCancelToken) { driver => + pc.compilerAccess.withNonInterruptableCompiler(null, EmptyCancelToken) { driver => driver.compiler().currentCtx - }.get(timeout.length, timeout.unit) + }(emptyQueryContext).get(timeout.length, timeout.unit) case _ => throw IllegalStateException("Presentation compiler should always be of type of ScalaPresentationCompiler") + private def emptyQueryContext = PcQueryContext(None, () => "")(using EmptyReportContext) + @Before def beforeEach: Unit = presentationCompiler.restart() diff --git a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionBacktickSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionBacktickSuite.scala index 5d13c60b2fd5..d41261d2d21e 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionBacktickSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionBacktickSuite.scala @@ -179,3 +179,60 @@ class CompletionBacktickSuite extends BaseCompletionSuite: |} |""".stripMargin ) + + @Test def `add-backticks-around-identifier` = + checkEdit( + """|object Main { + | def `Foo Bar` = 123 + | Foo@@ + |} + |""".stripMargin, + """|object Main { + | def `Foo Bar` = 123 + | `Foo Bar` + |} + |""".stripMargin + ) + + @Test def `complete-inside-backticks` = + checkEdit( + """|object Main { + | def `Foo Bar` = 123 + | `Foo@@` + |} + |""".stripMargin, + """|object Main { + | def `Foo Bar` = 123 + | `Foo Bar` + |} + |""".stripMargin + ) + + @Test def `complete-inside-backticks-after-space` = + checkEdit( + """|object Main { + | def `Foo Bar` = 123 + | `Foo B@@a` + |} + |""".stripMargin, + """|object Main { + | def `Foo Bar` = 123 + | `Foo Bar` + |} + |""".stripMargin + ) + + @Test def `complete-inside-empty-backticks` = + checkEdit( + """|object Main { + | def `Foo Bar` = 123 + | `@@` + |} + |""".stripMargin, + """|object Main { + | def `Foo Bar` = 123 + | `Foo Bar` + |} + |""".stripMargin, + filter = _ == "Foo Bar: Int" + ) diff --git a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala index 4a30f9b06efa..2c216b4c40f7 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala @@ -147,6 +147,50 @@ class CompletionSuite extends BaseCompletionSuite: "XtensionMethod(a: Int): XtensionMethod" ) + @Test def tupleDirect = + check( + """ + |trait Foo { + | def setup: List[(String, String)] + |} + |object Foo { + | val foo: Foo = ??? + | foo.setup.exist@@ + |}""".stripMargin, + """|exists(p: ((String, String)) => Boolean): Boolean + |""".stripMargin + ) + + @Test def tupleAlias = + check( + """ + |trait Foo { + | def setup: List[Foo.TupleAliasResult] + |} + |object Foo { + | type TupleAliasResult = (String, String) + | val foo: Foo = ??? + | foo.setup.exist@@ + |}""".stripMargin, + """|exists(p: TupleAliasResult => Boolean): Boolean + |""".stripMargin + ) + + @Test def listAlias = + check( + """ + |trait Foo { + | def setup: List[Foo.ListAliasResult] + |} + |object Foo { + | type ListAliasResult = List[String] + | val foo: Foo = ??? + | foo.setup.exist@@ + |}""".stripMargin, + """|exists(p: ListAliasResult => Boolean): Boolean + |""".stripMargin + ) + @Ignore("This test should be handled by compiler fuzzy search") @Test def fuzzy = check( @@ -2168,3 +2212,14 @@ class CompletionSuite extends BaseCompletionSuite: """|build: Unit |""".stripMargin, ) + + @Test def i7191 = + check( + """|val x = Some(3).map(_.@@) + |""".stripMargin, + """|!=(x: Byte): Boolean + |!=(x: Char): Boolean + |!=(x: Double): Boolean + |""".stripMargin, + topLines = Some(3) + ) diff --git a/presentation-compiler/test/dotty/tools/pc/tests/edit/InsertInferredTypeSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/edit/InsertInferredTypeSuite.scala index a96dd78be138..c1a84d6abb79 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/edit/InsertInferredTypeSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/edit/InsertInferredTypeSuite.scala @@ -883,6 +883,93 @@ class InsertInferredTypeSuite extends BaseCodeActionSuite: |""".stripMargin ) + @Test def `operator-val` = + checkEdit( + """|object A { + | val <> = 1 + |} + |""".stripMargin, + """|object A { + | val ! : Int = 1 + |} + |""".stripMargin + ) + + @Test def `operator-def` = + checkEdit( + """|object A { + | def <> = 1 + |} + |""".stripMargin, + """|object A { + | def ! : Int = 1 + |} + |""".stripMargin + ) + + @Test def `operator-def-param` = + checkEdit( + """|object A { + | def <>[T] = 1 + |} + |""".stripMargin, + """|object A { + | def ![T]: Int = 1 + |} + |""".stripMargin + ) + + @Test def `operator-def-type-param` = + checkEdit( + """|object A { + | def <>(x: Int) = 1 + |} + |""".stripMargin, + """|object A { + | def !(x: Int): Int = 1 + |} + |""".stripMargin + ) + + @Test def `operator-for` = + checkEdit( + """|object A { + | def foo = for(<> <- List(1)) yield ! + |} + |""".stripMargin, + """|object A { + | def foo = for(! : Int <- List(1)) yield ! + |} + |""".stripMargin + ) + @Test def `operator-lambda` = + checkEdit( + """|object A { + | val foo: Int => Int = (<>) => ! + 1 + |} + |""".stripMargin, + """|object A { + | val foo: Int => Int = (! : Int) => ! + 1 + |} + |""".stripMargin + ) + + @Test def `operator-ident` = + checkEdit( + """|object A { + | def foo = + | val ! = 1 + | <> + |} + |""".stripMargin, + """|object A { + | def foo = + | val ! = 1 + | ! : Int + |} + |""".stripMargin + ) + def checkEdit( original: String, expected: String diff --git a/presentation-compiler/test/dotty/tools/pc/tests/highlight/DocumentHighlightSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/highlight/DocumentHighlightSuite.scala index 5d9893f6a1c1..4809bc5cd5b8 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/highlight/DocumentHighlightSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/highlight/DocumentHighlightSuite.scala @@ -1462,5 +1462,29 @@ class DocumentHighlightSuite extends BaseDocumentHighlightSuite: |""".stripMargin ) + @Test def i3053 = + check( + s"""import Aaaa.* + | + |def classDef2(cdef: List[Int]): Int = { + | def aaa(ddef: Thicket2): List[Int] = ddef match { + | case Thicket2(_) => ??? + | } + |${("//" + "x" * 64 + "\n") * 64} + | 1 + |}.<>("aaa") + | + |case class Thicket2(trees: List[Int]) {} + | + |object Aaaa { + | extension [T](x: T) + | def <>[U](aaa: String): T = { + | x + | } + |} + | + |""".stripMargin + ) + end DocumentHighlightSuite diff --git a/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverHoleSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverHoleSuite.scala new file mode 100644 index 000000000000..cc9bd9f39fac --- /dev/null +++ b/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverHoleSuite.scala @@ -0,0 +1,75 @@ +package dotty.tools.pc.tests.hover + +import dotty.tools.pc.base.BaseHoverSuite + +import org.junit.Test + +class HoverHoleSuite extends BaseHoverSuite: + @Test def basic = + check( + """object a { + | val x: Int = ?@@?? + |} + |""".stripMargin, + """|**Expression type**: + |```scala + |Int + |``` + |**Symbol signature**: + |```scala + |def ???: Nothing + |``` + |""".stripMargin + ) + + @Test def function = + check( + """object a { + | def m(i: Int) = ??? + | val x = m(??@@?) + |} + |""".stripMargin, + """|**Expression type**: + |```scala + |Int + |``` + |**Symbol signature**: + |```scala + |def ???: Nothing + |``` + |""".stripMargin + ) + + @Test def constructor = + check( + """object a { + | val x = List(1, ?@@??) + |} + |""".stripMargin, + """|**Expression type**: + |```scala + |Int + |``` + |**Symbol signature**: + |```scala + |def ???: Nothing + |``` + |""".stripMargin + ) + + @Test def bounds = + check( + """|trait Foo + |def foo[T <: Foo](a: T): Boolean = ??? + |val _ = foo(?@@??) + |""".stripMargin, + """|**Expression type**: + |```scala + |Foo + |``` + |**Symbol signature**: + |```scala + |def ???: Nothing + |``` + |""".stripMargin + ) diff --git a/presentation-compiler/test/dotty/tools/pc/utils/TestInlayHints.scala b/presentation-compiler/test/dotty/tools/pc/utils/TestInlayHints.scala index b9d3fd411dcc..5fb38ad88e19 100644 --- a/presentation-compiler/test/dotty/tools/pc/utils/TestInlayHints.scala +++ b/presentation-compiler/test/dotty/tools/pc/utils/TestInlayHints.scala @@ -3,8 +3,10 @@ package dotty.tools.pc.utils import scala.collection.mutable.ListBuffer import scala.meta.internal.jdk.CollectionConverters._ +import scala.meta.internal.pc.InlayHints import dotty.tools.pc.utils.InteractiveEnrichments.* +import com.google.gson.JsonElement import org.eclipse.lsp4j.InlayHint import org.eclipse.lsp4j.TextEdit import org.eclipse.{lsp4j => l} @@ -31,7 +33,7 @@ object TestInlayHints { case Right(labelParts) => labelParts.asScala.map(_.getValue()).toList } val data = - inlayHint.getData().asInstanceOf[Array[Any]] + InlayHints.fromData(inlayHint.getData().asInstanceOf[JsonElement])._2 buffer += "/*" labels.zip(data).foreach { case (label, data) => buffer += label.nn @@ -41,15 +43,13 @@ object TestInlayHints { buffer.toList.mkString } - private def readData(data: Any): List[String] = { - data match { - case data: String if data.isEmpty => Nil - case data: String => List("<<", data, ">>") - case data: l.Position => + private def readData(data: Either[String, l.Position]): List[String] = + data match + case Left("") => Nil + case Left(data) => List("<<", data, ">>") + case Right(data) => val str = s"(${data.getLine()}:${data.getCharacter()})" List("<<", str, ">>") - } - } def applyInlayHints(text: String, inlayHints: List[InlayHint]): String = { val textEdits = inlayHints.map { hint => diff --git a/project/Build.scala b/project/Build.scala index c1bac587636a..3e53990cfd56 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -292,7 +292,9 @@ object Build { "-deprecation", "-unchecked", //"-Wconf:cat=deprecation&msg=Unsafe:s", // example usage - "-Xfatal-warnings", // -Werror in modern usage + "-Werror", + //"-Wunused:all", + //"-rewrite", // requires -Werror:false since no rewrites are applied with errors "-encoding", "UTF8", "-language:implicitConversions", ), @@ -1207,7 +1209,6 @@ object Build { lazy val `scala2-library-bootstrapped` = project.in(file("scala2-library-bootstrapped")). withCommonSettings(Bootstrapped). dependsOn(dottyCompiler(Bootstrapped) % "provided; compile->runtime; test->test"). - settings(commonBootstrappedSettings). settings(scala2LibraryBootstrappedSettings). settings(moduleName := "scala2-library") // -Ycheck:all is set in project/scripts/scala2-library-tasty-mima.sh @@ -1219,7 +1220,6 @@ object Build { lazy val `scala2-library-cc` = project.in(file("scala2-library-cc")). withCommonSettings(Bootstrapped). dependsOn(dottyCompiler(Bootstrapped) % "provided; compile->runtime; test->test"). - settings(commonBootstrappedSettings). settings(scala2LibraryBootstrappedSettings). settings( moduleName := "scala2-library-cc", @@ -1233,8 +1233,8 @@ object Build { }, Compile / doc / scalacOptions += "-Ydocument-synthetic-types", scalacOptions += "-Ycompile-scala2-library", - scalacOptions += "-Yscala2Unpickler:never", - scalacOptions -= "-Xfatal-warnings", + scalacOptions += "-Yscala2-unpickler:never", + scalacOptions += "-Werror:false", Compile / compile / logLevel.withRank(KeyRanks.Invisible) := Level.Error, ivyConfigurations += SourceDeps.hide, transitiveClassifiers := Seq("sources"), @@ -1483,7 +1483,7 @@ object Build { BuildInfoPlugin.buildInfoDefaultSettings lazy val presentationCompilerSettings = { - val mtagsVersion = "1.4.2" + val mtagsVersion = "1.5.1" Seq( libraryDependencies ++= Seq( "org.lz4" % "lz4-java" % "1.8.0", @@ -1601,7 +1601,7 @@ object Build { dependsOn(`scala3-library-bootstrappedJS`). settings( bspEnabled := false, - scalacOptions --= Seq("-Xfatal-warnings", "-deprecation"), + scalacOptions --= Seq("-Werror", "-deprecation"), // Required to run Scala.js tests. Test / fork := false, diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index 08e1d7850a46..8427b4398c5f 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -14,6 +14,8 @@ object MiMaFilters { ProblemFilters.exclude[MissingClassProblem]("scala.runtime.stdLibPatches.language$experimental$quotedPatternsWithPolymorphicFunctions$"), ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.runtime.Patterns.higherOrderHoleWithTypes"), ProblemFilters.exclude[MissingClassProblem]("scala.annotation.internal.preview"), + ProblemFilters.exclude[MissingFieldProblem]("scala.runtime.stdLibPatches.language#experimental.packageObjectValues"), + ProblemFilters.exclude[MissingClassProblem]("scala.runtime.stdLibPatches.language$experimental$packageObjectValues$"), ), // Additions since last LTS @@ -96,6 +98,7 @@ object MiMaFilters { ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#MethodTypeModule.apply"), ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#MethodTypeMethods.methodTypeKind"), ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#MethodTypeMethods.isContextual"), + ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#ImplicitsModule.searchIgnoring"), // Change `experimental` annotation to a final class ProblemFilters.exclude[FinalClassProblem]("scala.annotation.experimental"), ), diff --git a/project/Scala2LibraryBootstrappedMiMaFilters.scala b/project/Scala2LibraryBootstrappedMiMaFilters.scala index 102a2a50e9d4..70070fddf3e2 100644 --- a/project/Scala2LibraryBootstrappedMiMaFilters.scala +++ b/project/Scala2LibraryBootstrappedMiMaFilters.scala @@ -15,11 +15,21 @@ object Scala2LibraryBootstrappedMiMaFilters { ProblemFilters.exclude[FinalClassProblem]("scala.language$experimental$"), ProblemFilters.exclude[FinalClassProblem]("scala.languageFeature$*$"), - // trait $init$ - ProblemFilters.exclude[DirectMissingMethodProblem]("scala.*.$init$"), - - // Value class extension methods - ProblemFilters.exclude[DirectMissingMethodProblem]("scala.*$extension"), + // Issue: https://github.com/scala/scala3/issues/22495 + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.ArrayOps.scala$collection$ArrayOps$$elemTag$extension"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.ArrayOps.iterateUntilEmpty$extension"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.StringOps.isLineBreak$extension"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.StringOps.isLineBreak2$extension"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.StringOps.linesSeparated$extension"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.StringOps.escape$extension"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.StringOps.toBooleanImpl$extension"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.StringOps.unwrapArg$extension"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.StringOps.iterateUntilEmpty$extension"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.Tuple2Zipped.coll1$extension"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.Tuple2Zipped.coll2$extension"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.Tuple3Zipped.coll1$extension"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.Tuple3Zipped.coll2$extension"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.Tuple3Zipped.coll3$extension"), // Companion module class ProblemFilters.exclude[FinalClassProblem]("scala.*$"), @@ -56,17 +66,8 @@ object Scala2LibraryBootstrappedMiMaFilters { ProblemFilters.exclude[FinalMethodProblem]("scala.io.Source.NoPositioner"), ProblemFilters.exclude[FinalMethodProblem]("scala.io.Source.RelaxedPosition"), ProblemFilters.exclude[FinalMethodProblem]("scala.io.Source.RelaxedPositioner"), - ProblemFilters.exclude[IncompatibleResultTypeProblem]("scala.collection.immutable.SortedMapOps.coll"), - ProblemFilters.exclude[IncompatibleResultTypeProblem]("scala.collection.immutable.TreeMap.empty"), - ProblemFilters.exclude[IncompatibleResultTypeProblem]("scala.collection.immutable.TreeMap.fromSpecific"), - ProblemFilters.exclude[IncompatibleResultTypeProblem]("scala.collection.mutable.ArrayBuilder#ofUnit.addAll"), - ProblemFilters.exclude[IncompatibleResultTypeProblem]("scala.collection.mutable.TreeMap.empty"), - ProblemFilters.exclude[IncompatibleResultTypeProblem]("scala.collection.mutable.TreeMap.fromSpecific"), - ProblemFilters.exclude[IncompatibleResultTypeProblem]("scala.reflect.ManifestFactory#NothingManifest.newArray"), - ProblemFilters.exclude[IncompatibleResultTypeProblem]("scala.reflect.ManifestFactory#NullManifest.newArray"), ProblemFilters.exclude[MissingFieldProblem]("scala.collection.ArrayOps#ReverseIterator.xs"), ProblemFilters.exclude[MissingFieldProblem]("scala.runtime.NonLocalReturnControl.value"), - ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.collection.immutable.SortedMapOps.coll"), ) ++ Seq( // DirectMissingMethodProblem "scala.collection.LinearSeqIterator#LazyCell.this", diff --git a/sbt-test/scala2-compat/erasure/build.sbt b/sbt-test/scala2-compat/erasure/build.sbt index 694101e79388..f705299f549d 100644 --- a/sbt-test/scala2-compat/erasure/build.sbt +++ b/sbt-test/scala2-compat/erasure/build.sbt @@ -1,3 +1,5 @@ +ThisBuild / fork := true + lazy val scala2Lib = project.in(file("scala2Lib")) .settings( scalaVersion := sys.props("plugin.scala2Version") diff --git a/sbt-test/scala2-compat/erasure/dottyApp/Api.scala b/sbt-test/scala2-compat/erasure/dottyApp/Api.scala index 7ce908820390..154e5027d2d1 100644 --- a/sbt-test/scala2-compat/erasure/dottyApp/Api.scala +++ b/sbt-test/scala2-compat/erasure/dottyApp/Api.scala @@ -195,4 +195,10 @@ class Z { def objectARRAY_88(x: Array[Any]): Unit = {} def objectARRAY_89(x: Array[AnyRef]): Unit = {} def objectARRAY_90(x: Array[AnyVal]): Unit = {} + + def nothing$ARRAY_91(x: Array[Nothing]): Unit = {} + def null$ARRAY_92(x: Array[Null]): Unit = {} + def nothing$ARRAY_93(x: Array[_ <: Nothing]): Unit = {} + def null$ARRAY_94(x: Array[_ <: Null]): Unit = {} + } diff --git a/sbt-test/scala2-compat/erasure/dottyApp/Main.scala b/sbt-test/scala2-compat/erasure/dottyApp/Main.scala index be2c6c737316..48b9575302a0 100644 --- a/sbt-test/scala2-compat/erasure/dottyApp/Main.scala +++ b/sbt-test/scala2-compat/erasure/dottyApp/Main.scala @@ -53,10 +53,10 @@ object Main { z.c_40(dummy) z.c_41(dummy) z.c_42(dummy) - z.b_43(dummy) + //z.b_43(dummy) z.c_44(dummy) z.c_45(dummy) - z.b_46(dummy) + //z.b_46(dummy) z.c_47(dummy) // z.a_48(dummy) // z.c_49(dummy) @@ -101,6 +101,10 @@ object Main { z.objectARRAY_88(dummy) z.objectARRAY_89(dummy) z.objectARRAY_90(dummy) + z.objectARRAY_91(dummy) + z.objectARRAY_92(dummy) + z.objectARRAY_93(dummy) + z.objectARRAY_94(dummy) val methods = classOf[scala2Lib.Z].getDeclaredMethods.toList ++ classOf[dottyApp.Z].getDeclaredMethods.toList methods.foreach { m => diff --git a/sbt-test/scala2-compat/erasure/scala2Lib/Api.scala b/sbt-test/scala2-compat/erasure/scala2Lib/Api.scala index 2578f0556ecb..14a96b8e4004 100644 --- a/sbt-test/scala2-compat/erasure/scala2Lib/Api.scala +++ b/sbt-test/scala2-compat/erasure/scala2Lib/Api.scala @@ -186,4 +186,10 @@ class Z { def objectARRAY_88(x: Array[Any]): Unit = {} def objectARRAY_89(x: Array[AnyRef]): Unit = {} def objectARRAY_90(x: Array[AnyVal]): Unit = {} + + def objectARRAY_91(x: Array[Nothing]): Unit = {} + def objectARRAY_92(x: Array[Null]): Unit = {} + def objectARRAY_93(x: Array[_ <: Nothing]): Unit = {} + def objectARRAY_94(x: Array[_ <: Null]): Unit = {} + } diff --git a/scala2-library-cc/src/scala/collection/concurrent/TrieMap.scala b/scala2-library-cc/src/scala/collection/concurrent/TrieMap.scala index 3bcbc60c8744..9c150e19ab9c 100644 --- a/scala2-library-cc/src/scala/collection/concurrent/TrieMap.scala +++ b/scala2-library-cc/src/scala/collection/concurrent/TrieMap.scala @@ -1071,6 +1071,7 @@ object TrieMap extends MapFactory[TrieMap] { // non-final as an extension point for parallel collections private[collection] class TrieMapIterator[K, V](var level: Int, private var ct: TrieMap[K, V], mustInit: Boolean = true) extends AbstractIterator[(K, V)] { + this:TrieMapIterator[K, V]^ => private val stack = new Array[Array[BasicNode]](7) private val stackpos = new Array[Int](7) private var depth = -1 @@ -1161,7 +1162,7 @@ private[collection] class TrieMapIterator[K, V](var level: Int, private var ct: /** Returns a sequence of iterators over subsets of this iterator. * It's used to ease the implementation of splitters for a parallel version of the TrieMap. */ - protected def subdivide(): Seq[Iterator[(K, V)]] = if (subiter ne null) { + protected def subdivide(): Seq[Iterator[(K, V)]^{this}] = if (subiter ne null) { // the case where an LNode is being iterated val it = newIterator(level + 1, ct, _mustInit = false) it.depth = -1 diff --git a/scala2-library-cc/src/scala/collection/immutable/Range.scala b/scala2-library-cc/src/scala/collection/immutable/Range.scala index 459591d1a9cb..11fed1d9b1af 100644 --- a/scala2-library-cc/src/scala/collection/immutable/Range.scala +++ b/scala2-library-cc/src/scala/collection/immutable/Range.scala @@ -643,6 +643,7 @@ private class RangeIterator( lastElement: Int, initiallyEmpty: Boolean ) extends AbstractIterator[Int] with Serializable { + this: RangeIterator^ => private[this] var _hasNext: Boolean = !initiallyEmpty private[this] var _next: Int = start override def knownSize: Int = if (_hasNext) (lastElement - _next) / step + 1 else 0 @@ -656,7 +657,7 @@ private class RangeIterator( value } - override def drop(n: Int): Iterator[Int] = { + override def drop(n: Int): Iterator[Int]^{this} = { if (n > 0) { val longPos = _next.toLong + step * n if (step > 0) { diff --git a/scala2-library-cc/src/scala/collection/mutable/UnrolledBuffer.scala b/scala2-library-cc/src/scala/collection/mutable/UnrolledBuffer.scala index cfb6d014ae9d..0b7da1430f38 100644 --- a/scala2-library-cc/src/scala/collection/mutable/UnrolledBuffer.scala +++ b/scala2-library-cc/src/scala/collection/mutable/UnrolledBuffer.scala @@ -18,6 +18,7 @@ import scala.collection.generic.DefaultSerializable import scala.reflect.ClassTag import scala.collection.immutable.Nil import language.experimental.captureChecking +import caps.unsafe.unsafeAssumePure /** A buffer that stores elements in an unrolled linked list. * @@ -259,13 +260,14 @@ object UnrolledBuffer extends StrictOptimizedClassTagSeqFactory[UnrolledBuffer] /** Unrolled buffer node. */ class Unrolled[T: ClassTag] private[collection] (var size: Int, var array: Array[T], var next: Unrolled[T], val buff: UnrolledBuffer[T] = null) { + //this: Unrolled[T]^ => private[collection] def this() = this(0, new Array[T](unrolledlength), null, null) private[collection] def this(b: UnrolledBuffer[T]) = this(0, new Array[T](unrolledlength), null, b) private def nextlength = if (buff eq null) unrolledlength else buff.calcNextLength(array.length) // adds and returns itself or the new unrolled if full - @tailrec final def append(elem: T): Unrolled[T] = if (size < array.length) { + @tailrec final def append(elem: T): Unrolled[T]^{this} = if (size < array.length) { array(size) = elem size += 1 this @@ -307,21 +309,21 @@ object UnrolledBuffer extends StrictOptimizedClassTagSeqFactory[UnrolledBuffer] if (idx < size) array(idx) else next.apply(idx - size) @tailrec final def update(idx: Int, newelem: T): Unit = if (idx < size) array(idx) = newelem else next.update(idx - size, newelem) - @tailrec final def locate(idx: Int): Unrolled[T] = + @tailrec final def locate(idx: Int): Unrolled[T]^{this} = if (idx < size) this else next.locate(idx - size) - def prepend(elem: T) = if (size < array.length) { + def prepend(elem: T): Unrolled[T] = if (size < array.length) { // shift the elements of the array right // then insert the element shiftright() array(0) = elem size += 1 - this + this.unsafeAssumePure } else { // allocate a new node and store element // then make it point to this val newhead = new Unrolled[T](buff) newhead append elem - newhead.next = this + newhead.next = this.unsafeAssumePure newhead } // shifts right assuming enough space @@ -340,7 +342,7 @@ object UnrolledBuffer extends StrictOptimizedClassTagSeqFactory[UnrolledBuffer] val r = array(idx) shiftleft(idx) size -= 1 - if (tryMergeWithNext()) buffer.lastPtr = this + if (tryMergeWithNext()) buffer.lastPtr = this.unsafeAssumePure r } else next.remove(idx - size, buffer) @@ -397,7 +399,7 @@ object UnrolledBuffer extends StrictOptimizedClassTagSeqFactory[UnrolledBuffer] curr.next = newnextnode // try to merge the last node of this with the newnextnode and fix tail pointer if needed - if (curr.tryMergeWithNext()) buffer.lastPtr = curr + if (curr.tryMergeWithNext()) buffer.lastPtr = curr.unsafeAssumePure else if (newnextnode.next eq null) buffer.lastPtr = newnextnode appended } diff --git a/scaladoc/src/dotty/tools/scaladoc/api.scala b/scaladoc/src/dotty/tools/scaladoc/api.scala index 8ff40644fac2..a6b5dfbb6933 100644 --- a/scaladoc/src/dotty/tools/scaladoc/api.scala +++ b/scaladoc/src/dotty/tools/scaladoc/api.scala @@ -143,7 +143,7 @@ object Signature: case class LinkToType(signature: Signature, dri: DRI, kind: Kind) case class HierarchyGraph(edges: Seq[(LinkToType, LinkToType)], sealedNodes: Set[LinkToType] = Set.empty): - def vertecies: Seq[LinkToType] = edges.flatten((a, b) => Seq(a, b)).distinct + def vertecies: Seq[LinkToType] = edges.flatten(using (a, b) => Seq(a, b)).distinct def verteciesWithId: Map[LinkToType, Int] = vertecies.zipWithIndex.toMap def +(edge: (LinkToType, LinkToType)): HierarchyGraph = this ++ Seq(edge) def ++(edges: Seq[(LinkToType, LinkToType)]): HierarchyGraph = diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala index 15c3071c38c9..110ee498a3ac 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala @@ -76,7 +76,7 @@ trait TypesSupport: else inner(tpe) ++ plain(".").l ++ suffix case tpe => inner(tpe) - // TODO #23 add support for all types signatures that makes sense + // TODO #23 add support for all types signatures that make sense private def inner( using Quotes, )( @@ -88,7 +88,7 @@ trait TypesSupport: ): SSignature = import reflect._ def noSupported(name: String): SSignature = - println(s"WARN: Unsupported type: $name: ${tp.show}") + report.warning(s"Unsupported type: $name: ${tp.show}") plain(s"Unsupported[$name]").l tp match case OrType(left, right) => diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala index ff4405d3ec71..44a1c3630a5f 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/Comments.scala @@ -112,7 +112,7 @@ abstract class MarkupConversion[T](val repr: Repr)(using dctx: DocContext) { case None => sym.dri DocLink.ToDRI(dri, targetText) case None => - val txt = s"No DRI found for query" + val txt = s"Couldn't resolve a member for the given link query" val msg = s"$txt: $queryStr" if (!summon[DocContext].args.noLinkWarnings) then diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/MemberLookup.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/MemberLookup.scala index 26c4fb06dfdf..0dab4a88907e 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/MemberLookup.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/MemberLookup.scala @@ -31,8 +31,8 @@ trait MemberLookup { def nearestPackage(sym: Symbol): Symbol = if sym.flags.is(Flags.Package) then sym else nearestPackage(sym.owner) - def nearestMembered(sym: Symbol): Symbol = - if sym.isClassDef || sym.flags.is(Flags.Package) then sym else nearestMembered(sym.owner) + def nearestMember(sym: Symbol): Symbol = + if sym.isClassDef || sym.flags.is(Flags.Package) then sym else nearestMember(sym.owner) val res: Option[(Symbol, String, Option[Symbol])] = { def toplevelLookup(querystrings: List[String]) = @@ -43,7 +43,7 @@ trait MemberLookup { ownerOpt match { case Some(owner) => - val nearest = nearestMembered(owner) + val nearest = nearestMember(owner) val nearestCls = nearestClass(owner) val nearestPkg = nearestPackage(owner) def relativeLookup(querystrings: List[String], owner: Symbol): Option[(Symbol, Option[Symbol])] = { diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/wiki/Entities.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/wiki/Entities.scala index f11e8095afe7..86e7298226ea 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/wiki/Entities.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/wiki/Entities.scala @@ -5,8 +5,7 @@ import scala.collection.{Seq => _, _} // import representations._ /** A body of text. A comment has a single body, which is composed of - * at least one block. Inside every body is exactly one summary (see - * [[scala.tools.nsc.doc.model.comment.Summary]]). */ + * at least one block. Inside every body is exactly one summary. */ final case class Body(blocks: Seq[Block]) { /** The summary text of the comment body. */ diff --git a/staging/src/scala/quoted/staging/QuoteDriver.scala b/staging/src/scala/quoted/staging/QuoteDriver.scala index 0131a56cd8aa..82e91f7d7888 100644 --- a/staging/src/scala/quoted/staging/QuoteDriver.scala +++ b/staging/src/scala/quoted/staging/QuoteDriver.scala @@ -43,7 +43,7 @@ private class QuoteDriver(appClassloader: ClassLoader) extends Driver: val compiledExpr = try - new QuoteCompiler().newRun(ctx).compileExpr(exprBuilder) + new QuoteCompiler().newRun(using ctx).compileExpr(exprBuilder) catch case ex: dotty.tools.FatalError => val enrichedMessage = s"""An unhandled exception was thrown in the staging compiler. diff --git a/tasty/src/dotty/tools/tasty/TastyReader.scala b/tasty/src/dotty/tools/tasty/TastyReader.scala index b5aa29f16954..d4374a76ff99 100644 --- a/tasty/src/dotty/tools/tasty/TastyReader.scala +++ b/tasty/src/dotty/tools/tasty/TastyReader.scala @@ -100,7 +100,7 @@ class TastyReader(val bytes: Array[Byte], start: Int, end: Int, val base: Int = /** Read an uncompressed Long stored in 8 bytes in big endian format */ def readUncompressedLong(): Long = { var x: Long = 0 - for (i <- 0 to 7) + for (_ <- 0 to 7) x = (x << 8) | (readByte() & 0xff) x } diff --git a/tests/best-effort/compiler-semanticdb-crash/err/ClassNode.java b/tests/best-effort/compiler-semanticdb-crash/err/ClassNode.java new file mode 100644 index 000000000000..0a2aabc411d7 --- /dev/null +++ b/tests/best-effort/compiler-semanticdb-crash/err/ClassNode.java @@ -0,0 +1,11 @@ +package dotty.tools.backend.jvm; + +public class ClassNode { + + public ClassNode(int api) { + } + + public ClassNode visitMethod(int access) { + return null; + } +} diff --git a/tests/best-effort/compiler-semanticdb-crash/err/Main.scala b/tests/best-effort/compiler-semanticdb-crash/err/Main.scala new file mode 100644 index 000000000000..cfc81a297a8e --- /dev/null +++ b/tests/best-effort/compiler-semanticdb-crash/err/Main.scala @@ -0,0 +1,4 @@ +package dotty.tools.backend.jvm + +val errorGenerator: Int = "0" +def readClass(bytes: Array[Byte]): ClassNode = ??? diff --git a/tests/best-effort/compiler-semanticdb-crash/main/Test.scala b/tests/best-effort/compiler-semanticdb-crash/main/Test.scala new file mode 100644 index 000000000000..8e5aee732d3e --- /dev/null +++ b/tests/best-effort/compiler-semanticdb-crash/main/Test.scala @@ -0,0 +1 @@ +def c = dotty.tools.backend.jvm.readClass(Array()) diff --git a/tests/neg-custom-args/captures/capture-vars-subtyping2.scala b/tests/neg-custom-args/captures/capture-vars-subtyping2.scala new file mode 100644 index 000000000000..205451ee41ed --- /dev/null +++ b/tests/neg-custom-args/captures/capture-vars-subtyping2.scala @@ -0,0 +1,44 @@ +import language.experimental.captureChecking +import caps.* + +trait BoundsTest: + + trait Bar { val f: () => Unit } + def bar(x: Bar^, y: () ->{x.f} Unit): Unit = ??? + + val b: Bar^ = ??? + + def testTransMixed[A^, + B >: CapSet <: A, + C >: CapSet <: CapSet^{B^}, + D >: CapSet <: C, + E >: CapSet <: CapSet^{D^}, + F >: CapSet <: CapSet^{A^,b}, + X >: CapSet <: CapSet^{F^,D^}, + Y >: CapSet^{F^} <: CapSet^{F^,A^,b}, + Z >: CapSet^{b} <: CapSet^{b,Y^}] = + val e: E = ??? + val e2: CapSet^{E^} = e + val ed: D = e + val ed2: CapSet^{D^} = e + val ec: C = e + val ec2: CapSet^{C^} = e + val eb: B = e + val eb2: CapSet^{B^} = e + val ea: A = e + val ea2: CapSet^{A^} = e + val ex: X = e // error + val ex2: CapSet^{X^} = e // error + val f: F = ??? + val f2: CapSet^{F^} = f + val y: Y = f + val y2: CapSet^{Y^} = f + val cb: CapSet^{b} = ??? + val z: Z = cb + val z2: CapSet^{Z^} = cb + + def callTransMixed = + val x, y, z: Bar^ = ??? + testTransMixed[CapSet^{x,y,z}, CapSet^{x,y,z}, CapSet^{x,y,z}, CapSet^{x,y,z}, CapSet^{x,y,z}, CapSet^{x,y,z}, CapSet^{x,y,z}, CapSet^{x,y,z}, CapSet^{b,x,y,z}] + testTransMixed[CapSet^{x,y,z}, CapSet^{x,y}, CapSet^{x,y}, CapSet^{x}, CapSet^{}, CapSet^{b,x}, CapSet^{b}, CapSet^{b,x}, CapSet^{b}] + testTransMixed[CapSet^{x,y,z}, CapSet^{x,y}, CapSet^{x,y}, CapSet^{x}, CapSet^{}, CapSet^{b,x}, CapSet^{b}, CapSet^{b,x}, CapSet^{b,x,y,z}] // error diff --git a/tests/neg-macros/i21916.check b/tests/neg-macros/i21916.check new file mode 100644 index 000000000000..58a8ac43782a --- /dev/null +++ b/tests/neg-macros/i21916.check @@ -0,0 +1,15 @@ + +-- Error: tests/neg-macros/i21916/Test_2.scala:3:27 -------------------------------------------------------------------- +3 |@main def Test = Macro.test() // error + | ^^^^^^^^^^^^ + | Exception occurred while executing macro expansion. + | java.lang.AssertionError: Illegal empty Array type constructor. Please supply a type parameter. + | at Macro$.testImpl(Macro_1.scala:8) + | + |--------------------------------------------------------------------------------------------------------------------- + |Inline stack trace + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + |This location contains code that was inlined from Macro_1.scala:3 +3 | inline def test() = ${testImpl} + | ^^^^^^^^^^^ + --------------------------------------------------------------------------------------------------------------------- diff --git a/tests/neg-macros/i21916/Macro_1.scala b/tests/neg-macros/i21916/Macro_1.scala new file mode 100644 index 000000000000..78af2cde2963 --- /dev/null +++ b/tests/neg-macros/i21916/Macro_1.scala @@ -0,0 +1,9 @@ +import scala.quoted._ +object Macro: + inline def test() = ${testImpl} + def testImpl(using Quotes): Expr[Any] = { + import quotes.reflect._ + val tpe = TypeRepr.of[Array[Byte]] match + case AppliedType(tycons, _) => tycons + Literal(ClassOfConstant(tpe)).asExpr + } diff --git a/tests/neg-macros/i21916/Test_2.scala b/tests/neg-macros/i21916/Test_2.scala new file mode 100644 index 000000000000..103bced0c04e --- /dev/null +++ b/tests/neg-macros/i21916/Test_2.scala @@ -0,0 +1,3 @@ +// lack of type ascription is on purpose, +// as that was part of what would cause the crash before. +@main def Test = Macro.test() // error diff --git a/tests/neg/22145.check b/tests/neg/22145.check new file mode 100644 index 000000000000..4592c42e9e7f --- /dev/null +++ b/tests/neg/22145.check @@ -0,0 +1,4 @@ +-- [E008] Not Found Error: tests/neg/22145.scala:5:7 ------------------------------------------------------------------- +5 | base.foo() // error + | ^^^^^^^^ + | value foo is not a member of foo.Collection diff --git a/tests/neg/22145.scala b/tests/neg/22145.scala new file mode 100644 index 000000000000..59d58b167ab4 --- /dev/null +++ b/tests/neg/22145.scala @@ -0,0 +1,8 @@ +package foo + +trait Collection: + val base: Collection = ??? + base.foo() // error + + object O extends Collection: + def foo(): Int = ??? diff --git a/tests/neg/22145b.check b/tests/neg/22145b.check new file mode 100644 index 000000000000..de605ce24276 --- /dev/null +++ b/tests/neg/22145b.check @@ -0,0 +1,36 @@ +-- [E008] Not Found Error: tests/neg/22145b.scala:15:19 ---------------------------------------------------------------- +15 | require(base.isWithin(p, start, end), "position is out of bounds") // error + | ^^^^^^^^^^^^^ + | value isWithin is not a member of Collection.this.Self +-- [E008] Not Found Error: tests/neg/22145b.scala:28:59 ---------------------------------------------------------------- +28 | def positionAfter(p: Position): Position = self.base.positionAfter(p) // error + | ^^^^^^^^^^^^^^^^^^^^^^^ + |value positionAfter is not a member of Collection.this.Self. + |An extension method was tried, but could not be fully constructed: + | + | this.positionAfter(self.base) + | + | failed with: + | + | Found: (self.base : Collection.this.Self) + | Required: foo.Collection.given_is_Slice_Collection.Self² + | + | where: Self is a type in trait Collection + | Self² is a type in object given_is_Slice_Collection which is an alias of Collection.this.Slice + | +-- [E008] Not Found Error: tests/neg/22145b.scala:29:50 ---------------------------------------------------------------- +29 | def apply(p: Position): Element = self.base.apply(p) // error + | ^^^^^^^^^^^^^^^ + |value apply is not a member of Collection.this.Self. + |An extension method was tried, but could not be fully constructed: + | + | this.apply(self.base) + | + | failed with: + | + | Found: (self.base : Collection.this.Self) + | Required: foo.Collection.given_is_Slice_Collection.Self² + | + | where: Self is a type in trait Collection + | Self² is a type in object given_is_Slice_Collection which is an alias of Collection.this.Slice + | diff --git a/tests/neg/22145b.scala b/tests/neg/22145b.scala new file mode 100644 index 000000000000..5b8de5672fba --- /dev/null +++ b/tests/neg/22145b.scala @@ -0,0 +1,40 @@ +package foo + +import language.experimental.modularity + +trait Collection: + me => + + type Self + type Position + type Element + + final class Slice(private[Collection] val base: Self, val start: Position, val end: Position): + + final def apply(p: Position): Element = + require(base.isWithin(p, start, end), "position is out of bounds") // error + base.apply(p) + + end Slice + + given Slice is Collection: + + type Position = me.Position + type Element = me.Element + + extension (self: Self) + def start: Position = self.start + def end: Position = self.end + def positionAfter(p: Position): Position = self.base.positionAfter(p) // error + def apply(p: Position): Element = self.base.apply(p) // error + + end given + + extension (self: Self) + + def start: Position + def end: Position + def positionAfter(p: Position): Position + def apply(p: Position): Element + + end extension diff --git a/tests/neg/22145c.check b/tests/neg/22145c.check new file mode 100644 index 000000000000..ddfb6b9daf1d --- /dev/null +++ b/tests/neg/22145c.check @@ -0,0 +1,4 @@ +-- [E008] Not Found Error: tests/neg/22145c.scala:4:35 ----------------------------------------------------------------- +4 | def bar(base: Collection) = base.foo // error + | ^^^^^^^^ + | value foo is not a member of foo.Collection diff --git a/tests/neg/22145c.scala b/tests/neg/22145c.scala new file mode 100644 index 000000000000..7776e57e7906 --- /dev/null +++ b/tests/neg/22145c.scala @@ -0,0 +1,8 @@ +package foo + +trait Collection: + def bar(base: Collection) = base.foo // error + object a extends Collection: + def foo: Int = 0 + object b extends Collection: + def foo: Int = 1 diff --git a/tests/neg/22145d.check b/tests/neg/22145d.check new file mode 100644 index 000000000000..ac6469c10b82 --- /dev/null +++ b/tests/neg/22145d.check @@ -0,0 +1,9 @@ +-- [E008] Not Found Error: tests/neg/22145d.scala:10:4 ----------------------------------------------------------------- +10 | 2.f() // error + | ^^^ + | value f is not a member of Int, but could be made available as an extension method. + | + | The following import might fix the problem: + | + | import foo.O2.f + | diff --git a/tests/neg/22145d.scala b/tests/neg/22145d.scala new file mode 100644 index 000000000000..bfb68e088322 --- /dev/null +++ b/tests/neg/22145d.scala @@ -0,0 +1,10 @@ +package foo + +class C[T]: + extension (x: T) def f(): Int = 1 + +object O1 extends C[String] +object O2 extends C[Int] + +def main = + 2.f() // error diff --git a/tests/neg/22145e.check b/tests/neg/22145e.check new file mode 100644 index 000000000000..e1c34e59f239 --- /dev/null +++ b/tests/neg/22145e.check @@ -0,0 +1,9 @@ +-- [E008] Not Found Error: tests/neg/22145e.scala:11:4 ----------------------------------------------------------------- +11 | 2.f() // error + | ^^^ + | value f is not a member of Int, but could be made available as an extension method. + | + | The following import might fix the problem: + | + | import foo.O2.Ext.f + | diff --git a/tests/neg/22145e.scala b/tests/neg/22145e.scala new file mode 100644 index 000000000000..579fd65c685e --- /dev/null +++ b/tests/neg/22145e.scala @@ -0,0 +1,11 @@ +package foo + +class C[T]: + object Ext: + extension (x: T) def f(): Int = 1 + +object O1 extends C[String] +object O2 extends C[Int] + +def main = + 2.f() // error diff --git a/tests/neg/22145f.check b/tests/neg/22145f.check new file mode 100644 index 000000000000..b870eb21057a --- /dev/null +++ b/tests/neg/22145f.check @@ -0,0 +1,10 @@ +-- [E008] Not Found Error: tests/neg/22145f.scala:11:6 ----------------------------------------------------------------- +11 | 2.f() // error + | ^^^ + | value f is not a member of Int, but could be made available as an extension method. + | + | One of the following imports might fix the problem: + | + | import C.this.O1.O2.Ext.f + | import C.this.O2.Ext.f + | diff --git a/tests/neg/22145f.scala b/tests/neg/22145f.scala new file mode 100644 index 000000000000..97f1336d1b1b --- /dev/null +++ b/tests/neg/22145f.scala @@ -0,0 +1,11 @@ +package foo + +class C[T]: + object Ext: + extension (x: T) def f(): Int = 1 + + object O1 extends C[String] + object O2 extends C[Int] + + def g = + 2.f() // error diff --git a/tests/neg/22145g.check b/tests/neg/22145g.check new file mode 100644 index 000000000000..175949ac113f --- /dev/null +++ b/tests/neg/22145g.check @@ -0,0 +1,4 @@ +-- [E008] Not Found Error: tests/neg/22145g.scala:10:4 ----------------------------------------------------------------- +10 | 2.f() // error + | ^^^ + | value f is not a member of Int diff --git a/tests/neg/22145g.scala b/tests/neg/22145g.scala new file mode 100644 index 000000000000..8b888516c044 --- /dev/null +++ b/tests/neg/22145g.scala @@ -0,0 +1,10 @@ +package foo + +class C[T]: + extension (x: T) def f(): Int = 1 + +object O: + val c0: C[String] = new C[String] + val c1: C[Int] = new C[Int] + // Currently no import suggestions here + 2.f() // error diff --git a/tests/neg/exports3.scala b/tests/neg/exports3.scala new file mode 100644 index 000000000000..eaea93d9f7ce --- /dev/null +++ b/tests/neg/exports3.scala @@ -0,0 +1,41 @@ +trait P: + def foo: Int + +class A extends P: + export this.foo // error + +trait Q extends P: + def bar: Int + +trait R extends P: + def baz: Int + val a1: A + val a2: A + +abstract class B extends R: + self => + export this.baz // error + export self.bar // error + export this.a1.foo + export self.a2.foo // error + export a2.foo // error + +abstract class D extends P: + val p: P + export p.foo + +abstract class E: + self: P => + export self.foo // error + +abstract class F: + self: P => + export this.foo // error + +class G(p: P): + self: P => + export p.foo + +class H(p: P): + self: P => + export this.p.foo \ No newline at end of file diff --git a/tests/neg/i18545.check b/tests/neg/i18545.check index 95edeacc0c95..1b336b0cc5ab 100644 --- a/tests/neg/i18545.check +++ b/tests/neg/i18545.check @@ -1,16 +1,26 @@ --- [E173] Reference Error: tests/neg/i18545.scala:13:20 ---------------------------------------------------------------- -13 | def test: IOLocal.IOLocalImpl[Int] = // error +-- [E173] Reference Error: tests/neg/i18545.scala:16:20 ---------------------------------------------------------------- +16 | def test: IOLocal.IOLocalImpl[Int] = // error | ^^^^^^^^^^^^^^^^^^^ |class IOLocalImpl cannot be accessed as a member of iolib.IOLocal.type from the top-level definitions in package tests. | private[IOLocal] class IOLocalImpl can only be accessed from object IOLocal in package iolib. --- [E173] Reference Error: tests/neg/i18545.scala:14:24 ---------------------------------------------------------------- -14 | IOLocal.IOLocalImpl.apply(42) // error +-- [E173] Reference Error: tests/neg/i18545.scala:17:24 ---------------------------------------------------------------- +17 | IOLocal.IOLocalImpl.apply(42) // error | ^^^^^^^^^^^^^^^^^^^^^^^^^ |method apply cannot be accessed as a member of iolib.IOLocal.IOLocalImpl.type from the top-level definitions in package tests. | private[IOLocal] method apply can only be accessed from object IOLocal in package iolib. --- [E050] Type Error: tests/neg/i18545.scala:15:22 --------------------------------------------------------------------- -15 | def test2 = IOLocal.IOLocalImpl(42) // error +-- [E050] Type Error: tests/neg/i18545.scala:18:22 --------------------------------------------------------------------- +18 | def test2 = IOLocal.IOLocalImpl(42) // error | ^^^^^^^^^^^^^^^^^^^ | object IOLocalImpl in object IOLocal does not take parameters | | longer explanation available when compiling with `-explain` +-- [E173] Reference Error: tests/neg/i18545.scala:19:22 ---------------------------------------------------------------- +19 | def test3 = IOLocal.AltIOLocalImpl.apply(42) // error + | ^^^^^^^^^^^^^^^^^^^^^^ + |object AltIOLocalImpl cannot be accessed as a member of iolib.IOLocal.type from the top-level definitions in package tests. + | private[IOLocal] object AltIOLocalImpl can only be accessed from object IOLocal in package iolib. +-- [E173] Reference Error: tests/neg/i18545.scala:20:22 ---------------------------------------------------------------- +20 | def test4 = IOLocal.AltIOLocalImpl(42) // error + | ^^^^^^^^^^^^^^^^^^^^^^ + |object AltIOLocalImpl cannot be accessed as a member of iolib.IOLocal.type from the top-level definitions in package tests. + | private[IOLocal] object AltIOLocalImpl can only be accessed from object IOLocal in package iolib. diff --git a/tests/neg/i18545.scala b/tests/neg/i18545.scala index 330482df11ae..86f18cd60771 100644 --- a/tests/neg/i18545.scala +++ b/tests/neg/i18545.scala @@ -7,9 +7,14 @@ package iolib: def apply[A](default: A): IO[IOLocal[A]] = IO(new IOLocalImpl(default)) private[IOLocal] final class IOLocalImpl[A](default: A) extends IOLocal[A] + object IOLocalImpl + + private[IOLocal] final class AltIOLocalImpl[A](default: A) extends IOLocal[A] package tests: import iolib.IOLocal def test: IOLocal.IOLocalImpl[Int] = // error IOLocal.IOLocalImpl.apply(42) // error def test2 = IOLocal.IOLocalImpl(42) // error + def test3 = IOLocal.AltIOLocalImpl.apply(42) // error + def test4 = IOLocal.AltIOLocalImpl(42) // error diff --git a/tests/neg/i20245.check b/tests/neg/i20245.check index 565bde7678b7..49d08c646f99 100644 --- a/tests/neg/i20245.check +++ b/tests/neg/i20245.check @@ -15,3 +15,17 @@ | Run with both -explain-cyclic and -Ydebug-cyclic to see full stack trace. | | longer explanation available when compiling with `-explain` +-- [E046] Cyclic Error: tests/neg/i20245/Typer_2.scala:10:7 ------------------------------------------------------------ +10 |import effekt.source.{ resolve } // error + | ^ + | Cyclic reference involving class Context + | + | The error occurred while trying to compute the base classes of class Context + | which required to compute the base classes of trait TyperOps + | which required to compute the signature of trait TyperOps + | which required to elaborate the export clause export unification.requireSubtype + | which required to compute the base classes of class Context + | + | Run with both -explain-cyclic and -Ydebug-cyclic to see full stack trace. + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i20245/Typer_2.scala b/tests/neg/i20245/Typer_2.scala index ed7f05de80d0..bf4718de759c 100644 --- a/tests/neg/i20245/Typer_2.scala +++ b/tests/neg/i20245/Typer_2.scala @@ -7,7 +7,7 @@ import effekt.util.messages.ErrorReporter import effekt.context.{ Context } // This import is also NECESSARY for the cyclic error -import effekt.source.{ resolve } +import effekt.source.{ resolve } // error trait TyperOps extends ErrorReporter { self: Context => diff --git a/tests/neg/i20265-1.check b/tests/neg/i20265-1.check new file mode 100644 index 000000000000..6eb9f4cfe5c6 --- /dev/null +++ b/tests/neg/i20265-1.check @@ -0,0 +1,5 @@ +-- Error: tests/neg/i20265-1.scala:4:6 --------------------------------------------------------------------------------- +4 | def apply(args: Tuple.Map[m.MirroredElemTypes, Expr]): Expr[T] = ??? // error + | ^ + | non-private method apply in trait Ops refers to private given instance m + | in its type signature (args: Tuple.Map[Ops.this.m.MirroredElemTypes, Expr]): Expr[T] diff --git a/tests/neg/i20265-1.scala b/tests/neg/i20265-1.scala new file mode 100644 index 000000000000..085d387ae901 --- /dev/null +++ b/tests/neg/i20265-1.scala @@ -0,0 +1,9 @@ +trait Expr[T] + +trait Ops[T](using m: scala.deriving.Mirror.ProductOf[T]) { + def apply(args: Tuple.Map[m.MirroredElemTypes, Expr]): Expr[T] = ??? // error +} + +case class P(a: Int) +object P extends Ops[P] + diff --git a/tests/neg/i20265.check b/tests/neg/i20265.check new file mode 100644 index 000000000000..607c2c2fde45 --- /dev/null +++ b/tests/neg/i20265.check @@ -0,0 +1,28 @@ +-- [E172] Type Error: tests/neg/i20265.scala:22:95 --------------------------------------------------------------------- +22 | println(summon[((String --> Unit) * (String --> Unit)) =:= Hinze[(String + String) --> Unit]]) // error + | ^ + | Cannot prove that (String --> Unit) * (String --> Unit) =:= Hinze[String + String --> Unit]. + | + | Note: a match type could not be fully reduced: + | + | trying to reduce Hinze[String + String --> Unit] + | failed since selector (String + String --> Unit)#unfix + | does not match case k1 + k2 --> v => Hinze[k1 --> v] * Hinze[k2 --> v] + | and cannot be shown to be disjoint from it either. + | Therefore, reduction cannot advance to the remaining case + | + | case k1 * k2 --> v => k1 --> Hinze[k2 --> v] +-- [E172] Type Error: tests/neg/i20265.scala:23:66 --------------------------------------------------------------------- +23 | println(summon[String =:= Hinze[Fix[Lambda[String]#L] --> Unit]]) // error + | ^ + | Cannot prove that String =:= Hinze[Fix[[X] =>> String + String * X + X * X] --> Unit]. + | + | Note: a match type could not be fully reduced: + | + | trying to reduce Hinze[Fix[[X] =>> String + String * X + X * X] --> Unit] + | failed since selector (Fix[[X] =>> String + String * X + X * X] --> Unit)#unfix + | does not match case k1 + k2 --> v => Hinze[k1 --> v] * Hinze[k2 --> v] + | and cannot be shown to be disjoint from it either. + | Therefore, reduction cannot advance to the remaining case + | + | case k1 * k2 --> v => k1 --> Hinze[k2 --> v] diff --git a/tests/neg/i20265.scala b/tests/neg/i20265.scala new file mode 100644 index 000000000000..1d312188ae99 --- /dev/null +++ b/tests/neg/i20265.scala @@ -0,0 +1,23 @@ +//> using options -source:3.3 + +trait Poly +trait -->[X, Y] extends Poly +trait +[X, Y] extends Poly +trait *[X, Y] extends Poly + +type Hinze[X <: Fix[?]] = X#unfix match + case (k1 + k2) --> v => Hinze[(k1 --> v)] * Hinze[(k2 --> v)] + case (k1 * k2) --> v => k1 --> Hinze[(k2 --> v)] + +trait Lambda[V]: + type Abs[X] = V * X + type App[X] = X * X + type L[X] = V + Abs[X] + App[X] + +trait Fix[F[X]]: + type unfix = F[Fix[F]] + +@main +def m = + println(summon[((String --> Unit) * (String --> Unit)) =:= Hinze[(String + String) --> Unit]]) // error + println(summon[String =:= Hinze[Fix[Lambda[String]#L] --> Unit]]) // error diff --git a/tests/neg/i22193.scala b/tests/neg/i22193.scala new file mode 100644 index 000000000000..f7ee5b1cf5e1 --- /dev/null +++ b/tests/neg/i22193.scala @@ -0,0 +1,32 @@ + +def fn2(arg: String, arg2: String)(f: String => Unit): Unit = f(arg) + +def fn3(arg: String, arg2: String)(f: => Unit): Unit = f + +def test1() = + + // ok baseline + fn2(arg = "blue sleeps faster than tuesday", arg2 = "the quick brown fox jumped over the lazy dog"): env => + val x = env + println(x) + + fn2( // error not a legal formal parameter for a function literal + arg = "blue sleeps faster than tuesday", + arg2 = "the quick brown fox jumped over the lazy dog"): env => + val x = env // error + println(x) + + fn2( + arg = "blue sleeps faster than tuesday", + arg2 = "the quick brown fox jumped over the lazy dog"): + env => // error indented definitions expected, identifier env found + val x = env + println(x) + +def test2() = + + fn3( // error missing argument list for value of type (=> Unit) => Unit + arg = "blue sleeps faster than tuesday", + arg2 = "the quick brown fox jumped over the lazy dog"): + val x = "Hello" // error + println(x) // error diff --git a/tests/neg/i22357.check b/tests/neg/i22357.check new file mode 100644 index 000000000000..213782a7fc6c --- /dev/null +++ b/tests/neg/i22357.check @@ -0,0 +1,4 @@ +-- Error: tests/neg/i22357.scala:1:0 ----------------------------------------------------------------------------------- +1 |@([A] =>> Int) // error + |^^^^^^^^^^^^^^ + |[A] =>> Int does not have a constructor diff --git a/tests/neg/i22357.scala b/tests/neg/i22357.scala new file mode 100644 index 000000000000..d572c150fb81 --- /dev/null +++ b/tests/neg/i22357.scala @@ -0,0 +1,2 @@ +@([A] =>> Int) // error +def i = 1 diff --git a/tests/neg/i22357a.check b/tests/neg/i22357a.check new file mode 100644 index 000000000000..9b2bcd2510d4 --- /dev/null +++ b/tests/neg/i22357a.check @@ -0,0 +1,4 @@ +-- Error: tests/neg/i22357a.scala:2:6 ---------------------------------------------------------------------------------- +2 | new ([A] =>> Int)(2) // error + | ^^^^^^^^^^^^^ + | [A] =>> Int does not have a constructor diff --git a/tests/neg/i22357a.scala b/tests/neg/i22357a.scala new file mode 100644 index 000000000000..b6c9c04fb268 --- /dev/null +++ b/tests/neg/i22357a.scala @@ -0,0 +1,2 @@ +def main = + new ([A] =>> Int)(2) // error diff --git a/tests/neg/i22440.check b/tests/neg/i22440.check new file mode 100644 index 000000000000..699d70f343c3 --- /dev/null +++ b/tests/neg/i22440.check @@ -0,0 +1,7 @@ +-- Error: tests/neg/i22440.scala:4:12 ---------------------------------------------------------------------------------- +4 |val _ = foo(1) // error + | ^ + | Implicit parameters should be provided with a `using` clause. + | This code can be rewritten automatically under -rewrite -source 3.7-migration. + | To disable the warning, please use the following option: + | "-Wconf:msg=Implicit parameters should be provided with a `using` clause:s" diff --git a/tests/neg/i22440.scala b/tests/neg/i22440.scala new file mode 100644 index 000000000000..79de3b71d2ec --- /dev/null +++ b/tests/neg/i22440.scala @@ -0,0 +1,4 @@ +//> using options -source future + +def foo(implicit x: Int) = x +val _ = foo(1) // error diff --git a/tests/neg/i22498.check b/tests/neg/i22498.check new file mode 100644 index 000000000000..ed57392e975d --- /dev/null +++ b/tests/neg/i22498.check @@ -0,0 +1,4 @@ +-- Error: tests/neg/i22498.scala:5:32 ---------------------------------------------------------------------------------- +5 | inline def proxy: Foo = new Foo(0) // error + | ^^^ + | Private constructors used in inline methods require @publicInBinary diff --git a/tests/neg/i22498.scala b/tests/neg/i22498.scala new file mode 100644 index 000000000000..54c59d2bb101 --- /dev/null +++ b/tests/neg/i22498.scala @@ -0,0 +1,5 @@ +import scala.annotation.publicInBinary + +class Foo: + private def this(x: Int) = this() + inline def proxy: Foo = new Foo(0) // error diff --git a/tests/neg/i22560.scala b/tests/neg/i22560.scala new file mode 100644 index 000000000000..2957ac4a1bf1 --- /dev/null +++ b/tests/neg/i22560.scala @@ -0,0 +1,22 @@ + +class A: + protected class B + +// This fails to compile, as expected +val x = A().B() // error + +object C: + protected val p = "protected" + protected def getString() = "Hello!" + protected class D: + def d = D() // ok + +// This fails to compile +// val y = C.p + +// And this also fails to compile +// val z = C.getString() + +// However, this compiles just fine. +val alpha = C.D() // error +val beta = new C.D() // error diff --git a/tests/neg/i22560b.scala b/tests/neg/i22560b.scala new file mode 100644 index 000000000000..58b41b6f8766 --- /dev/null +++ b/tests/neg/i22560b.scala @@ -0,0 +1,20 @@ + +class Enumeration: + protected class Val(i: Int): + def this() = this(42) + object Val + +class Test extends Enumeration: + val Hearts = Val(27) // error + val Diamonds = Val() // error + +package p: + private[p] class C(i: Int) // ctor proxy gets privateWithin of class + private[p] class D(i: Int) + object D + private class E(i: Int) + +package q: + def f() = p.C(42) // error + def g() = p.D(42) // error + def h() = p.E(42) // error diff --git a/tests/neg/i22560c/client_2.scala b/tests/neg/i22560c/client_2.scala new file mode 100644 index 000000000000..c04720cd207b --- /dev/null +++ b/tests/neg/i22560c/client_2.scala @@ -0,0 +1,11 @@ + +package i22560: + val alpha = C.D() // error + + class Test extends Enumeration: + val Hearts = Val(27) // error + val Diamonds = Val() // error + +package q: + def f() = p.C(42) // error + def g() = p.D(42) // error diff --git a/tests/neg/i22560c/lib_1.scala b/tests/neg/i22560c/lib_1.scala new file mode 100644 index 000000000000..2d9d24962b8d --- /dev/null +++ b/tests/neg/i22560c/lib_1.scala @@ -0,0 +1,16 @@ + +package i22560: + + object C: + protected class D + + class Enumeration: + protected class Val(i: Int): + def this() = this(42) + object Val + +package p: + private[p] class C(i: Int) // companion gets privateWithin of class + private[p] class D(i: Int) // ctor proxy gets privateWithin of class + object D + diff --git a/tests/neg/i22592.scala b/tests/neg/i22592.scala new file mode 100644 index 000000000000..6a9a89cacf2a --- /dev/null +++ b/tests/neg/i22592.scala @@ -0,0 +1,14 @@ +import scala.quoted.* + +trait Foo: + def inherited = () + +class Bar extends Foo: + def local = () + def localArg(arg: Any) = () + + def macro1(using Quotes): Expr[Unit] = '{ local } // error + def macro3(using Quotes): Expr[Unit] = '{ inherited } // error + def macro4(using Quotes): Expr[Unit] = '{ this.local } // error + def macro5(using Quotes): Expr[Unit] = '{ this.inherited } // error + def macro6(using Quotes): Expr[Unit] = '{ localArg(this) } // error // error diff --git a/tests/neg/i22620.scala b/tests/neg/i22620.scala new file mode 100644 index 000000000000..97d1d55e3302 --- /dev/null +++ b/tests/neg/i22620.scala @@ -0,0 +1,4 @@ + +import scala.collection.mutable.ArrayBuffer + +class PrivateTest[-M](private val v: ArrayBuffer[M]) // error diff --git a/tests/neg/i8069.scala b/tests/neg/i8069.scala deleted file mode 100644 index 50f8b7a3480e..000000000000 --- a/tests/neg/i8069.scala +++ /dev/null @@ -1,8 +0,0 @@ -trait A: - type B - -enum Test: - case Test(a: A, b: a.B) // error: Implementation restriction: case classes cannot have dependencies between parameters - -case class Test2(a: A, b: a.B) // error: Implementation restriction: case classes cannot have dependencies between parameters - diff --git a/tests/neg/inline-unstable-accessors.scala b/tests/neg/inline-unstable-accessors.scala index c02097f1921a..a7fa75c2c6c9 100644 --- a/tests/neg/inline-unstable-accessors.scala +++ b/tests/neg/inline-unstable-accessors.scala @@ -1,4 +1,4 @@ -//> using options -experimental -Werror -WunstableInlineAccessors -explain +//> using options -Werror -WunstableInlineAccessors -explain package foo import scala.annotation.publicInBinary diff --git a/tests/neg/named-tuples-mirror.check b/tests/neg/named-tuples-mirror.check new file mode 100644 index 000000000000..5c24e37cb2b4 --- /dev/null +++ b/tests/neg/named-tuples-mirror.check @@ -0,0 +1,8 @@ +-- [E172] Type Error: tests/neg/named-tuples-mirror.scala:6:47 --------------------------------------------------------- +6 | summon[Mirror.SumOf[(foo: Int, bla: String)]] // error + | ^ + |No given instance of type scala.deriving.Mirror.SumOf[(foo : Int, bla : String)] was found for parameter x of method summon in object Predef. Failed to synthesize an instance of type scala.deriving.Mirror.SumOf[(foo : Int, bla : String)]: type `(foo : Int, bla : String)` is not a generic sum because named tuples are not sealed classes +-- Error: tests/neg/named-tuples-mirror.scala:9:4 ---------------------------------------------------------------------- +9 | }]// error + | ^ + |MirroredElemLabels mismatch, expected: (("foo" : String), ("bla" : String)), found: (("foo" : String), ("ba" : String)). diff --git a/tests/neg/named-tuples-mirror.scala b/tests/neg/named-tuples-mirror.scala new file mode 100644 index 000000000000..d78fa104f3c5 --- /dev/null +++ b/tests/neg/named-tuples-mirror.scala @@ -0,0 +1,10 @@ +import scala.language.experimental.namedTuples +import scala.deriving.* +import scala.compiletime.* + +@main def Test = + summon[Mirror.SumOf[(foo: Int, bla: String)]] // error + val namedTuple = summon[Mirror.Of[(foo: Int, bla: String)]{ + type MirroredElemLabels = ("foo", "ba") + }]// error + diff --git a/tests/neg/publicInBinaryOverride.check b/tests/neg/publicInBinaryOverride.check index 73c60fa55d6a..e44692c78525 100644 --- a/tests/neg/publicInBinaryOverride.check +++ b/tests/neg/publicInBinaryOverride.check @@ -1,5 +1,5 @@ --- [E164] Declaration Error: tests/neg/publicInBinaryOverride.scala:10:15 ---------------------------------------------- -10 | override def f(): Unit = () // error - | ^ - | error overriding method f in class A of type (): Unit; - | method f of type (): Unit also needs to be declared with @publicInBinary +-- [E164] Declaration Error: tests/neg/publicInBinaryOverride.scala:8:15 ----------------------------------------------- +8 | override def f(): Unit = () // error + | ^ + | error overriding method f in class A of type (): Unit; + | method f of type (): Unit also needs to be declared with @publicInBinary diff --git a/tests/neg/publicInBinaryOverride.scala b/tests/neg/publicInBinaryOverride.scala index 6529bf09736a..4b9144d27540 100644 --- a/tests/neg/publicInBinaryOverride.scala +++ b/tests/neg/publicInBinaryOverride.scala @@ -1,5 +1,3 @@ -//> using options -experimental - import scala.annotation.publicInBinary class A: diff --git a/tests/pos-macros/i15413/Macro_1.scala b/tests/pos-macros/i15413/Macro_1.scala index f451742dff9e..56fd4f0f0887 100644 --- a/tests/pos-macros/i15413/Macro_1.scala +++ b/tests/pos-macros/i15413/Macro_1.scala @@ -1,4 +1,4 @@ -//> using options -experimental -Werror -WunstableInlineAccessors +//> using options -Werror -WunstableInlineAccessors import scala.quoted.* import scala.annotation.publicInBinary diff --git a/tests/pos-macros/i15413b/Macro_1.scala b/tests/pos-macros/i15413b/Macro_1.scala index df27b6267915..c1e9bab422f8 100644 --- a/tests/pos-macros/i15413b/Macro_1.scala +++ b/tests/pos-macros/i15413b/Macro_1.scala @@ -1,4 +1,4 @@ -//> using options -experimental -Werror -WunstableInlineAccessors +//> using options -Werror -WunstableInlineAccessors package bar diff --git a/tests/pos-macros/i18409.scala b/tests/pos-macros/i18409.scala index 800e192b81bb..d1806b1d4d83 100644 --- a/tests/pos-macros/i18409.scala +++ b/tests/pos-macros/i18409.scala @@ -1,4 +1,4 @@ -//> using options -Werror -Wunused:all +//> using options -Werror -Wunused:imports import scala.quoted.* diff --git a/tests/pos-macros/i19526b/Test.scala b/tests/pos-macros/i19526b/Test.scala index 96274091218f..1cc037298e01 100644 --- a/tests/pos-macros/i19526b/Test.scala +++ b/tests/pos-macros/i19526b/Test.scala @@ -1,5 +1,3 @@ -//> using options -experimental - package crash.test case class Stack private[crash] ( diff --git a/tests/pos/annot-main-22364.scala b/tests/pos/annot-main-22364.scala new file mode 100644 index 000000000000..205589b525bc --- /dev/null +++ b/tests/pos/annot-main-22364.scala @@ -0,0 +1,5 @@ +def id[T](x: T): T = x + +class ann(x: Int) extends annotation.Annotation + +@ann(id(22)) @main def blop = () diff --git a/tests/pos/annot-main-22364b.scala b/tests/pos/annot-main-22364b.scala new file mode 100644 index 000000000000..c4e3067d7325 --- /dev/null +++ b/tests/pos/annot-main-22364b.scala @@ -0,0 +1,6 @@ +import util.chaining.* + +class ann(x: Int = 1, y: Int) extends annotation.Annotation + +@ann(y = 22.tap(println)) @main def blop = () + diff --git a/tests/pos/annot-main-22364c.scala b/tests/pos/annot-main-22364c.scala new file mode 100644 index 000000000000..53a6abe1a56b --- /dev/null +++ b/tests/pos/annot-main-22364c.scala @@ -0,0 +1,10 @@ +package p + +object P1: + class ann(x: Int) extends annotation.Annotation + +object P2: + def id[T](x: T): T = x + +object P3: + @P1.ann(P2.id(22)) @main def blop = () diff --git a/tests/pos/better-fors-i21804.scala b/tests/pos/better-fors-i21804.scala new file mode 100644 index 000000000000..7c8c753bf7c3 --- /dev/null +++ b/tests/pos/better-fors-i21804.scala @@ -0,0 +1,13 @@ +import scala.language.experimental.betterFors + +case class Container[A](val value: A) { + def map[B](f: A => B): Container[B] = Container(f(value)) +} + +sealed trait Animal +case class Dog() extends Animal + +def opOnDog(dog: Container[Dog]): Container[Animal] = + for + v <- dog + yield v diff --git a/tests/pos/enum-refinement.scala b/tests/pos/enum-refinement.scala new file mode 100644 index 000000000000..e357125489cd --- /dev/null +++ b/tests/pos/enum-refinement.scala @@ -0,0 +1,12 @@ +enum Enum: + case EC(val x: Int) + +val a: Enum.EC { val x: 1 } = Enum.EC(1).asInstanceOf[Enum.EC { val x: 1 }] + +import scala.language.experimental.modularity + +enum EnumT: + case EC(tracked val x: Int) + +val b: EnumT.EC { val x: 1 } = EnumT.EC(1) + diff --git a/tests/pos/i11729.scala b/tests/pos/i11729.scala index e97b285ac6a2..79d3174dc2e9 100644 --- a/tests/pos/i11729.scala +++ b/tests/pos/i11729.scala @@ -6,7 +6,7 @@ type Return[X] = X match object Return: def apply[A](a:A):Return[A] = a match - case a: List[t] => a + case a: List[?] => a case a: Any => List(a) object Test1: @@ -18,7 +18,7 @@ type Boxed[X] = X match case Any => Box[X] def box[X](x: X): Boxed[X] = x match - case b: Box[t] => b + case b: Box[?] => b case x: Any => Box(x) case class Box[A](a:A): diff --git a/tests/pos/i17631.scala b/tests/pos/i17631.scala index 7b8a064493df..ddcb71354968 100644 --- a/tests/pos/i17631.scala +++ b/tests/pos/i17631.scala @@ -1,4 +1,4 @@ -//> using options -Xfatal-warnings -Wunused:all -deprecation -feature +//> using options -Werror -Wunused:all -deprecation -feature object foo { type Bar @@ -32,3 +32,23 @@ object Main { (bad1, bad2) } } + +def `i18388`: Unit = + def func(pred: [A] => A => Boolean): Unit = + val _ = pred + () + val _ = func + +trait L[T]: + type E + +def `i19748` = + type Warn1 = [T] => (l: L[T]) => T => l.E + type Warn2 = [T] => L[T] => T + type Warn3 = [T] => T => T + def use(x: (Warn1, Warn2, Warn3)) = x + use + +type NoWarning1 = [T] => (l: L[T]) => T => l.E +type NoWarning2 = [T] => L[T] => T +type NoWarning3 = [T] => T => T diff --git a/tests/pos/i18366.scala b/tests/pos/i18366.scala deleted file mode 100644 index 698510ad13a2..000000000000 --- a/tests/pos/i18366.scala +++ /dev/null @@ -1,10 +0,0 @@ -//> using options -Xfatal-warnings -Wunused:all - -trait Builder { - def foo(): Unit -} - -def repro = - val builder: Builder = ??? - import builder.{foo => bar} - bar() \ No newline at end of file diff --git a/tests/pos/i21433.scala b/tests/pos/i21433.scala new file mode 100644 index 000000000000..0efc4ac197ae --- /dev/null +++ b/tests/pos/i21433.scala @@ -0,0 +1,6 @@ +trait A[T]: + type R = T ?=> Unit + def f: R = () + +class B extends A[Int]: + override def f: R = () diff --git a/tests/pos/i21931.scala b/tests/pos/i21931.scala index 6765768874af..d4deb93f4c2e 100644 --- a/tests/pos/i21931.scala +++ b/tests/pos/i21931.scala @@ -1,13 +1,18 @@ -def f() = - val NotFound: Char = 'a' - class crashing() { - class issue() { - NotFound - } - class Module() { - val obligatory = - class anonIssue() { - issue() +object Test { + def f() = { + val NotFound: Char = 'a' + class crashing() { + class issue() { + NotFound + } + class Module() { + val obligatory = { + def anonIssue = { + issue() + } + anonIssue } + } } } +} diff --git a/tests/pos/i22193.scala b/tests/pos/i22193.scala new file mode 100644 index 000000000000..2ba7f920fbd7 --- /dev/null +++ b/tests/pos/i22193.scala @@ -0,0 +1,141 @@ + +def fn2(arg: String, arg2: String)(f: String => Unit): Unit = f(arg) + +def fn3(arg: String, arg2: String)(f: => Unit): Unit = f + +def test() = + + fn2(arg = "blue sleeps faster than tuesday", arg2 = "the quick brown fox jumped over the lazy dog"): env => + val x = env + println(x) + + // doesn't compile + fn2( + arg = "blue sleeps faster than tuesday", + arg2 = "the quick brown fox jumped over the lazy dog"): env => + val x = env + println(x) + + fn2( + arg = "blue sleeps faster than tuesday", + arg2 = "the quick brown fox jumped over the lazy dog"): env => + val x = env + println(x) + + fn2( + arg = "blue sleeps faster than tuesday", + arg2 = "the quick brown fox jumped over the lazy dog"): env => + val x = env + println(x) + + // does compile + fn2( + arg = "blue sleeps faster than tuesday", + arg2 = "the quick brown fox jumped over the lazy dog"): + env => + val x = env + println(x) + + // does compile + fn2( + arg = "blue sleeps faster than tuesday", + arg2 = "the quick brown fox jumped over the lazy dog" + ): env => + val x = env + println(x) + + fn2( + arg = "blue sleeps faster than tuesday", + arg2 = "the quick brown fox jumped over the lazy dog" + ): env => + val x = env + println(x) + + fn3( + arg = "blue sleeps faster than tuesday", + arg2 = "the quick brown fox jumped over the lazy dog"): + val x = "Hello" + println(x) + + fn3( + arg = "blue sleeps faster than tuesday", + arg2 = "the quick brown fox jumped over the lazy dog"): + val x = "Hello" + println(x) + + fn3( // arg at 3, body at 3 + arg = "blue sleeps faster than tuesday", + arg2 = "the quick brown fox jumped over the lazy dog"): + val x = "Hello" + println(x) + + fn3( // arg at 3, body at 1: not sure if sig indent of 1 is allowed, saw some comments from odersky + arg = "blue sleeps faster than tuesday", + arg2 = "the quick brown fox jumped over the lazy dog"): + val x = "Hello" + println(x) + + fn3( // arg at 3, body at 2: even if sig indent of 1 is not allowed, body is at fn3+2, not arg2-1 + arg = "blue sleeps faster than tuesday", + arg2 = "the quick brown fox jumped over the lazy dog"): + val x = "Hello" + println(x) + + fn3( // arg at 3, body at 4 + arg = "blue sleeps faster than tuesday", + arg2 = "the quick brown fox jumped over the lazy dog"): + val x = "Hello" + println(x) + +// don't turn innocent empty cases into functions +def regress(x: Int) = + x match + case 42 => + case _ => + +// previously lookahead calculated indent width at the colon +def k(xs: List[Int]) = + xs.foldLeft( + 0) + : (acc, x) => + acc + x + +def `test kit`(xs: List[Int]): Unit = + def addOne(i: Int): Int = i + 1 + def isPositive(i: Int): Boolean = i > 0 + // doesn't compile but would be nice + // first body is indented "twice", or, rather, first outdent establishes an intermediate indentation level + xs.map: x => + x + 1 + .filter: x => + x > 0 + xs.map: + addOne + .filter: + isPositive + + // does compile + xs + .map: x => + x + 1 + .filter: x => + x > 0 + + // does compile but doesn't look good, at least, to some people + xs.map: x => + x + 1 + .filter: x => + x > 0 + +def `tested kit`(xs: List[Int]): Unit = + { + def addOne(i: Int): Int = i.+(1) + def isPositive(i: Int): Boolean = i.>(0) + xs.map[Int]((x: Int) => x.+(1)).filter((x: Int) => x.>(0)) + xs.map[Int]((i: Int) => addOne(i)).filter((i: Int) => isPositive(i)) + xs.map[Int]((x: Int) => x.+(1)).filter((x: Int) => x.>(0)) + { + xs.map[Int]((x: Int) => x.+(1)).filter((x: Int) => x.>(0)) + () + } + } diff --git a/tests/pos/i22266.scala b/tests/pos/i22266.scala new file mode 100644 index 000000000000..2ecdf9492a93 --- /dev/null +++ b/tests/pos/i22266.scala @@ -0,0 +1,22 @@ +sealed trait NonPolygon +sealed trait Polygon + +sealed trait SymmetryAspect +sealed trait RotationalSymmetry extends SymmetryAspect +sealed trait MaybeRotationalSymmetry extends SymmetryAspect + +enum Shape: + case Circle extends Shape with NonPolygon with RotationalSymmetry + case Triangle extends Shape with Polygon with MaybeRotationalSymmetry + case Square extends Shape with Polygon with RotationalSymmetry + +object Shape: + + def hasPolygon( + rotationalSyms: Vector[Shape & RotationalSymmetry], + maybeSyms: Vector[Shape & MaybeRotationalSymmetry] + ): Boolean = + val all = rotationalSyms.concat(maybeSyms) + all.exists: + case _: Polygon => true + case _ => false diff --git a/tests/pos/i22266.unenum.scala b/tests/pos/i22266.unenum.scala new file mode 100644 index 000000000000..e7529b7edabe --- /dev/null +++ b/tests/pos/i22266.unenum.scala @@ -0,0 +1,22 @@ +sealed trait NonPolygon +sealed trait Polygon + +sealed trait SymmetryAspect +sealed trait RotationalSymmetry extends SymmetryAspect +sealed trait MaybeRotationalSymmetry extends SymmetryAspect + +sealed abstract class Shape + +object Shape: + case object Circle extends Shape with NonPolygon with RotationalSymmetry + case object Triangle extends Shape with Polygon with MaybeRotationalSymmetry + case object Square extends Shape with Polygon with RotationalSymmetry + + def hasPolygon( + rotationalSyms: Vector[Shape & RotationalSymmetry], + maybeSyms: Vector[Shape & MaybeRotationalSymmetry] + ): Boolean = + val all = rotationalSyms.concat(maybeSyms) + all.exists: + case _: Polygon => true + case _ => false diff --git a/tests/pos/i22332.scala b/tests/pos/i22332.scala new file mode 100644 index 000000000000..1b0b6a370329 --- /dev/null +++ b/tests/pos/i22332.scala @@ -0,0 +1,5 @@ + +object Foo: + val foo = 42 + // one space + \ No newline at end of file diff --git a/tests/pos/i22408.scala b/tests/pos/i22408.scala new file mode 100644 index 000000000000..17fd0fbb474d --- /dev/null +++ b/tests/pos/i22408.scala @@ -0,0 +1,11 @@ +object Obj: + @scala.annotation.static + val some_static_value: Int = { + val some_local_value: Int = { + val some_local_value_1 = ??? + some_local_value_1 + } + some_local_value + } + +class Obj diff --git a/tests/pos/i22440.scala b/tests/pos/i22440.scala new file mode 100644 index 000000000000..f72bb25d569f --- /dev/null +++ b/tests/pos/i22440.scala @@ -0,0 +1,4 @@ +//> using options "-Wconf:msg=Implicit parameters should be provided with a `using` clause:s" + +def foo(implicit x: Int) = x +val _ = foo(1) // warn \ No newline at end of file diff --git a/tests/pos/i22456.scala b/tests/pos/i22456.scala new file mode 100644 index 000000000000..ed1241bc4b39 --- /dev/null +++ b/tests/pos/i22456.scala @@ -0,0 +1,4 @@ +import language.experimental.modularity + +class T(tracked val y: Int) +class C(tracked val x: Int) extends T(x + 1) diff --git a/tests/pos/i22468.scala b/tests/pos/i22468.scala new file mode 100644 index 000000000000..265d6d3a89b7 --- /dev/null +++ b/tests/pos/i22468.scala @@ -0,0 +1,10 @@ +import Result.* +opaque type Result[+E, +A] = Success[A] | Error[E] + +object Result: + opaque type Success[+A] = A + sealed abstract class Error[+E] + + extension [E, A](self: Result[E, A]) + inline def transform[B]: B = ??? + def problem: Boolean = transform[Boolean] diff --git a/tests/pos/i22470.scala b/tests/pos/i22470.scala new file mode 100644 index 000000000000..83599f2564fc --- /dev/null +++ b/tests/pos/i22470.scala @@ -0,0 +1,17 @@ +trait A +trait OuterClass +trait MidClass +trait InnerClass + +object Obj: + def outerDef(a: A) = + new OuterClass { + def midDef(): Unit = { + new MidClass { + val valdef = new InnerClass { + def innerDef() = + println(a) + } + } + } + } diff --git a/tests/pos/i22518.scala b/tests/pos/i22518.scala new file mode 100644 index 000000000000..d530159701c4 --- /dev/null +++ b/tests/pos/i22518.scala @@ -0,0 +1,9 @@ +sealed trait Foo[T] +class Bar extends Foo[?] + +def mkFoo[T]: Foo[T] = + ??? + +def test: Unit = + mkFoo match + case _ => () diff --git a/tests/pos/i22548.scala b/tests/pos/i22548.scala new file mode 100644 index 000000000000..beb878d92670 --- /dev/null +++ b/tests/pos/i22548.scala @@ -0,0 +1,2 @@ +trait Bar[T] +class Foo[T <: Bar[T]] (private val buffer: Any) extends AnyVal diff --git a/tests/pos/i22560.scala b/tests/pos/i22560.scala new file mode 100644 index 000000000000..af4382ba5a15 --- /dev/null +++ b/tests/pos/i22560.scala @@ -0,0 +1,32 @@ + +package companionless: + + class Enumeration: + protected class Val(i: Int): + def this() = this(42) + + class Test extends Enumeration: + val Hearts = Val(27) + val Diamonds = Val() + + +package companioned: + + class Enumeration: + protected class Val(i: Int): + def this() = this(42) + protected object Val + + class Test extends Enumeration: + val Hearts = Val(27) + val Diamonds = Val() + +package p: + + package internal: + + protected[p] class P(i : Int) + private[p] class Q(i : Int) + + def f = internal.P(42) + def g = internal.Q(42) diff --git a/tests/pos/i22560b/client_2.scala b/tests/pos/i22560b/client_2.scala new file mode 100644 index 000000000000..bb57276c12c6 --- /dev/null +++ b/tests/pos/i22560b/client_2.scala @@ -0,0 +1,17 @@ + +package companionless: + + class Test extends Enumeration: + val Hearts = Val(27) + val Diamonds = Val() + + +package companioned: + + class Test extends Enumeration: + val Hearts = Val(27) + val Diamonds = Val() + +package p: + + def f = internal.P(42) diff --git a/tests/pos/i22560b/lib_1.scala b/tests/pos/i22560b/lib_1.scala new file mode 100644 index 000000000000..d247d63ec9cf --- /dev/null +++ b/tests/pos/i22560b/lib_1.scala @@ -0,0 +1,19 @@ + +package companionless: + + class Enumeration: + protected class Val(i: Int): + def this() = this(42) + +package companioned: + + class Enumeration: + protected class Val(i: Int): + def this() = this(42) + protected object Val + +package p: + + package internal: + + protected[p] class P(i : Int) diff --git a/tests/pos/i22592.scala b/tests/pos/i22592.scala new file mode 100644 index 000000000000..f6a1f2eff696 --- /dev/null +++ b/tests/pos/i22592.scala @@ -0,0 +1,15 @@ +import scala.quoted.* + +trait Foo: + def inherited = () + +object Bar extends Foo: + def local = () + def localArg(arg: Any) = () + + def macro1(using Quotes): Expr[Unit] = '{ local } + def macro2(using Quotes): Expr[Unit] = '{ Bar.inherited } + def macro3(using Quotes): Expr[Unit] = '{ inherited } + def macro4(using Quotes): Expr[Unit] = '{ this.local } + def macro5(using Quotes): Expr[Unit] = '{ this.inherited } + def macro6(using Quotes): Expr[Unit] = '{ localArg(this) } diff --git a/tests/pos/i22608.scala b/tests/pos/i22608.scala new file mode 100644 index 000000000000..e4b49e87769f --- /dev/null +++ b/tests/pos/i22608.scala @@ -0,0 +1,48 @@ + +def f(i: Int) = i +def g(i: Int, j: Int) = i+j + +def t = + val y = f( + if (true)// then + val x = 1 + 5 + else 7 + ) + y + +def u(j: Int) = + val y = g( + if (true)// then + val x = 1 + 5 + else 7, + j + ) + y + +def b(k: Boolean): Int = + f( + if ( + k + && b(!k) > 0 + ) then 27 + else 42 + ) + +def p(b: Boolean) = + import collection.mutable.ListBuffer + val xs, ys = ListBuffer.empty[String] + (if (b) + xs + else + ys) += "hello, world" + (xs.toString, ys.toString) + +def q(b: Boolean) = + import collection.mutable.ListBuffer + val xs, ys = ListBuffer.empty[String] + (if (b) + then xs + else ys) += "hello, world" + (xs.toString, ys.toString) diff --git a/tests/pos/i3323.scala b/tests/pos/i3323.scala deleted file mode 100644 index 94d072d4a2fc..000000000000 --- a/tests/pos/i3323.scala +++ /dev/null @@ -1,9 +0,0 @@ -//> using options -Xfatal-warnings -deprecation -feature - -class Foo { - def foo[A](lss: List[List[A]]): Unit = { - lss match { - case xss: List[List[A]] => - } - } -} diff --git a/tests/pos/interleavingExperimental.scala b/tests/pos/interleavingExperimental.scala deleted file mode 100644 index 63227ef1ebfe..000000000000 --- a/tests/pos/interleavingExperimental.scala +++ /dev/null @@ -1,5 +0,0 @@ -//> using options --source 3.5 - -import scala.language.experimental.clauseInterleaving - -def ba[A](x: A)[B](using B): B = summon[B] \ No newline at end of file diff --git a/tests/pos/match-type-disjoint-22076.scala b/tests/pos/match-type-disjoint-22076.scala new file mode 100644 index 000000000000..c10555501b97 --- /dev/null +++ b/tests/pos/match-type-disjoint-22076.scala @@ -0,0 +1,8 @@ +trait Foo[CP <: NonEmptyTuple]: + type EndNode = Tuple.Last[CP] + +def f(end: Foo[?]): end.EndNode = + ??? + +trait Bar[CP <: NonEmptyTuple] extends Foo[CP]: + val v: EndNode = f(this) diff --git a/tests/pos/tuple-exaustivity.scala b/tests/pos/tuple-exaustivity.scala deleted file mode 100644 index a27267fc89e5..000000000000 --- a/tests/pos/tuple-exaustivity.scala +++ /dev/null @@ -1,6 +0,0 @@ -//> using options -Xfatal-warnings -deprecation -feature - -def test(t: Tuple) = - t match - case Tuple() => - case head *: tail => diff --git a/tests/printing/dependent-annot-default-args.check b/tests/printing/dependent-annot-default-args.check index f457d5d62edb..ccb988e83663 100644 --- a/tests/printing/dependent-annot-default-args.check +++ b/tests/printing/dependent-annot-default-args.check @@ -23,15 +23,16 @@ package { new dependent-annot-default-args$package() final module class dependent-annot-default-args$package() extends Object() { this: dependent-annot-default-args$package.type => - def f(x: Int): Int @annot(x) = x + def f(x: Any): Any @annot(x) = x def f2(x: Int): Int @annot2( y = Array.apply[Any](["Hello",x : Any]*)(scala.reflect.ClassTag.Any)) = x + def f3(x: Any, y: Any): Any @annot(x = x, y = y) = x def test: Unit = { val y: Int = ??? - val z: Int @annot(y) = f(y) + val z: Any @annot(y) = f(y) val z2: Int @annot2( y = Array.apply[Any](["Hello",y : Any]*)(scala.reflect.ClassTag.Any) @@ -41,6 +42,78 @@ package { @annot2( y = Array.apply[Any](["Hello",y : Any]*)(scala.reflect.ClassTag.Any)) val z4: Int = 45 + val z5: annot = + { + val y$1: Array[String] = + Array.apply[String](["World" : String]*)( + scala.reflect.ClassTag.apply[String](classOf[String])) + new annot(x = 1, y = y$1) + } + val z6: annot2 = + { + val y$2: Array[Any] = + Array.apply[Any](["World" : Any]*)(scala.reflect.ClassTag.Any) + new annot2(x = 1, y = y$2) + } + @annot(x = 2, + y = + { + val y$3: Array[String] = + Array.apply[String](["World" : String]*)( + scala.reflect.ClassTag.apply[String](classOf[String])) + new annot(x = 1, y = y$3) + } + ) val z7: Int = 45 + @annot(x = 4, + y = + 3: + Int @annot(x = 1, + y = + Array.apply[String](["World" : String]*)( + scala.reflect.ClassTag.apply[String](classOf[String])) + ) + ) val z8: Int = 45 + val z9: + Int @annot(x = 2, + y = + { + val y$4: Array[String] = + Array.apply[String](["World" : String]*)( + scala.reflect.ClassTag.apply[String](classOf[String])) + new annot(x = 1, y = y$4) + } + ) + = 46 + @annot(x = 4, + y = + 3: + Int @annot(x = 1, + y = + Array.apply[String](["World" : String]*)( + scala.reflect.ClassTag.apply[String](classOf[String])) + ) + ) val z10: Int = 45 + val z11: Any @annot(annot) = + f( + { + val y$5: Array[String] = + Array.apply[String](["World" : String]*)( + scala.reflect.ClassTag.apply[String](classOf[String])) + new annot(x = 1, y = y$5) + } + ) + val z12: Any @annot(x = x, y = y) = + f3( + Array.apply[String](["World" : String]*)( + scala.reflect.ClassTag.apply[String](classOf[String])), + 1) + val z13: Any @annot(x = x, y = y) = + { + val y$6: Array[String] = + Array.apply[String](["World" : String]*)( + scala.reflect.ClassTag.apply[String](classOf[String])) + f3(x = 1, y = y$6) + } () } } diff --git a/tests/printing/dependent-annot-default-args.scala b/tests/printing/dependent-annot-default-args.scala index 11fc9ef52cc9..f3cb2a82c910 100644 --- a/tests/printing/dependent-annot-default-args.scala +++ b/tests/printing/dependent-annot-default-args.scala @@ -1,8 +1,9 @@ class annot(x: Any, y: Any = 42) extends annotation.Annotation class annot2(x: Any = -1, y: Array[Any] = Array("Hello")) extends annotation.Annotation -def f(x: Int): Int @annot(x) = x +def f(x: Any): Any @annot(x) = x def f2(x: Int): Int @annot2(y = Array("Hello", x)) = x +def f3(x: Any, y: Any): Any @annot(y=y, x=x) = x def test = val y: Int = ??? @@ -13,3 +14,14 @@ def test = @annot(44) val z3 = 45 @annot2(y = Array("Hello", y)) val z4 = 45 + // Arguments are still lifted if the annotation class is instantiated + // explicitly. See #22526. + val z5 = new annot(y = Array("World"), x = 1) + val z6 = new annot2(y = Array("World"), x = 1) + @annot(y = new annot(y = Array("World"), x = 1), x = 2) val z7 = 45 + @annot(y = 3: Int @annot(y = Array("World"), x = 1), x = 4) val z8 = 45 + val z9: Int @annot(y = new annot(y = Array("World"), x = 1), x = 2) = 46 + @annot(y = 3: Int @annot(y = Array("World"), x = 1), x = 4) val z10 = 45 + val z11 = f(new annot(y = Array("World"), x = 1)) + val z12 = f3(Array("World"), 1) + val z13 = f3(y=Array("World"), x=1) diff --git a/tests/printing/posttyper/i22533.check b/tests/printing/posttyper/i22533.check new file mode 100644 index 000000000000..33c023d94a74 --- /dev/null +++ b/tests/printing/posttyper/i22533.check @@ -0,0 +1,25 @@ +[[syntax trees at end of posttyper]] // tests/printing/posttyper/i22533.scala +package { + @SourceFile("tests/printing/posttyper/i22533.scala") trait A() extends Any { + override def equals(x: Any): Boolean = ??? + override def hashCode(): Int = ??? + } + @SourceFile("tests/printing/posttyper/i22533.scala") final class Foo(u: Int) + extends AnyVal(), A { + override def hashCode(): Int = Foo.this.u.hashCode() + override def equals(x$0: Any): Boolean = + x$0 match + { + case x$0 @ _:Foo @unchecked => this.u.==(x$0.u) + case _ => false + } + private[this] val u: Int + } + final lazy module val Foo: Foo = new Foo() + @SourceFile("tests/printing/posttyper/i22533.scala") final module class Foo() + extends AnyRef() { this: Foo.type => + private def writeReplace(): AnyRef = + new scala.runtime.ModuleSerializationProxy(classOf[Foo.type]) + } +} + diff --git a/tests/printing/posttyper/i22533.flags b/tests/printing/posttyper/i22533.flags new file mode 100644 index 000000000000..21379f85d52a --- /dev/null +++ b/tests/printing/posttyper/i22533.flags @@ -0,0 +1 @@ +-Ycompile-scala2-library diff --git a/tests/printing/posttyper/i22533.scala b/tests/printing/posttyper/i22533.scala new file mode 100644 index 000000000000..07e9e1c4c011 --- /dev/null +++ b/tests/printing/posttyper/i22533.scala @@ -0,0 +1,7 @@ +//> using options -Ycompile-scala2-library + +trait A extends Any: + override def equals(x: Any): Boolean = ??? + override def hashCode(): Int = ??? + +class Foo(u: Int) extends AnyVal, A \ No newline at end of file diff --git a/tests/rewrites/ambigious-named-tuple-assignment.check b/tests/rewrites/ambiguous-named-tuple-assignment.check similarity index 100% rename from tests/rewrites/ambigious-named-tuple-assignment.check rename to tests/rewrites/ambiguous-named-tuple-assignment.check diff --git a/tests/rewrites/ambigious-named-tuple-assignment.scala b/tests/rewrites/ambiguous-named-tuple-assignment.scala similarity index 100% rename from tests/rewrites/ambigious-named-tuple-assignment.scala rename to tests/rewrites/ambiguous-named-tuple-assignment.scala diff --git a/tests/rewrites/i22440.check b/tests/rewrites/i22440.check new file mode 100644 index 000000000000..417fd442f9f2 --- /dev/null +++ b/tests/rewrites/i22440.check @@ -0,0 +1,5 @@ +//> using options -source 3.7-migration + +def foo(implicit x: Int) = () +val _ = foo(using 1) +val _ = foo (using 1) diff --git a/tests/rewrites/i22440.scala b/tests/rewrites/i22440.scala new file mode 100644 index 000000000000..7bbe8e44b5f7 --- /dev/null +++ b/tests/rewrites/i22440.scala @@ -0,0 +1,5 @@ +//> using options -source 3.7-migration + +def foo(implicit x: Int) = () +val _ = foo(1) +val _ = foo (1) \ No newline at end of file diff --git a/tests/rewrites/unused.check b/tests/rewrites/unused.check new file mode 100644 index 000000000000..1ff93bfb6ef2 --- /dev/null +++ b/tests/rewrites/unused.check @@ -0,0 +1,55 @@ + +//> using options -Wunused:all + +package p1: + import java.lang.Runnable + class C extends Runnable { def run() = () } + +package p2: + import java.lang.Runnable + class C extends Runnable { def run() = () } + +package p3: + import java.lang.Runnable + class C extends Runnable { def run() = () } + +package p4: + import java.lang.{Runnable, System}, System.out + class C extends Runnable { def run() = out.println() } + +package p5: + import java.lang.{Runnable, System}, System.out + class C extends Runnable { def run() = out.println() } + +package p6: + import java.lang.{Runnable, System}, System.out + class C extends Runnable { def run() = out.println() } + +package p7: + import java.lang.{Runnable, + System}, System.out + class C extends Runnable { def run() = out.println() } + +package p8: + import java.lang.{Runnable as R, System}, System.out + class C extends R { def run() = out.println() } + +package p9: + import java.lang.{Runnable as R, System, + Thread} + class C extends R { def run() = Thread(() => System.out.println()).start() } + +package p10: + object X: + def x = 42 + class C: + import X.* // preserve text, don't rewrite to p10.X.* + def c = x + +package p11: + import collection.mutable, mutable.ListBuffer // warn // warn // not checked :( + def buf = ListBuffer.empty[String] + +package p12: + import java.lang.System, java.lang.Runnable + class C extends Runnable { def run() = System.out.println() } diff --git a/tests/rewrites/unused.scala b/tests/rewrites/unused.scala new file mode 100644 index 000000000000..85a83c7c0015 --- /dev/null +++ b/tests/rewrites/unused.scala @@ -0,0 +1,61 @@ + +//> using options -Wunused:all + +package p1: + import java.lang.Runnable + import java.lang.{Thread, String}, java.lang.Integer + class C extends Runnable { def run() = () } + +package p2: + import java.lang.Thread + import java.lang.String + import java.lang.Runnable + import java.lang.Integer + class C extends Runnable { def run() = () } + +package p3: + import java.lang.{Runnable, Thread, String} + class C extends Runnable { def run() = () } + +package p4: + import java.lang.{Runnable, System, Thread}, System.out + class C extends Runnable { def run() = out.println() } + +package p5: + import java.lang.{Thread, Runnable, System}, System.out + class C extends Runnable { def run() = out.println() } + +package p6: + import java.lang.{Runnable, System}, java.lang.Thread, System.out + class C extends Runnable { def run() = out.println() } + +package p7: + import java.lang.{Runnable, + Thread, + System}, System.out + class C extends Runnable { def run() = out.println() } + +package p8: + import java.lang.{Runnable as R, System, + Thread}, System.out + class C extends R { def run() = out.println() } + +package p9: + import java.lang.{Runnable as R, System, + Thread}, System.out + class C extends R { def run() = Thread(() => System.out.println()).start() } + +package p10: + object X: + def x = 42 + class C: + import X.*, java.util.HashMap // preserve text, don't rewrite to p10.X.* + def c = x + +package p11: + import collection.mutable, mutable.ListBuffer, java.lang.{Runnable, Thread} // warn // warn // not checked :( + def buf = ListBuffer.empty[String] + +package p12: + import collection.mutable, java.lang.System, java.lang.Runnable + class C extends Runnable { def run() = System.out.println() } diff --git a/tests/run-macros/i21225.check b/tests/run-macros/i21225.check new file mode 100644 index 000000000000..51779b7b89af --- /dev/null +++ b/tests/run-macros/i21225.check @@ -0,0 +1,2 @@ +bar codec +foo codec diff --git a/tests/run-macros/i21225/Macro_1.scala b/tests/run-macros/i21225/Macro_1.scala new file mode 100644 index 000000000000..2b7bfd3b969f --- /dev/null +++ b/tests/run-macros/i21225/Macro_1.scala @@ -0,0 +1,33 @@ +//> using options -experimental + +import scala.quoted.* + +trait Codec[-T] { def print(): Unit } +object Codec { + inline def derivedWithDeps[T](deps: Any): Codec[T] = ${derivedWithDepsImpl[T]('deps)} + + private def derivedWithDepsImpl[T](deps: Expr[Any])(using q: Quotes)(using Type[T]): Expr[Codec[T]] = { + import q.reflect.* + + val givenSelector: Selector = GivenSelector(None) + val theImport = Import(deps.asTerm, List(givenSelector)) + Block(List(theImport), '{scala.compiletime.summonInline[Codec[T]]}.asTerm).asExprOf[Codec[T]] + /* import deps.given + * summonInline[Codec[T]] + */ + } + + inline def derivedWithDepsWithNamedOmitted[T](deps: Any): Codec[T] = ${derivedWithDepsWithNamedOmittedImpl[T]('deps)} + + private def derivedWithDepsWithNamedOmittedImpl[T](deps: Expr[Any])(using q: Quotes)(using Type[T]): Expr[Codec[T]] = { + import q.reflect.* + + val givenSelector: Selector = GivenSelector(None) + val omitSelector: Selector = OmitSelector("named") + val theImport = Import(deps.asTerm, List(givenSelector, omitSelector)) + Block(List(theImport), '{scala.compiletime.summonInline[Codec[T]]}.asTerm).asExprOf[Codec[T]] + /* import deps.{given, named => _} + * summonInline[Codec[T]] + */ + } +} diff --git a/tests/run-macros/i21225/Test_2.scala b/tests/run-macros/i21225/Test_2.scala new file mode 100644 index 000000000000..bd9081588754 --- /dev/null +++ b/tests/run-macros/i21225/Test_2.scala @@ -0,0 +1,14 @@ +//> using options -experimental + +import scala.quoted.* + +sealed trait Foo +class Bar extends Foo +object CustomCodecs { + given named: Codec[Bar] = new Codec[Bar] { def print(): Unit = println("bar codec")} + given Codec[Foo] = new Codec[Foo] { def print(): Unit = println("foo codec") } +} + +@main def Test = + Codec.derivedWithDeps[Bar](CustomCodecs).print() + Codec.derivedWithDepsWithNamedOmitted[Bar](CustomCodecs).print() diff --git a/tests/run-macros/summonIgnoring-nonrecursive.check b/tests/run-macros/summonIgnoring-nonrecursive.check new file mode 100644 index 000000000000..643b49550f62 --- /dev/null +++ b/tests/run-macros/summonIgnoring-nonrecursive.check @@ -0,0 +1,3 @@ +TC[C2] generated in macro using: +TC2[_] generated in macro using: +TC[C1] generated in macro diff --git a/tests/run-macros/summonIgnoring-nonrecursive/Macro_1.scala b/tests/run-macros/summonIgnoring-nonrecursive/Macro_1.scala new file mode 100644 index 000000000000..b34a9a878483 --- /dev/null +++ b/tests/run-macros/summonIgnoring-nonrecursive/Macro_1.scala @@ -0,0 +1,50 @@ +//> using options -experimental +import scala.quoted._ +class C1 +trait TC[T] { + def print(): Unit +} + +object TC { + implicit transparent inline def auto[T]: TC[T] = ${autoImpl[T]} + def autoImpl[T: Type](using Quotes): Expr[TC[T]] = + import quotes.reflect._ + if (TypeRepr.of[T].typeSymbol == Symbol.classSymbol("C1")){ + '{ + new TC[T] { + def print() = { + println("TC[C1] generated in macro") + } + } + } + } else { + Expr.summonIgnoring[TC2[C1]](Symbol.classSymbol("TC").companionModule.methodMember("auto")*) match + case Some(a) => + '{ + new TC[T] { + def print(): Unit = + println(s"TC[${${Expr(TypeRepr.of[T].show)}}] generated in macro using:") + $a.print() + } + } + case None => + '{ + new TC[T]{ + def print(): Unit = + println(s"TC[${${Expr(TypeRepr.of[T].show)}}] generated in macro without TC2[_]") + } + } + } +} + +trait TC2[T] { + def print(): Unit +} + +object TC2 { + implicit def auto2[T](using tc: TC[T]): TC2[T] = new TC2[T] { + def print(): Unit = + println(s"TC2[_] generated in macro using:") + tc.print() + } +} diff --git a/tests/run-macros/summonIgnoring-nonrecursive/Test_2.scala b/tests/run-macros/summonIgnoring-nonrecursive/Test_2.scala new file mode 100644 index 000000000000..abd947d3e7fb --- /dev/null +++ b/tests/run-macros/summonIgnoring-nonrecursive/Test_2.scala @@ -0,0 +1,6 @@ +//> using options -experimental + +@main def Test(): Unit = { + class C2 + summon[TC[C2]].print() +} diff --git a/tests/run-macros/summonIgnoring.check b/tests/run-macros/summonIgnoring.check new file mode 100644 index 000000000000..5369c42c4888 --- /dev/null +++ b/tests/run-macros/summonIgnoring.check @@ -0,0 +1,5 @@ +No given in scope: +TC[C2] generated in macro without TC[C1] +Given in scope: +TC[C2] generated in macro using: +TC[C1] defined by a user diff --git a/tests/run-macros/summonIgnoring/Macro_1.scala b/tests/run-macros/summonIgnoring/Macro_1.scala new file mode 100644 index 000000000000..e4771588ce4e --- /dev/null +++ b/tests/run-macros/summonIgnoring/Macro_1.scala @@ -0,0 +1,38 @@ +//> using options -experimental +import scala.quoted._ +class C1 +trait TC[T] { + def print(): Unit +} +object TC { + implicit transparent inline def auto[T]: TC[T] = ${autoImpl[T]} + def autoImpl[T: Type](using Quotes): Expr[TC[T]] = + import quotes.reflect._ + if(TypeRepr.of[T].typeSymbol == Symbol.classSymbol("C1")){ + '{ + new TC[T] { + def print() = { + println("TC[C1] generated in macro") + } + } + } + } else { + Expr.summonIgnoring[TC[C1]](Symbol.classSymbol("TC").companionModule.methodMember("auto")*) match + case Some(a) => + '{ + new TC[T] { + def print(): Unit = + println(s"TC[${${Expr(TypeRepr.of[T].show)}}] generated in macro using:") + $a.print() + } + } + case None => + '{ + new TC[T]{ + def print(): Unit = + println(s"TC[${${Expr(TypeRepr.of[T].show)}}] generated in macro without TC[C1]") + } + } + } + +} diff --git a/tests/run-macros/summonIgnoring/Test_2.scala b/tests/run-macros/summonIgnoring/Test_2.scala new file mode 100644 index 000000000000..ca9007f269e2 --- /dev/null +++ b/tests/run-macros/summonIgnoring/Test_2.scala @@ -0,0 +1,15 @@ +//> using options -experimental + +@main def Test(): Unit = { + class C2 + println("No given in scope:") + summon[TC[C2]].print() + + { + println("Given in scope:") + given TC[C1] = new TC[C1] { + def print() = println("TC[C1] defined by a user") + } + summon[TC[C2]].print() + } +} diff --git a/tests/run-macros/tasty-extractors-2.check b/tests/run-macros/tasty-extractors-2.check index 5dd6af8d8b04..15d844670b7a 100644 --- a/tests/run-macros/tasty-extractors-2.check +++ b/tests/run-macros/tasty-extractors-2.check @@ -49,7 +49,7 @@ TypeRef(ThisType(TypeRef(NoPrefix(), "scala")), "Unit") Inlined(None, Nil, Block(List(ClassDef("Foo", DefDef("", List(TermParamClause(Nil)), Inferred(), None), List(Apply(Select(New(Inferred()), ""), Nil)), None, List(DefDef("a", Nil, Inferred(), Some(Literal(IntConstant(0))))))), Literal(UnitConstant()))) TypeRef(ThisType(TypeRef(NoPrefix(), "scala")), "Unit") -Inlined(None, Nil, Block(List(ClassDef("Foo", DefDef("", List(TermParamClause(Nil)), Inferred(), None), List(Apply(Select(New(Inferred()), ""), Nil), TypeSelect(Select(Ident("_root_"), "scala"), "Product"), TypeSelect(Select(Ident("_root_"), "scala"), "Serializable")), None, List(DefDef("hashCode", List(TermParamClause(Nil)), Inferred(), Some(Apply(Ident("_hashCode"), List(This(Some("Foo")))))), DefDef("equals", List(TermParamClause(List(ValDef("x$0", Inferred(), None)))), Inferred(), Some(Apply(Select(Apply(Select(This(Some("Foo")), "eq"), List(TypeApply(Select(Ident("x$0"), "$asInstanceOf$"), List(Inferred())))), "||"), List(Match(Ident("x$0"), List(CaseDef(Bind("x$0", Typed(Wildcard(), Inferred())), None, Apply(Select(Literal(BooleanConstant(true)), "&&"), List(Apply(Select(Ident("x$0"), "canEqual"), List(This(Some("Foo"))))))), CaseDef(Wildcard(), None, Literal(BooleanConstant(false))))))))), DefDef("toString", List(TermParamClause(Nil)), Inferred(), Some(Apply(Ident("_toString"), List(This(Some("Foo")))))), DefDef("canEqual", List(TermParamClause(List(ValDef("that", Inferred(), None)))), Inferred(), Some(TypeApply(Select(Ident("that"), "isInstanceOf"), List(Inferred())))), DefDef("productArity", Nil, Inferred(), Some(Literal(IntConstant(0)))), DefDef("productPrefix", Nil, Inferred(), Some(Literal(StringConstant("Foo")))), DefDef("productElement", List(TermParamClause(List(ValDef("n", Inferred(), None)))), Inferred(), Some(Match(Ident("n"), List(CaseDef(Wildcard(), None, Apply(Ident("throw"), List(Apply(Select(New(Inferred()), ""), List(Apply(Select(Ident("n"), "toString"), Nil)))))))))), DefDef("productElementName", List(TermParamClause(List(ValDef("n", Inferred(), None)))), Inferred(), Some(Match(Ident("n"), List(CaseDef(Wildcard(), None, Apply(Ident("throw"), List(Apply(Select(New(Inferred()), ""), List(Apply(Select(Ident("n"), "toString"), Nil)))))))))), DefDef("copy", List(TermParamClause(Nil)), Inferred(), Some(Apply(Select(New(Inferred()), ""), Nil))))), ValDef("Foo", TypeIdent("Foo$"), Some(Apply(Select(New(TypeIdent("Foo$")), ""), Nil))), ClassDef("Foo$", DefDef("", List(TermParamClause(Nil)), Inferred(), None), List(Apply(Select(New(Inferred()), ""), Nil), Inferred()), Some(ValDef("_", Singleton(Ident("Foo")), None)), List(DefDef("apply", List(TermParamClause(Nil)), Inferred(), Some(Apply(Select(New(Inferred()), ""), Nil))), DefDef("unapply", List(TermParamClause(List(ValDef("x$1", Inferred(), None)))), Singleton(Literal(BooleanConstant(true))), Some(Literal(BooleanConstant(true)))), DefDef("toString", Nil, Inferred(), Some(Literal(StringConstant("Foo")))), TypeDef("MirroredMonoType", TypeBoundsTree(Inferred(), Inferred())), DefDef("fromProduct", List(TermParamClause(List(ValDef("x$0", Inferred(), None)))), Inferred(), Some(Apply(Select(New(Inferred()), ""), Nil)))))), Literal(UnitConstant()))) +Inlined(None, Nil, Block(List(ClassDef("Foo", DefDef("", List(TermParamClause(Nil)), Inferred(), None), List(Apply(Select(New(Inferred()), ""), Nil), TypeSelect(Select(Ident("_root_"), "scala"), "Product"), TypeSelect(Select(Ident("_root_"), "scala"), "Serializable")), None, List(DefDef("hashCode", List(TermParamClause(Nil)), Inferred(), Some(Apply(Ident("_hashCode"), List(This(Some("Foo")))))), DefDef("equals", List(TermParamClause(List(ValDef("x$0", Inferred(), None)))), Inferred(), Some(Apply(Select(Apply(Select(This(Some("Foo")), "eq"), List(TypeApply(Select(Ident("x$0"), "$asInstanceOf$"), List(Inferred())))), "||"), List(Match(Ident("x$0"), List(CaseDef(Bind("x$0", Typed(Wildcard(), Inferred())), None, Apply(Select(Literal(BooleanConstant(true)), "&&"), List(Apply(Select(Ident("x$0"), "canEqual"), List(This(Some("Foo"))))))), CaseDef(Wildcard(), None, Literal(BooleanConstant(false))))))))), DefDef("toString", List(TermParamClause(Nil)), Inferred(), Some(Apply(Ident("_toString"), List(This(Some("Foo")))))), DefDef("canEqual", List(TermParamClause(List(ValDef("that", Inferred(), None)))), Inferred(), Some(TypeApply(Select(Ident("that"), "isInstanceOf"), List(Inferred())))), DefDef("productArity", Nil, Inferred(), Some(Literal(IntConstant(0)))), DefDef("productPrefix", Nil, Inferred(), Some(Literal(StringConstant("Foo")))), DefDef("productElement", List(TermParamClause(List(ValDef("n", Inferred(), None)))), Inferred(), Some(Match(Ident("n"), List(CaseDef(Wildcard(), None, Apply(Ident("throw"), List(Apply(Select(New(Inferred()), ""), List(Apply(Select(Ident("n"), "toString"), Nil)))))))))), DefDef("productElementName", List(TermParamClause(List(ValDef("n", Inferred(), None)))), Inferred(), Some(Match(Ident("n"), List(CaseDef(Wildcard(), None, Apply(Ident("throw"), List(Apply(Select(New(Inferred()), ""), List(Apply(Select(Ident("n"), "toString"), Nil)))))))))), DefDef("copy", List(TermParamClause(Nil)), Inferred(), Some(Apply(Select(New(Inferred()), ""), Nil))))), ValDef("Foo", TypeIdent("Foo$"), Some(Apply(Select(New(TypeIdent("Foo$")), ""), Nil))), ClassDef("Foo$", DefDef("", List(TermParamClause(Nil)), Inferred(), None), List(Apply(Select(New(Inferred()), ""), Nil), Inferred()), Some(ValDef("_", Singleton(Ident("Foo")), None)), List(DefDef("apply", List(TermParamClause(Nil)), Inferred(), Some(Apply(Select(New(Inferred()), ""), Nil))), DefDef("unapply", List(TermParamClause(List(ValDef("x$1", Inferred(), None)))), Singleton(Literal(BooleanConstant(true))), Some(Literal(BooleanConstant(true)))), DefDef("toString", Nil, Inferred(), Some(Literal(StringConstant("Foo")))), TypeDef("MirroredMonoType", TypeBoundsTree(Inferred(), Inferred())), DefDef("fromProduct", List(TermParamClause(List(ValDef("x$0", Inferred(), None)))), Inferred(), Some(Block(Nil, Apply(Select(New(Inferred()), ""), Nil))))))), Literal(UnitConstant()))) TypeRef(ThisType(TypeRef(NoPrefix(), "scala")), "Unit") Inlined(None, Nil, Block(List(ClassDef("Foo1", DefDef("", List(TermParamClause(List(ValDef("a", TypeIdent("Int"), None)))), Inferred(), None), List(Apply(Select(New(Inferred()), ""), Nil)), None, List(ValDef("a", Inferred(), None)))), Literal(UnitConstant()))) diff --git a/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala b/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala index 2c4ee5401f92..bda454c0be2b 100644 --- a/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala +++ b/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala @@ -39,9 +39,6 @@ val experimentalDefinitionInLibrary = Set( "scala.annotation.into", "scala.annotation.internal.$into", - //// New feature: @publicInBinary - "scala.annotation.publicInBinary", - //// New feature: Macro annotations "scala.annotation.MacroAnnotation", @@ -73,6 +70,10 @@ val experimentalDefinitionInLibrary = Set( "scala.quoted.Quotes.reflectModule.MethodTypeMethods.hasErasedParams", "scala.quoted.Quotes.reflectModule.TermParamClauseMethods.erasedArgs", "scala.quoted.Quotes.reflectModule.TermParamClauseMethods.hasErasedArgs", + "scala.quoted.Quotes.reflectModule.GivenSelectorModule.apply", + "scala.quoted.Quotes.reflectModule.OmitSelectorModule.apply", + "scala.quoted.Quotes.reflectModule.RenameSelectorModule.apply", + "scala.quoted.Quotes.reflectModule.SimpleSelectorModule.apply", // New feature: fromNullable for explicit nulls "scala.Predef$.fromNullable", diff --git a/tests/run/better-fors-map-elim.check b/tests/run/better-fors-map-elim.check new file mode 100644 index 000000000000..0ef3447a47c4 --- /dev/null +++ b/tests/run/better-fors-map-elim.check @@ -0,0 +1,4 @@ +MySome(()) +MySome(2) +MySome((2,3)) +MySome((2,(3,4))) diff --git a/tests/run/better-fors-map-elim.scala b/tests/run/better-fors-map-elim.scala new file mode 100644 index 000000000000..653984bc8e28 --- /dev/null +++ b/tests/run/better-fors-map-elim.scala @@ -0,0 +1,64 @@ +import scala.language.experimental.betterFors + +class myOptionModule(doOnMap: => Unit) { + sealed trait MyOption[+A] { + def map[B](f: A => B): MyOption[B] = this match { + case MySome(x) => { + doOnMap + MySome(f(x)) + } + case MyNone => MyNone + } + def flatMap[B](f: A => MyOption[B]): MyOption[B] = this match { + case MySome(x) => f(x) + case MyNone => MyNone + } + } + case class MySome[A](x: A) extends MyOption[A] + case object MyNone extends MyOption[Nothing] + object MyOption { + def apply[A](x: A): MyOption[A] = MySome(x) + } +} + +object Test extends App { + + val myOption = new myOptionModule(println("map called")) + + import myOption.* + + def portablePrintMyOption(opt: MyOption[Any]): Unit = + if opt == MySome(()) then + println("MySome(())") + else + println(opt) + + val z = for { + a <- MyOption(1) + b <- MyOption(()) + } yield () + + portablePrintMyOption(z) + + val z2 = for { + a <- MyOption(1) + b <- MyOption(2) + } yield b + + portablePrintMyOption(z2) + + val z3 = for { + a <- MyOption(1) + (b, c) <- MyOption((2, 3)) + } yield (b, c) + + portablePrintMyOption(z3) + + val z4 = for { + a <- MyOption(1) + (b, (c, d)) <- MyOption((2, (3, 4))) + } yield (b, (c, d)) + + portablePrintMyOption(z4) + +} diff --git a/tests/run/i13215.scala b/tests/run/i13215.scala index f43e9aa1e38a..738eb25d598a 100644 --- a/tests/run/i13215.scala +++ b/tests/run/i13215.scala @@ -1,4 +1,4 @@ -//> using options -experimental -Werror -WunstableInlineAccessors +//> using options -Werror -WunstableInlineAccessors import scala.annotation.publicInBinary diff --git a/tests/run/i22497.check b/tests/run/i22497.check new file mode 100644 index 000000000000..7ef6ca89b9f4 --- /dev/null +++ b/tests/run/i22497.check @@ -0,0 +1,3 @@ +public Foo() +public Foo(int) +public Foo(java.lang.String) diff --git a/tests/run/i22497.scala b/tests/run/i22497.scala new file mode 100644 index 000000000000..723668db9750 --- /dev/null +++ b/tests/run/i22497.scala @@ -0,0 +1,11 @@ +// scalajs: --skip + +import scala.annotation.publicInBinary +import scala.annotation.experimental + +class Foo: + @publicInBinary private def this(i: Int) = this() + @publicInBinary protected def this(i: String) = this() + +@main def Test = + println(classOf[Foo].getConstructors().mkString("\n")) diff --git a/tests/run/i22498.scala b/tests/run/i22498.scala new file mode 100644 index 000000000000..839a73ecff88 --- /dev/null +++ b/tests/run/i22498.scala @@ -0,0 +1,8 @@ +import scala.annotation.publicInBinary + +class Foo: + @publicInBinary private def this(x: Int) = this() + inline def proxy: Foo = new Foo(0) + +@main def Test = + val x = (new Foo).proxy diff --git a/tests/run/i8073.scala b/tests/run/i8073.scala new file mode 100644 index 000000000000..6b5bfc3b9832 --- /dev/null +++ b/tests/run/i8073.scala @@ -0,0 +1,86 @@ +import scala.deriving.Mirror + +trait A: + type B + +object Test: + case class CC(a: A, b: a.B) + + def test1(): Unit = + val generic = summon[Mirror.Of[CC]] + // No language syntax for type projection of a singleton type + // summon[generic.MirroredElemTypes =:= (A, CC#a.B)] + + val aa: A { type B = Int } = new A { type B = Int } + val x: CC { val a: aa.type } = CC(aa, 1).asInstanceOf[CC { val a: aa.type }] // manual `tracked` + + val dependent = summon[Mirror.Of[x.type]] + summon[dependent.MirroredElemTypes =:= (A, x.a.B)] + + assert(CC(aa, 1) == generic.fromProduct((aa, 1))) + assert(CC(aa, 1) == dependent.fromProduct((aa, 1))) + + x match + case CC(a, b) => + val a1: A = a + // Dependent pattern matching is not currently supported + // val b1: a1.B = b + val b1 = b // Type is CC#a.B + + end test1 + + case class CCPoly[T <: A](a: T, b: a.B) + + def test2(): Unit = + val generic = summon[Mirror.Of[CCPoly[A]]] + // No language syntax for type projection of a singleton type + // summon[generic.MirroredElemTypes =:= (A, CCPoly[A]#a.B)] + + val aa: A { type B = Int } = new A { type B = Int } + val x: CCPoly[aa.type] = CCPoly(aa, 1) + + val dependent = summon[Mirror.Of[x.type]] + summon[dependent.MirroredElemTypes =:= (aa.type, x.a.B)] + + assert(CCPoly[A](aa, 1) == generic.fromProduct((aa, 1))) + assert(CCPoly[A](aa, 1) == dependent.fromProduct((aa, 1))) + + x match + case CCPoly(a, b) => + val a1: A = a + // Dependent pattern matching is not currently supported + // val b1: a1.B = b + val b1 = b // Type is CC#a.B + + end test2 + + enum Enum: + case EC(a: A, b: a.B) + + def test3(): Unit = + val generic = summon[Mirror.Of[Enum.EC]] + // No language syntax for type projection of a singleton type + // summon[generic.MirroredElemTypes =:= (A, Enum.EC#a.B)] + + val aa: A { type B = Int } = new A { type B = Int } + val x: Enum.EC { val a: aa.type } = Enum.EC(aa, 1).asInstanceOf[Enum.EC { val a: aa.type }] // manual `tracked` + + val dependent = summon[Mirror.Of[x.type]] + summon[dependent.MirroredElemTypes =:= (A, x.a.B)] + + assert(Enum.EC(aa, 1) == generic.fromProduct((aa, 1))) + assert(Enum.EC(aa, 1) == dependent.fromProduct((aa, 1))) + + x match + case Enum.EC(a, b) => + val a1: A = a + // Dependent pattern matching is not currently supported + // val b1: a1.B = b + val b1 = b // Type is Enum.EC#a.B + + end test3 + + def main(args: Array[String]): Unit = + test1() + test2() + test3() diff --git a/tests/run/i8073b.scala b/tests/run/i8073b.scala new file mode 100644 index 000000000000..cc85731d01df --- /dev/null +++ b/tests/run/i8073b.scala @@ -0,0 +1,86 @@ +import scala.deriving.Mirror + +trait A: + type B + +// Test local mirrors +@main def Test = + case class CC(a: A, b: a.B) + + def test1(): Unit = + val generic = summon[Mirror.Of[CC]] + // No language syntax for type projection of a singleton type + // summon[generic.MirroredElemTypes =:= (A, CC#a.B)] + + val aa: A { type B = Int } = new A { type B = Int } + val x: CC { val a: aa.type } = CC(aa, 1).asInstanceOf[CC { val a: aa.type }] // manual `tracked` + + val dependent = summon[Mirror.Of[x.type]] + summon[dependent.MirroredElemTypes =:= (A, x.a.B)] + + assert(CC(aa, 1) == generic.fromProduct((aa, 1))) + assert(CC(aa, 1) == dependent.fromProduct((aa, 1))) + + x match + case CC(a, b) => + val a1: A = a + // Dependent pattern matching is not currently supported + // val b1: a1.B = b + val b1 = b // Type is CC#a.B + + end test1 + + case class CCPoly[T <: A](a: T, b: a.B) + + def test2(): Unit = + val generic = summon[Mirror.Of[CCPoly[A]]] + // No language syntax for type projection of a singleton type + // summon[generic.MirroredElemTypes =:= (A, CCPoly[A]#a.B)] + + val aa: A { type B = Int } = new A { type B = Int } + val x: CCPoly[aa.type] = CCPoly(aa, 1) + + val dependent = summon[Mirror.Of[x.type]] + summon[dependent.MirroredElemTypes =:= (aa.type, x.a.B)] + + assert(CCPoly[A](aa, 1) == generic.fromProduct((aa, 1))) + assert(CCPoly[A](aa, 1) == dependent.fromProduct((aa, 1))) + + x match + case CCPoly(a, b) => + val a1: A = a + // Dependent pattern matching is not currently supported + // val b1: a1.B = b + val b1 = b // Type is CC#a.B + + end test2 + + enum Enum: + case EC(a: A, b: a.B) + + def test3(): Unit = + val generic = summon[Mirror.Of[Enum.EC]] + // No language syntax for type projection of a singleton type + // summon[generic.MirroredElemTypes =:= (A, Enum.EC#a.B)] + + val aa: A { type B = Int } = new A { type B = Int } + val x: Enum.EC { val a: aa.type } = Enum.EC(aa, 1).asInstanceOf[Enum.EC { val a: aa.type }] // manual `tracked` + + val dependent = summon[Mirror.Of[x.type]] + summon[dependent.MirroredElemTypes =:= (A, x.a.B)] + + assert(Enum.EC(aa, 1) == generic.fromProduct((aa, 1))) + assert(Enum.EC(aa, 1) == dependent.fromProduct((aa, 1))) + + x match + case Enum.EC(a, b) => + val a1: A = a + // Dependent pattern matching is not currently supported + // val b1: a1.B = b + val b1 = b // Type is Enum.EC#a.B + + end test3 + + test1() + test2() + test3() diff --git a/tests/run/named-tuples-mirror.check b/tests/run/named-tuples-mirror.check new file mode 100644 index 000000000000..e6656d280fe6 --- /dev/null +++ b/tests/run/named-tuples-mirror.check @@ -0,0 +1,4 @@ +NamedTuple +List(foo: Int, bla: String) +15 +test diff --git a/tests/run/named-tuples-mirror.scala b/tests/run/named-tuples-mirror.scala new file mode 100644 index 000000000000..5dfdb6ef3104 --- /dev/null +++ b/tests/run/named-tuples-mirror.scala @@ -0,0 +1,29 @@ +import scala.language.experimental.namedTuples +import scala.deriving.* +import scala.compiletime.* + +type ToString[T] = T match + case Int => "Int" + case String => "String" + +inline def showLabelsAndTypes[Types <: Tuple, Labels <: Tuple]: List[String] = + inline erasedValue[Types] match { + case _: (tpe *: types) => + inline erasedValue[Labels] match { + case _: (label *: labels) => + val labelStr = constValue[label] + val tpeStr = constValue[ToString[tpe]] + s"$labelStr: $tpeStr" :: showLabelsAndTypes[types, labels] + } + case _: EmptyTuple => + Nil +} + +@main def Test = + val mirror = summon[Mirror.Of[(foo: Int, bla: String)]] + println(constValue[mirror.MirroredLabel]) + println(showLabelsAndTypes[mirror.MirroredElemTypes, mirror.MirroredElemLabels]) + + val namedTuple = summon[Mirror.Of[(foo: Int, bla: String)]].fromProduct((15, "test")) + println(namedTuple.foo) + println(namedTuple.bla) diff --git a/tests/run/noProtectedSuper.scala b/tests/run/noProtectedSuper.scala index d05c13d90c9f..41b0615d12ab 100644 --- a/tests/run/noProtectedSuper.scala +++ b/tests/run/noProtectedSuper.scala @@ -1,5 +1,3 @@ -//> using options -experimental - import scala.annotation.publicInBinary package p { diff --git a/tests/run/pkgobjvals.check b/tests/run/pkgobjvals.check new file mode 100644 index 000000000000..3e327fcc0c3e --- /dev/null +++ b/tests/run/pkgobjvals.check @@ -0,0 +1,4 @@ +Foo was created +Foo was created +Foo was created +Foo was created diff --git a/tests/run/pkgobjvals.scala b/tests/run/pkgobjvals.scala new file mode 100644 index 000000000000..8df1a984642c --- /dev/null +++ b/tests/run/pkgobjvals.scala @@ -0,0 +1,22 @@ +import language.experimental.packageObjectValues + +package a: + package object b: + class Foo: + println("Foo was created") + + def foo() = Foo() + end b + + def test = + val bb = b + bb.foo() + new bb.Foo() +end a + +@main def Test = + a.test + val ab: a.b.type = a.b + ab.foo() + new ab.Foo() + diff --git a/tests/run/publicInBinary/Lib_1.scala b/tests/run/publicInBinary/Lib_1.scala index e7b5a0780d1c..b6db126b7d82 100644 --- a/tests/run/publicInBinary/Lib_1.scala +++ b/tests/run/publicInBinary/Lib_1.scala @@ -1,4 +1,4 @@ -//> using options -experimental -Werror -WunstableInlineAccessors +//> using options -Werror -WunstableInlineAccessors package foo diff --git a/tests/semanticdb/expect/JavaStaticVar.expect.scala b/tests/semanticdb/expect/JavaStaticVar.expect.scala new file mode 100644 index 000000000000..171596bc9519 --- /dev/null +++ b/tests/semanticdb/expect/JavaStaticVar.expect.scala @@ -0,0 +1,7 @@ +package example + +import com.javacp.JavaStaticVar/*->com::javacp::JavaStaticVar#*/ + +class ScalaFoo/*<-example::ScalaFoo#*/ { + val javaStaticVarFoo/*<-example::ScalaFoo#javaStaticVarFoo.*/ = JavaStaticVar/*->com::javacp::JavaStaticVar#*/.foo/*->com::javacp::JavaStaticVar#foo.*/ +} diff --git a/tests/semanticdb/expect/JavaStaticVar.scala b/tests/semanticdb/expect/JavaStaticVar.scala new file mode 100644 index 000000000000..7a7efeaa670a --- /dev/null +++ b/tests/semanticdb/expect/JavaStaticVar.scala @@ -0,0 +1,7 @@ +package example + +import com.javacp.JavaStaticVar + +class ScalaFoo { + val javaStaticVarFoo = JavaStaticVar.foo +} diff --git a/tests/semanticdb/javacp/com/javacp/JavaStaticVar.java b/tests/semanticdb/javacp/com/javacp/JavaStaticVar.java new file mode 100644 index 000000000000..1124c8768421 --- /dev/null +++ b/tests/semanticdb/javacp/com/javacp/JavaStaticVar.java @@ -0,0 +1,5 @@ +package com.javacp; + +public class JavaStaticVar { + public static int foo = 0; +} diff --git a/tests/semanticdb/metac.expect b/tests/semanticdb/metac.expect index b038d4a4d388..f674c6fb4159 100644 --- a/tests/semanticdb/metac.expect +++ b/tests/semanticdb/metac.expect @@ -8,7 +8,7 @@ Text => empty Language => Scala Symbols => 9 entries Occurrences => 19 entries -Diagnostics => 4 entries +Diagnostics => 2 entries Symbols: example/Access# => class Access extends Object { self: Access => +8 decls } @@ -43,12 +43,10 @@ Occurrences: [9:11..9:14): ??? -> scala/Predef.`???`(). Diagnostics: -[3:14..3:16): [warning] unused private member [4:16..4:16): [warning] Ignoring [this] qualifier. This syntax will be deprecated in the future; it should be dropped. See: https://docs.scala-lang.org/scala3/reference/dropped-features/this-qualifier.html This construct can be rewritten automatically under -rewrite -source 3.4-migration. -[4:20..4:22): [warning] unused private member [7:18..7:18): [warning] Ignoring [this] qualifier. This syntax will be deprecated in the future; it should be dropped. See: https://docs.scala-lang.org/scala3/reference/dropped-features/this-qualifier.html @@ -575,7 +573,7 @@ Text => empty Language => Scala Symbols => 108 entries Occurrences => 127 entries -Diagnostics => 6 entries +Diagnostics => 11 entries Synthetics => 2 entries Symbols: @@ -818,6 +816,7 @@ Occurrences: [53:10..53:11): + -> scala/Int#`+`(+4). Diagnostics: +[13:20..13:21): [warning] unused explicit parameter [18:9..18:10): [warning] unused explicit parameter [20:23..20:23): [warning] Ignoring [this] qualifier. This syntax will be deprecated in the future; it should be dropped. @@ -830,6 +829,10 @@ See: https://docs.scala-lang.org/scala3/reference/dropped-features/this-qualifie This construct can be rewritten automatically under -rewrite -source 3.4-migration. [22:27..22:28): [warning] unused explicit parameter [24:10..24:11): [warning] unused explicit parameter +[36:11..36:12): [warning] unused explicit parameter +[39:11..39:12): [warning] unused explicit parameter +[39:19..39:20): [warning] unused explicit parameter +[51:30..51:31): [warning] unused explicit parameter Synthetics: [51:16..51:27):List(1).map => *[Int] @@ -2095,6 +2098,7 @@ Text => empty Language => Scala Symbols => 45 entries Occurrences => 66 entries +Diagnostics => 1 entries Synthetics => 3 entries Symbols: @@ -2212,6 +2216,9 @@ Occurrences: [41:8..41:17): given_Z_T -> givens/InventedNames$package.given_Z_T(). [41:18..41:24): String -> scala/Predef.String# +Diagnostics: +[24:13..24:13): [warning] unused implicit parameter + Synthetics: [24:0..24:0): => *(x$1) [34:8..34:20):given_Double => *(intValue) @@ -2269,6 +2276,33 @@ Synthetics: [8:2..8:10):(x1, x1) => *(Tuple2(Int, Int)) [8:10..8:10): => *(Int, Int) +expect/JavaStaticVar.scala +-------------------------- + +Summary: +Schema => SemanticDB v4 +Uri => JavaStaticVar.scala +Text => empty +Language => Scala +Symbols => 3 entries +Occurrences => 9 entries + +Symbols: +example/ScalaFoo# => class ScalaFoo extends Object { self: ScalaFoo => +2 decls } +example/ScalaFoo#``(). => primary ctor (): ScalaFoo +example/ScalaFoo#javaStaticVarFoo. => val method javaStaticVarFoo Int + +Occurrences: +[0:8..0:15): example <- example/ +[2:7..2:10): com -> com/ +[2:11..2:17): javacp -> com/javacp/ +[2:18..2:31): JavaStaticVar -> com/javacp/JavaStaticVar# +[4:6..4:14): ScalaFoo <- example/ScalaFoo# +[5:2..5:2): <- example/ScalaFoo#``(). +[5:6..5:22): javaStaticVarFoo <- example/ScalaFoo#javaStaticVarFoo. +[5:25..5:38): JavaStaticVar -> com/javacp/JavaStaticVar# +[5:39..5:42): foo -> com/javacp/JavaStaticVar#foo. + expect/Local.scala ------------------ @@ -3533,6 +3567,7 @@ Text => empty Language => Scala Symbols => 62 entries Occurrences => 165 entries +Diagnostics => 3 entries Synthetics => 39 entries Symbols: @@ -3766,6 +3801,11 @@ Occurrences: [68:18..68:24): impure -> local20 [68:30..68:31): s -> local16 +Diagnostics: +[19:21..19:22): [warning] unused pattern variable +[41:4..41:5): [warning] unused pattern variable +[63:10..63:11): [warning] unused explicit parameter + Synthetics: [5:2..5:13):List(1).map => *[Int] [5:2..5:6):List => *.apply[Int] @@ -4267,7 +4307,6 @@ Text => empty Language => Scala Symbols => 8 entries Occurrences => 18 entries -Diagnostics => 1 entries Symbols: _empty_/Test_depmatch. => final object Test_depmatch extends Object { self: Test_depmatch.type => +4 decls } @@ -4299,9 +4338,6 @@ Occurrences: [6:19..6:20): U -> local0 [6:24..6:27): ??? -> scala/Predef.`???`(). -Diagnostics: -[6:8..6:9): [warning] unused local definition - expect/example-dir/FileInDir.scala ---------------------------------- @@ -4620,6 +4656,7 @@ Text => empty Language => Scala Symbols => 24 entries Occurrences => 63 entries +Diagnostics => 2 entries Symbols: _empty_/Copy# => trait Copy [typeparam In <: Txn[In], typeparam Out <: Txn[Out]] extends Object { self: Copy[In, Out] => +5 decls } @@ -4712,6 +4749,10 @@ Occurrences: [14:8..14:15): println -> scala/Predef.println(+1). [17:4..17:7): out -> local0 +Diagnostics: +[13:12..13:17): [warning] unused pattern variable +[13:28..13:34): [warning] unused pattern variable + expect/inlineconsume.scala -------------------------- @@ -5006,7 +5047,7 @@ Text => empty Language => Scala Symbols => 50 entries Occurrences => 78 entries -Diagnostics => 4 entries +Diagnostics => 6 entries Synthetics => 2 entries Symbols: @@ -5146,6 +5187,8 @@ Diagnostics: [9:36..9:37): [warning] unused explicit parameter [9:42..9:43): [warning] unused explicit parameter [21:11..21:12): [warning] unused explicit parameter +[24:24..24:27): [warning] unused pattern variable +[25:27..25:28): [warning] unused pattern variable Synthetics: [23:6..23:10):List => *.unapplySeq[Nothing] @@ -5558,13 +5601,13 @@ Occurrences: [119:39..119:42): Int -> scala/Int# Diagnostics: -[5:13..5:14): [warning] unused explicit parameter [62:25..62:29): [warning] with as a type operator has been deprecated; use & instead This construct can be rewritten automatically under -rewrite -source 3.4-migration. [63:25..63:29): [warning] with as a type operator has been deprecated; use & instead This construct can be rewritten automatically under -rewrite -source 3.4-migration. [71:31..71:31): [warning] `_` is deprecated for wildcard arguments of types: use `?` instead This construct can be rewritten automatically under -rewrite -source 3.4-migration. +[96:13..96:14): [warning] unused explicit parameter Synthetics: [68:20..68:24):@ann => *[Int] diff --git a/tests/untried/neg/warn-unused-privates.scala b/tests/untried/neg/warn-unused-privates.scala deleted file mode 100644 index 64e7679f37ac..000000000000 --- a/tests/untried/neg/warn-unused-privates.scala +++ /dev/null @@ -1,105 +0,0 @@ -class Bippy(a: Int, b: Int) { - private def this(c: Int) = this(c, c) // warn - private def bippy(x: Int): Int = bippy(x) // TODO: could warn - private def boop(x: Int) = x + a + b // warn - final private val MILLIS1 = 2000 // no warn, might have been inlined - final private val MILLIS2: Int = 1000 // warn - final private val HI_COMPANION: Int = 500 // no warn, accessed from companion - def hi() = Bippy.HI_INSTANCE -} -object Bippy { - def hi(x: Bippy) = x.HI_COMPANION - private val HI_INSTANCE: Int = 500 // no warn, accessed from instance - private val HEY_INSTANCE: Int = 1000 // warn -} - -class A(val msg: String) -class B1(msg: String) extends A(msg) -class B2(msg0: String) extends A(msg0) -class B3(msg0: String) extends A("msg") - -/*** Early defs warnings disabled primarily due to SI-6595. - * The test case is here to assure we aren't issuing false positives; - * the ones labeled "warn" don't warn. - ***/ -class Boppy extends { - private val hmm: String = "abc" // no warn, used in early defs - private val hom: String = "def" // no warn, used in body - private final val him = "ghi" // no warn, might have been (was) inlined - final val him2 = "ghi" // no warn, same - final val himinline = him - private val hum: String = "jkl" // warn - final val ding = hmm.length -} with Mutable { - val dinger = hom - private val hummer = "def" // warn - - private final val bum = "ghi" // no warn, might have been (was) inlined - final val bum2 = "ghi" // no warn, same -} - -trait Accessors { - private var v1: Int = 0 // warn - private var v2: Int = 0 // warn, never set - private var v3: Int = 0 // warn, never got - private var v4: Int = 0 // no warn - - def bippy(): Int = { - v3 = 5 - v4 = 6 - v2 + v4 - } -} - -trait DefaultArgs { - // warn about default getters for x2 and x3 - private def bippy(x1: Int, x2: Int = 10, x3: Int = 15): Int = x1 + x2 + x3 - - def boppy() = bippy(5, 100, 200) -} - -class Outer { - class Inner -} - -trait Locals { - def f0 = { - var x = 1 // warn - var y = 2 - y = 3 - y + y - } - def f1 = { - val a = new Outer // no warn - val b = new Outer // warn - new a.Inner - } - def f2 = { - var x = 100 // warn about it being a var - x - } -} - -object Types { - private object Dongo { def f = this } // warn - private class Bar1 // warn - private class Bar2 // no warn - private type Alias1 = String // warn - private type Alias2 = String // no warn - def bippo = (new Bar2).toString - - def f(x: Alias2) = x.length - - def l1() = { - object HiObject { def f = this } // warn - class Hi { // warn - def f1: Hi = new Hi - def f2(x: Hi) = x - } - class DingDongDoobie // warn - class Bippy // no warn - type Something = Bippy // no warn - type OtherThing = String // warn - (new Bippy): Something - } -} diff --git a/tests/pos/ext-override.scala b/tests/warn/ext-override.scala similarity index 100% rename from tests/pos/ext-override.scala rename to tests/warn/ext-override.scala diff --git a/tests/warn/i12460.check b/tests/warn/i12460.check new file mode 100644 index 000000000000..ba44bed1a18f --- /dev/null +++ b/tests/warn/i12460.check @@ -0,0 +1,24 @@ +-- [E208] Potential Issue Warning: tests/warn/i12460.scala:3:23 -------------------------------------------------------- +3 |extension (s: String = "hello, world") def invert = s.reverse.toUpperCase // warn + | ^ + | Extension method invert should not have a default argument for its receiver. + |--------------------------------------------------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | The receiver cannot be omitted when an extension method is invoked as a selection. + | A default argument for that parameter would never be used in that case. + | An extension method can be invoked as a regular method, but if that is the intended usage, + | it should not be defined as an extension. + --------------------------------------------------------------------------------------------------------------------- +-- [E208] Potential Issue Warning: tests/warn/i12460.scala:5:37 -------------------------------------------------------- +5 |extension (using String)(s: String = "hello, world") def revert = s.reverse.toUpperCase // warn + | ^ + | Extension method revert should not have a default argument for its receiver. + |--------------------------------------------------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | The receiver cannot be omitted when an extension method is invoked as a selection. + | A default argument for that parameter would never be used in that case. + | An extension method can be invoked as a regular method, but if that is the intended usage, + | it should not be defined as an extension. + --------------------------------------------------------------------------------------------------------------------- diff --git a/tests/warn/i12460.scala b/tests/warn/i12460.scala new file mode 100644 index 000000000000..0e37c6aa7de7 --- /dev/null +++ b/tests/warn/i12460.scala @@ -0,0 +1,9 @@ +//> using options -explain -Ystop-after:refchecks + +extension (s: String = "hello, world") def invert = s.reverse.toUpperCase // warn + +extension (using String)(s: String = "hello, world") def revert = s.reverse.toUpperCase // warn + +extension (s: String) + def divert(m: String = "hello, world") = (s+m).reverse.toUpperCase // ok + def divertimento(using String)(m: String = "hello, world") = (s+m).reverse.toUpperCase // ok diff --git a/tests/pos/i15226.scala b/tests/warn/i15226.scala similarity index 100% rename from tests/pos/i15226.scala rename to tests/warn/i15226.scala diff --git a/tests/warn/i15503a.scala b/tests/warn/i15503a.scala index df8691c21a13..40b6c75983bf 100644 --- a/tests/warn/i15503a.scala +++ b/tests/warn/i15503a.scala @@ -1,5 +1,4 @@ -//> using options -Wunused:imports - +//> using options -Wunused:imports -Wconf:origin=Suppressed.*:s object FooUnused: import collection.mutable.Set // warn @@ -68,7 +67,7 @@ object InlineChecks: inline def getSet = Set(1) object InlinedBar: - import collection.mutable.Set // ok + import collection.mutable.Set // warn (don't be fooled by inline expansion) import collection.mutable.Map // warn val a = InlineFoo.getSet @@ -100,20 +99,28 @@ object SomeGivenImports: given String = "foo" /* BEGIN : Check on packages*/ -package testsamepackageimport: - package p { +package nestedpackageimport: + package p: class C - } - - package p { - import p._ // warn - package q { - class U { + package p: + package q: + import p.* // warn + class U: def f = new C - } - } - } -// ----------------------- +package unnestedpackageimport: + package p: + class C + package p.q: + import p.* // nowarn + class U: + def f = new C + +package redundancy: + object redundant: + def f = 42 + import redundancy.* // warn superseded by def in scope + class R: + def g = redundant.f package testpackageimport: package a: @@ -213,7 +220,8 @@ package testImportsInImports: package c: import a.b // OK import b.x // OK - val y = x + import b.x as z // OK + val y = x + z //------------------------------------- package testOnOverloadedMethodsImports: @@ -265,4 +273,51 @@ package foo.test.typeapply.hklamdba.i16680: import foo.IO // OK def f[F[_]]: String = "hello" - def go = f[IO] \ No newline at end of file + def go = f[IO] + +object Selections: + def f(list: List[Int]): Int = + import list.{head => first} // OK + first + + def f2(list: List[Int]): Int = + import list.head // OK + head + + def f3(list: List[Int]): Int = + import list.head // warn + list.head + + object N: + val ns: List[Int] = Nil + + def g(): Int = + import N.ns // OK + ns.head +end Selections + +object `more nestings`: + object Outer: + object Inner: + val thing = 42 + def j() = + import Inner.thing // warn + thing + def k() = + import Inner.thing // warn + Inner.thing + + object Thing: + object Inner: + val thing = 42 + import Inner.thing // warn + def j() = + thing + def k() = + Inner.thing + +object Suppressed: + val suppressed = 42 +object Suppressing: + import Suppressed.* // no warn, see options + def f = 42 diff --git a/tests/warn/i15503b.scala b/tests/warn/i15503b.scala index 7ab86026ff00..0ea110f833f1 100644 --- a/tests/warn/i15503b.scala +++ b/tests/warn/i15503b.scala @@ -117,7 +117,7 @@ package foo.scala2.tests: object Types { def l1() = { - object HiObject { def f = this } // OK + object HiObject { def f = this } // warn class Hi { // warn def f1: Hi = new Hi def f2(x: Hi) = x @@ -141,4 +141,4 @@ package test.foo.twisted.i16682: } isInt - def f = myPackage("42") \ No newline at end of file + def f = myPackage("42") diff --git a/tests/warn/i15503c.scala b/tests/warn/i15503c.scala index a813329da89b..86b972487e17 100644 --- a/tests/warn/i15503c.scala +++ b/tests/warn/i15503c.scala @@ -1,4 +1,4 @@ -//> using options -Wunused:privates -source:3.3 +//> using options -Wunused:privates -source:3.3 trait C class A: @@ -33,17 +33,22 @@ class A: var w = 2 // OK package foo.test.constructors: - case class A private (x:Int) // OK + case class A private (x: Int) // OK class B private (val x: Int) // OK - class C private (private val x: Int) // warn + object B { def default = B(42) } + class C private (private val xy: Int) // warn + object C { def default = C(42) } class D private (private val x: Int): // OK def y = x + object D { def default = D(42) } class E private (private var x: Int): // warn not set def y = x + object E { def default = E(42) } class F private (private var x: Int): // OK def y = x = 3 x + object F { def default = F(42) } package test.foo.i16682: object myPackage: @@ -55,4 +60,13 @@ package test.foo.i16682: case _ => println("NaN") } - def f = myPackage.isInt("42") \ No newline at end of file + def f = myPackage.isInt("42") + +object LazyVals: + import java.util.concurrent.CountDownLatch + + // This trait extends Serializable to fix #16806 that caused a race condition + sealed trait LazyValControlState extends Serializable + + final class Waiting extends CountDownLatch(1), LazyValControlState: + private def writeReplace(): Any = null diff --git a/tests/warn/i15503d.scala b/tests/warn/i15503d.scala index 9a0ba9b2f8dc..494952e2e4b0 100644 --- a/tests/warn/i15503d.scala +++ b/tests/warn/i15503d.scala @@ -1,5 +1,6 @@ -//> using options -Wunused:unsafe-warn-patvars -// todo : change to :patvars +//> using options -Wunused:patvars + +import scala.reflect.Typeable sealed trait Calc sealed trait Const extends Calc @@ -8,23 +9,118 @@ case class S(pred: Const) extends Const case object Z extends Const val a = Sum(S(S(Z)),Z) match { - case Sum(a,Z) => Z // warn + case Sum(x,Z) => Z // warn // case Sum(a @ _,Z) => Z // todo : this should pass in the future - case Sum(a@S(_),Z) => Z // warn - case Sum(a@S(_),Z) => a // warn unreachable - case Sum(a@S(b@S(_)), Z) => a // warn - case Sum(a@S(b@S(_)), Z) => a // warn - case Sum(a@S(b@(S(_))), Z) => Sum(a,b) // warn unreachable + case Sum(x@S(_),Z) => Z // warn + case Sum(x@S(_),Z) => x // warn unreachable + case Sum(x@S(y@S(_)), Z) => x // warn + case Sum(x@S(y@S(_)), Z) => x // warn + case Sum(x@S(y@(S(_))), Z) => Sum(x,y) // warn unreachable case Sum(_,_) => Z // OK case _ => Z // warn unreachable } -// todo : This should pass in the future -// val b = for { -// case Some(x) <- Option(Option(1)) -// } println(s"$x") +case class K(i: Int, j: Int) + +class C(c0: Option[Int], k0: K): + private val Some(c) = c0: @unchecked // warn valdef from pattern + private val K(i, j) = k0 // warn // warn valdefs from pattern (RHS patvars are NoWarn) + val K(v, w) = k0 // nowarn nonprivate + private val K(r, s) = k0 // warn // warn valdefs from pattern + def f(x: Option[Int]) = x match + case Some(y) => true // warn Bind in pattern + case _ => false + def g(ns: List[Int]) = + for x <- ns do println() // warn valdef function param from for + def g1(ns: List[Int]) = + for x <- ns do println(x) // x => println(x) + def h(ns: List[Int]) = + for x <- ns; y = x + 1 // warn tupling from for; x is used, y is unused + do println() + def k(x: Option[K]) = + x match + case Some(K(i, j)) => // nowarn canonical names + case _ => + + val m = Map( + "first" -> Map((true, 1), (false, 2), (true, 3)), + "second" -> Map((true, 1), (false, 2), (true, 3)), + ) + def guardedUse = + m.map: (a, m1) => + for (status, lag) <- m1 if status + yield (a, status, lag) + def guardedUseOnly = + m.map: (a, m1) => + for (status, lag) <- m1 if status + yield (a, lag) + def guardedUseMissing = + m.map: (a, m1) => + for (status, lag) <- m1 // warn + yield (a, lag) + def flatGuardedUse = + for (a, m1) <- m; (status, lag) <- m1 if status + yield (a, status, lag) + def leading = + for _ <- List("42"); i = 1; _ <- List("0", "27")(i) + yield () + def optional = + for case Some(x) <- List(Option(42)) + yield x + def nonoptional = + for case Some(x) <- List(Option(42)) // warn + yield 27 + def optionalName = + for case Some(value) <- List(Option(42)) + yield 27 + + /* + def tester[A](a: A)(using Typeable[K]) = + a match + case S(i, j) => i + j + case _ => 0 + */ + +class Wild: + def f(x: Any) = + x match + case _: Option[?] => true + case _ => false + +def untuple(t: Tuple) = + t match + case Tuple() => + case h *: t => // no warn canonical names taken from tuple element types, (Head, Tail) -> (head, tail) + //case head *: tail => // no warn canonical names taken from tuple element types, (Head, Tail) -> (head, tail) + +// empty case class: +// def equals(other) = other match { case other => true } // exonerated name +object i15967: + sealed trait A[-Z] + final case class B[Y]() extends A[Y] + +object `patvar is assignable`: + var (i, j) = (42, 27) // no warn nonprivate + j += 1 + println((i, j)) + +object `privy patvar is assignable`: + private var (i, j) = (42, 27) // warn + j += 1 + println((i, j)) + +object `local patvar is assignable`: + def f() = + var (i, j) = (42, 27) // warn + j += 1 + println((i, j)) + +object `mutable patvar in for`: + def f(xs: List[Int]) = + for x <- xs; y = x + 1 if y > 10 yield + var z :: Nil = y :: Nil: @unchecked // warn + z + 10 -// todo : This should *NOT* pass in the future -// val c = for { -// case Some(x) <- Option(Option(1)) -// } println(s"hello world") \ No newline at end of file +class `unset var requires -Wunused`: + private var i = 0 // no warn as we didn't ask for it + def f = println(i) diff --git a/tests/warn/i15503e.scala b/tests/warn/i15503e.scala index 46d73a4945cd..2fafec339ac1 100644 --- a/tests/warn/i15503e.scala +++ b/tests/warn/i15503e.scala @@ -1,4 +1,6 @@ -//> using options -Wunused:explicits +//> using options -Wunused:explicits + +import annotation.* object Foo { /* This goes around the "trivial method" detection */ @@ -31,16 +33,17 @@ package scala3main: package foo.test.lambda.param: val default_val = 1 val a = (i: Int) => i // OK - val b = (i: Int) => default_val // OK + val b = (i: Int) => default_val // warn val c = (_: Int) => default_val // OK package foo.test.trivial: /* A twisted test from Scala 2 */ - class C { + class C(val value: Int) { def answer: 42 = 42 object X private def g0(x: Int) = ??? // OK private def f0(x: Int) = () // OK + private def f00(x: Int) = {} // OK private def f1(x: Int) = throw new RuntimeException // OK private def f2(x: Int) = 42 // OK private def f3(x: Int): Option[Int] = None // OK @@ -50,13 +53,16 @@ package foo.test.trivial: private def f7(x: Int) = Y // OK private def f8(x: Int): List[C] = Nil // OK private def f9(x: Int): List[Int] = List(1,2,3,4) // warn - private def foo:Int = 32 // OK + private def foo: Int = 32 // OK private def f77(x: Int) = foo // warn + private def self(x: Int): C = this // no warn + private def unwrap(x: Int): Int = value // no warn } object Y package foo.test.i16955: - class S(var r: String) // OK + class S(var rrr: String) // OK + class T(rrr: String) // warn package foo.test.i16865: trait Foo: @@ -64,7 +70,26 @@ package foo.test.i16865: trait Bar extends Foo object Ex extends Bar: - def fn(a: Int, b: Int): Int = b + 3 // OK + def fn(a: Int, b: Int): Int = b + 3 // warn object Ex2 extends Bar: - override def fn(a: Int, b: Int): Int = b + 3 // OK \ No newline at end of file + override def fn(a: Int, b: Int): Int = b + 3 // warn + +final class alpha(externalName: String) extends StaticAnnotation // no warn annotation arg + +object Unimplemented: + import compiletime.* + inline def f(inline x: Int | Double): Unit = error("unimplemented") // no warn param of trivial method + +def `trivially wrapped`(x: String): String ?=> String = "hello, world" // no warn param of trivial method + +object UnwrapTyped: + import compiletime.error + inline def requireConst(inline x: Boolean | Byte | Short | Int | Long | Float | Double | Char | String): Unit = + error("Compiler bug: `requireConst` was not evaluated by the compiler") + + transparent inline def codeOf(arg: Any): String = + error("Compiler bug: `codeOf` was not evaluated by the compiler") + +object `default usage`: + def f(i: Int)(j: Int = i * 2) = j // warn I guess diff --git a/tests/warn/i15503f.scala b/tests/warn/i15503f.scala index ccf0b7e74065..0550f82d9398 100644 --- a/tests/warn/i15503f.scala +++ b/tests/warn/i15503f.scala @@ -6,9 +6,49 @@ val default_int = 1 object Xd { private def f1(a: Int) = a // OK private def f2(a: Int) = 1 // OK - private def f3(a: Int)(using Int) = a // OK - private def f4(a: Int)(using Int) = default_int // OK + private def f3(a: Int)(using Int) = a // warn + private def f4(a: Int)(using Int) = default_int // warn private def f6(a: Int)(using Int) = summon[Int] // OK private def f7(a: Int)(using Int) = summon[Int] + a // OK private def f8(a: Int)(using foo: Int) = a // warn + private def f9(a: Int)(using Int) = ??? // OK trivial + private def g1(a: Int)(implicit foo: Int) = a // warn } + +trait T +object T: + def hole(using T) = () + +class C(using T) // warn + +class D(using T): + def t = T.hole // nowarn + +object Example: + import scala.quoted.* + given OptionFromExpr[T](using Type[T], FromExpr[T]): FromExpr[Option[T]] with + def unapply(x: Expr[Option[T]])(using Quotes) = x match + case '{ Option[T](${Expr(y)}) } => Some(Option(y)) + case '{ None } => Some(None) + case '{ ${Expr(opt)} : Some[T] } => Some(opt) + case _ => None + +object ExampleWithoutWith: + import scala.quoted.* + given [T] => (Type[T], FromExpr[T]) => FromExpr[Option[T]]: + def unapply(x: Expr[Option[T]])(using Quotes) = x match + case '{ Option[T](${Expr(y)}) } => Some(Option(y)) + case '{ None } => Some(None) + case '{ ${Expr(opt)} : Some[T] } => Some(opt) + case _ => None + +//absolving names on matches of quote trees requires consulting non-abstract types in QuotesImpl +object Unmatched: + import scala.quoted.* + def transform[T](e: Expr[T])(using Quotes): Expr[T] = + import quotes.reflect.* + def f(tree: Tree) = + tree match + case Ident(name) => + case _ => + e diff --git a/tests/warn/i15503g.scala b/tests/warn/i15503g.scala index fbd9f3c1352c..cfbfcdb04d1e 100644 --- a/tests/warn/i15503g.scala +++ b/tests/warn/i15503g.scala @@ -6,8 +6,8 @@ object Foo { private def f1(a: Int) = a // OK private def f2(a: Int) = default_int // warn - private def f3(a: Int)(using Int) = a // OK - private def f4(a: Int)(using Int) = default_int // warn + private def f3(a: Int)(using Int) = a // warn + private def f4(a: Int)(using Int) = default_int // warn // warn private def f6(a: Int)(using Int) = summon[Int] // warn private def f7(a: Int)(using Int) = summon[Int] + a // OK /* --- Trivial method check --- */ @@ -20,4 +20,5 @@ package foo.test.i17101: extension[A] (x: Test[A]) { // OK def value: A = x def causesIssue: Unit = println("oh no") - } \ No newline at end of file + def isAnIssue(y: A): Boolean = x == x // warn + } diff --git a/tests/warn/i15503h.scala b/tests/warn/i15503h.scala index 854693981488..a2bf47c843dd 100644 --- a/tests/warn/i15503h.scala +++ b/tests/warn/i15503h.scala @@ -1,4 +1,4 @@ -//> using options -Wunused:linted +//> using options -Wunused:linted import collection.mutable.Set // warn @@ -7,7 +7,7 @@ class A { val b = 2 // OK private def c = 2 // warn - def d(using x:Int): Int = b // ok + def d(using x: Int): Int = b // warn def e(x: Int) = 1 // OK def f = val x = 1 // warn @@ -15,6 +15,6 @@ class A { 3 def g(x: Int): Int = x match - case x:1 => 0 // OK + case x: 1 => 0 // OK case _ => 1 -} \ No newline at end of file +} diff --git a/tests/warn/i15503i.scala b/tests/warn/i15503i.scala index b7981e0e4206..89ff382eb68c 100644 --- a/tests/warn/i15503i.scala +++ b/tests/warn/i15503i.scala @@ -1,4 +1,4 @@ -//> using options -Wunused:all +//> using options -Wunused:all -Ystop-after:checkUnusedPostInlining import collection.mutable.{Map => MutMap} // warn import collection.mutable.Set // warn @@ -17,10 +17,12 @@ class A { private def c2 = 2 // OK def c3 = c2 - def d1(using x:Int): Int = default_int // ok - def d2(using x:Int): Int = x // OK + def d1(using x: Int): Int = default_int // warn param + def d2(using x: Int): Int = x // OK + def d3(using Int): Int = summon[Int] // OK + def d4(using Int): Int = default_int // warn - def e1(x: Int) = default_int // ok + def e1(x: Int) = default_int // warn param def e2(x: Int) = x // OK def f = val x = 1 // warn @@ -29,11 +31,15 @@ class A { def g = 4 // OK y + g - // todo : uncomment once patvars is fixed - // def g(x: Int): Int = x match - // case x:1 => 0 // ?error - // case x:2 => x // ?OK - // case _ => 1 // ?OK + def g(x: Int): Int = x match + case x: 1 => 0 // no warn same name as selector (for shadowing or unused) + case x: 2 => x // OK + case _ => 1 // OK + + def h(x: Int): Int = x match + case y: 1 => 0 // warn unused despite trivial type and RHS + case y: 2 => y // OK + case _ => 1 // OK } /* ---- CHECK scala.annotation.unused ---- */ @@ -44,7 +50,7 @@ package foo.test.scala.annotation: val default_int = 12 def a1(a: Int) = a // OK - def a2(a: Int) = default_int // ok + def a2(a: Int) = default_int // warn def a3(@unused a: Int) = default_int //OK @@ -82,8 +88,8 @@ package foo.test.i16678: def run = println(foo(number => number.toString, value = 5)) // OK println(foo(number => "", value = 5)) // warn - println(foo(func = number => "", value = 5)) // warn println(foo(func = number => number.toString, value = 5)) // OK + println(foo(func = number => "", value = 5)) // warn println(foo(func = _.toString, value = 5)) // OK package foo.test.possibleclasses: @@ -91,7 +97,7 @@ package foo.test.possibleclasses: k: Int, // OK private val y: Int // OK /* Kept as it can be taken from pattern */ )( - s: Int, + s: Int, // warn val t: Int, // OK private val z: Int // warn ) @@ -130,22 +136,22 @@ package foo.test.possibleclasses: package foo.test.possibleclasses.withvar: case class AllCaseClass( k: Int, // OK - private var y: Int // OK /* Kept as it can be taken from pattern */ + private var y: Int // warn unset )( - s: Int, - var t: Int, // OK - private var z: Int // warn + s: Int, // warn + var ttt: Int, // OK + private var zzz: Int // warn ) case class AllCaseUsed( k: Int, // OK - private var y: Int // OK + private var y: Int // warn unset )( s: Int, // OK - var t: Int, // OK global scope can be set somewhere else - private var z: Int // warn not set + var tt: Int, // OK global scope can be set somewhere else + private var zz: Int // warn not set ) { - def a = k + y + s + t + z + def a = k + y + s + tt + zz } class AllClass( @@ -199,14 +205,14 @@ package foo.test.i16877: package foo.test.i16926: def hello(): Unit = for { - i <- (0 to 10).toList + i <- (0 to 10).toList // warn patvar (a, b) = "hello" -> "world" // OK } yield println(s"$a $b") package foo.test.i16925: def hello = for { - i <- 1 to 2 if true + i <- 1 to 2 if true // OK _ = println(i) // OK } yield () @@ -247,7 +253,7 @@ package foo.test.i16679a: import scala.deriving.Mirror object CaseClassByStringName: inline final def derived[A](using inline A: Mirror.Of[A]): CaseClassByStringName[A] = - new CaseClassByStringName[A]: // warn + new CaseClassByStringName[A]: def name: String = A.toString object secondPackage: @@ -263,7 +269,7 @@ package foo.test.i16679b: object CaseClassName: import scala.deriving.Mirror inline final def derived[A](using inline A: Mirror.Of[A]): CaseClassName[A] = - new CaseClassName[A]: // warn + new CaseClassName[A]: def name: String = A.toString object Foo: @@ -279,7 +285,7 @@ package foo.test.i17156: package a: trait Foo[A] object Foo: - inline def derived[T]: Foo[T] = new Foo{} // warn + inline def derived[T]: Foo[T] = new Foo {} package b: import a.Foo @@ -313,3 +319,52 @@ package foo.test.i17117: val test = t1.test } } + +// manual testing of cached look-ups +package deeply: + object Deep: + def f(): Unit = + def g(): Unit = + def h(): Unit = + println(Deep) + println(Deep) + println(Deep) + h() + g() + override def toString = "man, that is deep!" +/* result cache saves before context traversal and import/member look-up +CHK object Deep +recur * 10 = context depth is 10 between reference and definition +CHK method println +recur = was 19 at predef where max root is 21 +CHK object Deep +recur = cached result +CHK method println +recur +CHK object Deep +recur +*/ + +package constructors: + class C private (i: Int): // warn param + def this() = this(27) + private def this(s: String) = this(s.toInt) // warn ctor + def c = new C(42) + private def f(i: Int) = i // warn overloaded member + private def f(s: String) = s + def g = f("hello") // use one of overloaded member + + class D private (i: Int): + private def this() = this(42) // no warn used by companion + def d = i + object D: + def apply(): D = new D() + +package reversed: // reverse-engineered + class C: + def c: scala.Int = 42 // Int marked used; lint does not look up .scala + class D: + def d: Int = 27 // Int is found in root import scala.* + class E: + import scala.* // no warn because root import (which is cached! by previous lookup) is lower precedence + def e: Int = 27 diff --git a/tests/warn/i15503k.scala b/tests/warn/i15503k.scala new file mode 100644 index 000000000000..8148de44c588 --- /dev/null +++ b/tests/warn/i15503k.scala @@ -0,0 +1,43 @@ + +//> using options -Wunused:imports + +import scala.compiletime.ops.int.* // no warn + +object TupleOps: + /** Type of the element at position N in the tuple X */ + type Elem[X <: Tuple, N <: Int] = X match { + case x *: xs => + N match { + case 0 => x + case S[n1] => Elem[xs, n1] + } + } + + /** Literal constant Int size of a tuple */ + type Size[X <: Tuple] <: Int = X match { + case EmptyTuple => 0 + case x *: xs => S[Size[xs]] + } + +object Summoner: + transparent inline def summoner[T](using x: T): x.type = x + +object `Summoner's Tale`: + import compiletime.summonFrom // no warn + inline def valueOf[T]: T = summonFrom: // implicit match + case ev: ValueOf[T] => ev.value + import Summoner.* // no warn + def f[T](using T): T = summoner[T] // Inlined + +class C: + private def m: Int = 42 // no warn +object C: + class D: + private val c: C = C() // no warn + export c.m // no work to do, expanded member is non-private and uses the select expr + +object UsefulTypes: + trait T +object TypeUser: + import UsefulTypes.* + def f(x: => T) = x diff --git a/tests/warn/i15503kb/power.scala b/tests/warn/i15503kb/power.scala new file mode 100644 index 000000000000..f7e5ccce6d58 --- /dev/null +++ b/tests/warn/i15503kb/power.scala @@ -0,0 +1,14 @@ + +object Power: + import scala.math.pow as power + import scala.quoted.* + inline def powerMacro(x: Double, inline n: Int): Double = ${ powerCode('x, 'n) } + def powerCode(x: Expr[Double], n: Expr[Int])(using Quotes): Expr[Double] = + n match + case Expr(m) => unrolledPowerCode(x, m) + case _ => '{ power($x, $n.toDouble) } + def unrolledPowerCode(x: Expr[Double], n: Int)(using Quotes): Expr[Double] = + n match + case 0 => '{ 1.0 } + case 1 => x + case _ => '{ $x * ${ unrolledPowerCode(x, n - 1) } } diff --git a/tests/warn/i15503kb/square.scala b/tests/warn/i15503kb/square.scala new file mode 100644 index 000000000000..2a5f76e9be83 --- /dev/null +++ b/tests/warn/i15503kb/square.scala @@ -0,0 +1,5 @@ +//> using options -Werror -Wunused:all + +object PowerUser: + import Power.* + def square(x: Double): Double = powerMacro(x, 2) diff --git a/tests/pos/i15967.scala b/tests/warn/i15967.scala similarity index 100% rename from tests/pos/i15967.scala rename to tests/warn/i15967.scala diff --git a/tests/warn/i16639a.scala b/tests/warn/i16639a.scala index 9fe4efe57d7b..ca7798297aea 100644 --- a/tests/warn/i16639a.scala +++ b/tests/warn/i16639a.scala @@ -1,7 +1,7 @@ //> using options -Wunused:all -source:3.3 class Bippy(a: Int, b: Int) { - private def this(c: Int) = this(c, c) + private def this(c: Int) = this(c, c) // warn private def boop(x: Int) = x+a+b // warn private def bippy(x: Int): Int = bippy(x) // warn TODO: could warn final private val MILLIS1 = 2000 // warn no warn, /Dotty:Warn @@ -24,13 +24,13 @@ class B3(msg0: String) extends A("msg") // warn /Dotty: unused explicit paramete trait Bing trait Accessors { - private var v1: Int = 0 // warn warn - private var v2: Int = 0 // warn warn, never set - private var v3: Int = 0 + private var v1: Int = 0 // warn + private var v2: Int = 0 // warn, never set + private var v3: Int = 0 // warn, never got private var v4: Int = 0 // no warn - private[this] var v5 = 0 // warn warn, never set - private[this] var v6 = 0 + private[this] var v5 = 0 // warn, never set + private[this] var v6 = 0 // warn, never got private[this] var v7 = 0 // no warn def bippy(): Int = { @@ -43,13 +43,13 @@ trait Accessors { } class StableAccessors { - private var s1: Int = 0 // warn warn - private var s2: Int = 0 // warn warn, never set - private var s3: Int = 0 + private var s1: Int = 0 // warn + private var s2: Int = 0 // warn, never set + private var s3: Int = 0 // warn, never got private var s4: Int = 0 // no warn - private[this] var s5 = 0 // warn warn, never set - private[this] var s6 = 0 // no warn, limitation /Dotty: Why limitation ? + private[this] var s5 = 0 // warn, never set + private[this] var s6 = 0 // warn, never got private[this] var s7 = 0 // no warn def bippy(): Int = { @@ -62,7 +62,7 @@ class StableAccessors { } trait DefaultArgs { - private def bippy(x1: Int, x2: Int = 10, x3: Int = 15): Int = x1 + x2 + x3 // no more warn warn since #17061 + private def bippy(x1: Int, x2: Int = 10, x3: Int = 15): Int = x1 + x2 + x3 // warn // warn def boppy() = bippy(5, 100, 200) } @@ -91,7 +91,7 @@ trait Locals { } object Types { - private object Dongo { def f = this } // no more warn since #17061 + private object Dongo { def f = this } // warn private class Bar1 // warn warn private class Bar2 // no warn private type Alias1 = String // warn warn @@ -101,7 +101,7 @@ object Types { def f(x: Alias2) = x.length def l1() = { - object HiObject { def f = this } // no more warn since #17061 + object HiObject { def f = this } // warn class Hi { // warn warn def f1: Hi = new Hi def f2(x: Hi) = x @@ -124,9 +124,9 @@ trait Underwarn { } class OtherNames { - private def x_=(i: Int): Unit = () // no more warn since #17061 - private def x: Int = 42 // warn Dotty triggers unused private member : To investigate - private def y_=(i: Int): Unit = () // // no more warn since #17061 + private def x_=(i: Int): Unit = () // warn + private def x: Int = 42 // warn + private def y_=(i: Int): Unit = () // warn private def y: Int = 42 def f = y @@ -145,7 +145,7 @@ trait Forever { val t = Option((17, 42)) for { ns <- t - (i, j) = ns // no warn + (i, j) = ns // warn // warn -Wunused:patvars is in -Wunused:all } yield 42 // val emitted only if needed, hence nothing unused } } @@ -158,14 +158,14 @@ trait CaseyKasem { def f = 42 match { case x if x < 25 => "no warn" case y if toString.nonEmpty => "no warn" + y - case z => "warn" + case z => "warn" // warn patvar } } trait CaseyAtTheBat { def f = Option(42) match { case Some(x) if x < 25 => "no warn" - case Some(y @ _) if toString.nonEmpty => "no warn" - case Some(z) => "warn" + case Some(y @ _) if toString.nonEmpty => "no warn" // warn todo whether to use name @ _ to suppress + case Some(z) => "warn" // warn patvar case None => "no warn" } } @@ -173,7 +173,7 @@ trait CaseyAtTheBat { class `not even using companion privates` object `not even using companion privates` { - private implicit class `for your eyes only`(i: Int) { // no more warn since #17061 + private implicit class `for your eyes only`(i: Int) { // warn def f = i } } @@ -202,4 +202,4 @@ trait `short comings` { val x = 42 // warn /Dotty only triggers in dotty 17 } -} \ No newline at end of file +} diff --git a/tests/warn/i16743.check b/tests/warn/i16743.check index 6fa1f2c83357..9fdf80e71f2b 100644 --- a/tests/warn/i16743.check +++ b/tests/warn/i16743.check @@ -1,3 +1,10 @@ +-- [E194] Potential Issue Warning: tests/warn/i16743.scala:90:8 -------------------------------------------------------- +90 | def length() = 42 // warn This extension method will be shadowed by .length() on String. + | ^ + | Extension method length will never be selected from type String + | because String already has a member with the same name and compatible parameter types. + | + | longer explanation available when compiling with `-explain` -- [E194] Potential Issue Warning: tests/warn/i16743.scala:30:6 -------------------------------------------------------- 30 | def t = 27 // warn | ^ diff --git a/tests/warn/i16743.scala b/tests/warn/i16743.scala index 4c9c99cf30d0..e8860aeabaae 100644 --- a/tests/warn/i16743.scala +++ b/tests/warn/i16743.scala @@ -87,7 +87,7 @@ class Depends: object Depending: extension (using depends: Depends)(x: depends.Thing) def y = 42 - def length() = 42 // nowarn see Quote above + def length() = 42 // warn This extension method will be shadowed by .length() on String. def f(using d: Depends) = d.thing.y def g(using d: Depends) = d.thing.length() diff --git a/tests/pos/i17230.min1.scala b/tests/warn/i17230.min1.scala similarity index 100% rename from tests/pos/i17230.min1.scala rename to tests/warn/i17230.min1.scala diff --git a/tests/pos/i17230.orig.scala b/tests/warn/i17230.orig.scala similarity index 100% rename from tests/pos/i17230.orig.scala rename to tests/warn/i17230.orig.scala diff --git a/tests/pos/i17314.scala b/tests/warn/i17314.scala similarity index 84% rename from tests/pos/i17314.scala rename to tests/warn/i17314.scala index 8ece4a3bd7ac..cff90d843c38 100644 --- a/tests/pos/i17314.scala +++ b/tests/warn/i17314.scala @@ -1,4 +1,4 @@ -//> using options -Xfatal-warnings -Wunused:all -deprecation -feature +//> using options -Wunused:all -deprecation -feature import java.net.URI @@ -10,9 +10,7 @@ object circelike { type Configuration trait ConfiguredCodec[T] object ConfiguredCodec: - inline final def derived[A](using conf: Configuration)(using - inline mirror: Mirror.Of[A] - ): ConfiguredCodec[A] = + inline final def derived[A](using conf: Configuration)(using inline mirror: Mirror.Of[A]): ConfiguredCodec[A] = // warn // warn class InlinedConfiguredCodec extends ConfiguredCodec[A]: val codec = summonInline[Codec[URI]] // simplification new InlinedConfiguredCodec diff --git a/tests/pos/i17314a.scala b/tests/warn/i17314a.scala similarity index 70% rename from tests/pos/i17314a.scala rename to tests/warn/i17314a.scala index 4bce56d8bbed..14ae96848d63 100644 --- a/tests/pos/i17314a.scala +++ b/tests/warn/i17314a.scala @@ -1,4 +1,4 @@ -//> using options -Xfatal-warnings -Wunused:all -deprecation -feature +//> using options -Werror -Wunused:all -deprecation -feature package foo: class Foo[T] diff --git a/tests/warn/i17314b.scala b/tests/warn/i17314b.scala index e1500028ca93..ad4c8f1e4a31 100644 --- a/tests/warn/i17314b.scala +++ b/tests/warn/i17314b.scala @@ -1,4 +1,4 @@ -//> using options -Wunused:all +//> using options -Wunused:all package foo: class Foo[T] diff --git a/tests/warn/i17318.scala b/tests/warn/i17318.scala new file mode 100644 index 000000000000..d242c96484a7 --- /dev/null +++ b/tests/warn/i17318.scala @@ -0,0 +1,33 @@ + +//> using options -Werror -Wunused:all + +object events { + final val PollOut = 0x002 + transparent inline def POLLIN = 0x001 +} + +def withShort(v: Short): Unit = ??? +def withInt(v: Int): Unit = ??? + +def usage() = + import events.POLLIN // reports unused + def v: Short = POLLIN + println(v) + +def usage2() = + import events.POLLIN // reports unused + withShort(POLLIN) + +def usage3() = + import events.POLLIN // does not report unused + withInt(POLLIN) + +def usage4() = + import events.POLLIN // reports unused + withShort(POLLIN) + +def value = 42 +def withDouble(v: Double): Unit = ??? +def usage5() = withDouble(value) +def usage6() = withShort(events.POLLIN) +def usage7() = withShort(events.PollOut) diff --git a/tests/warn/i17371.scala b/tests/warn/i17371.scala new file mode 100644 index 000000000000..f9be76bfed07 --- /dev/null +++ b/tests/warn/i17371.scala @@ -0,0 +1,39 @@ +//> using options -Wunused:all + +class A +class B + +def Test() = + val ordA: Ordering[A] = ??? + val ordB: Ordering[B] = ??? + val a: A = ??? + val b: B = ??? + + import ordA.given + val _ = a > a + + import ordB.given + val _ = b < b + +// unminimized OP +trait Circular[T] extends Ordering[T] +trait Turns[C: Circular, T] extends Ordering[T]: // warn Circular is not a marker interface + extension (turns: T) def extract: C + +def f[K, T](start: T, end: T)(using circular: Circular[K], turns: Turns[K, T]): Boolean = + import turns.given + if start > end then throw new IllegalArgumentException("start must be <= end") + + import circular.given + start.extract < end.extract + +// -Wunused:implicits warns for unused implicit evidence unless it is an empty interface (only universal members). +// scala 2 also offers -Wunused:synthetics for whether to warn for synthetic implicit params. +object ContextBounds: + class C[A: Ordered](a: A): // warn + def f = a + + trait T[A] + + class D[A: T](a: A): // no warn + def f = a diff --git a/tests/warn/i17667.scala b/tests/warn/i17667.scala new file mode 100644 index 000000000000..cc3f6bfc8472 --- /dev/null +++ b/tests/warn/i17667.scala @@ -0,0 +1,10 @@ + +//> using options -Wunused:imports + +object MyImplicits: + extension (a: Int) def print: Unit = println(s"Hello, I am $a") + +import MyImplicits.print //Global import of extension +object Foo: + def printInt(a: Int): Unit = a.print + import MyImplicits.* // warn //Local import of extension diff --git a/tests/warn/i17667b.scala b/tests/warn/i17667b.scala new file mode 100644 index 000000000000..ba8fc7219945 --- /dev/null +++ b/tests/warn/i17667b.scala @@ -0,0 +1,22 @@ + +//> using options -Wunused:all + +import scala.util.Try +import scala.concurrent.* // warn +import scala.collection.Set +class C { + def ss[A](using Set[A]) = println() // warn + private def f = Try(42).get + private def z: Int = // warn + Try(27 + z).get + def g = f + f + def k = + val i = g + g + val j = i + 2 // warn + i + 1 + def c = C() + import scala.util.Try // warn +} +class D { + def d = C().g +} diff --git a/tests/warn/i17753.scala b/tests/warn/i17753.scala new file mode 100644 index 000000000000..66e4fdb8727b --- /dev/null +++ b/tests/warn/i17753.scala @@ -0,0 +1,10 @@ +//> using options -Wunused:all + +class PartiallyApplied[A] { + transparent inline def func[B](): Nothing = ??? +} + +def call[A] = new PartiallyApplied[A] + +def good = call[Int].func[String]() // no warn inline proxy +def bad = { call[Int].func[String]() } // no warn inline proxy diff --git a/tests/warn/i18313.scala b/tests/warn/i18313.scala new file mode 100644 index 000000000000..44b005b313e4 --- /dev/null +++ b/tests/warn/i18313.scala @@ -0,0 +1,14 @@ +//> using options -Werror -Wunused:imports + +import scala.deriving.Mirror + +case class Test(i: Int, d: Double) +case class Decoder(d: Product => Test) + +// OK, no warning returned +//val ok = Decoder(summon[Mirror.Of[Test]].fromProduct) +// +// returns warning: +// [warn] unused import +// [warn] import scala.deriving.Mirror +val d = Decoder(d = summon[Mirror.Of[Test]].fromProduct) // no warn diff --git a/tests/warn/i18366.scala b/tests/warn/i18366.scala new file mode 100644 index 000000000000..b6385b5bbb59 --- /dev/null +++ b/tests/warn/i18366.scala @@ -0,0 +1,19 @@ +//> using options -Werror -Wunused:all + +trait Builder { + def foo(): Unit +} + +def `i18366` = + val builder: Builder = ??? + import builder.{foo => bar} + bar() + +import java.io.DataOutputStream + +val buffer: DataOutputStream = ??? + +import buffer.{write => put} + +def `i17315` = + put(0: Byte) diff --git a/tests/warn/i18564.scala b/tests/warn/i18564.scala new file mode 100644 index 000000000000..19682b7955f9 --- /dev/null +++ b/tests/warn/i18564.scala @@ -0,0 +1,39 @@ + +//> using options -Wunused:imports + +import scala.compiletime.* +import scala.deriving.* + +trait Foo + +trait HasFoo[A]: + /** true if A contains a Foo */ + val hasFoo: Boolean + +// given instances that need to be imported to be in scope +object HasFooInstances: + given defaultHasFoo[A]: HasFoo[A] with + val hasFoo: Boolean = false + given HasFoo[Foo] with + val hasFoo: Boolean = true + +object HasFooDeriving: + + inline private def tupleHasFoo[T <: Tuple]: Boolean = + inline erasedValue[T] match + case _: EmptyTuple => false + case _: (t *: ts) => summonInline[HasFoo[t]].hasFoo || tupleHasFoo[ts] + + inline def deriveHasFoo[T](using p: Mirror.ProductOf[T]): HasFoo[T] = + // falsely reported as unused even though it has influence on this code + import HasFooInstances.given // no warn at inline method + val pHasFoo = tupleHasFoo[p.MirroredElemTypes] + new HasFoo[T]: // warn New anonymous class definition will be duplicated at each inline site + val hasFoo: Boolean = pHasFoo + +/* the import is used upon inline elaboration +object Test: + import HasFooDeriving.* + case class C(x: Foo, y: Int) + def f: HasFoo[C] = deriveHasFoo[C] +*/ diff --git a/tests/warn/i19252.scala b/tests/warn/i19252.scala new file mode 100644 index 000000000000..42ca99208299 --- /dev/null +++ b/tests/warn/i19252.scala @@ -0,0 +1,13 @@ +//> using options -Werror -Wunused:all +object Deps: + trait D1 + object D2 +end Deps + +object Bug: + import Deps.D1 // no warn + + class Cl(d1: D1): + import Deps.* + def f = (d1, D2) +end Bug diff --git a/tests/warn/i19657-mega.scala b/tests/warn/i19657-mega.scala new file mode 100644 index 000000000000..07411ee73fb1 --- /dev/null +++ b/tests/warn/i19657-mega.scala @@ -0,0 +1,4 @@ +//> using options -Wshadow:type-parameter-shadow -Wunused:all + +class F[X, M[N[X]]]: // warn + private def x[X] = toString // warn // warn diff --git a/tests/warn/i19657.scala b/tests/warn/i19657.scala new file mode 100644 index 000000000000..2caa1c832abe --- /dev/null +++ b/tests/warn/i19657.scala @@ -0,0 +1,117 @@ +//> using options -Wunused:imports -Ystop-after:checkUnusedPostInlining + +trait Schema[A] + +case class Foo() +case class Bar() + +trait SchemaGenerator[A] { + given Schema[A] = new Schema[A]{} +} + +object FooCodec extends SchemaGenerator[Foo] +object BarCodec extends SchemaGenerator[Bar] + +def summonSchemas(using Schema[Foo], Schema[Bar]) = () + +def summonSchema(using Schema[Foo]) = () + +def `i19657 check prefix to pick selector`: Unit = + import FooCodec.given + import BarCodec.given + summonSchemas + +def `i19657 regression test`: Unit = + import FooCodec.given + import BarCodec.given // warn + summonSchema + +def `i19657 check prefix to pick specific selector`: Unit = + import FooCodec.given_Schema_A + import BarCodec.given_Schema_A + summonSchemas + +def `same symbol different names`: Unit = + import FooCodec.given_Schema_A + import FooCodec.given_Schema_A as AThing + summonSchema(using given_Schema_A) + summonSchema(using AThing) + +package i17156: + package a: + trait Foo[A] + object Foo: + class Food[A] extends Foo[A] + inline def derived[T]: Foo[T] = Food() + + package b: + import a.Foo + type Xd[A] = Foo[A] + + package c: + import b.Xd + trait Z derives Xd // checks if dealiased import is prefix a.Foo + class Bar extends Xd[Int] // checks if import qual b is prefix of b.Xd + +object Coll: + class C: + type HM[K, V] = scala.collection.mutable.HashMap[K, V] +object CC extends Coll.C +import CC.* + +def `param type is imported`(map: HM[String, String]): Unit = println(map("hello, world")) + +object Constants: + final val i = 42 + def extra = 3 +def `old-style constants are usages`: Unit = + object Local: + final val j = 27 + import Constants.i + println(i + Local.j) + +object Constantinople: + val k = 42 +class `scope of super`: + import Constants.i // was bad warn + class C(x: Int): + def y = x + class D(j: Int) extends C(i + j): + import Constants.* // does not resolve i in C(i) and does not shadow named import + def m = i // actually picks the higher-precedence import + def f = + import Constantinople.* + class E(e: Int) extends C(i + k): + def g = e + y + k + 1 + E(0).g + def consume = extra // use the wildcard import from Constants + +import scala.annotation.meta.* +object Alias { + type A = Deprecated @param +} + +// avoid reporting on runtime (nothing to do with transparent inline) +import scala.runtime.EnumValue + +trait Lime + +enum Color(val rgb: Int): + case Red extends Color(0xFF0000) with EnumValue + case Green extends Color(0x00FF00) with Lime + case Blue extends Color(0x0000FF) + +object prefixes: + class C: + object N: + type U + object Test: + val c: C = ??? + def k2: c.N.U = ??? + import c.N.* + def k3: U = ??? // TypeTree if not a select + object Alt: + val c: C = ??? + import c.N + def k4: N.U = ??? +end prefixes diff --git a/tests/warn/i20520.scala b/tests/warn/i20520.scala new file mode 100644 index 000000000000..e09d16c27af2 --- /dev/null +++ b/tests/warn/i20520.scala @@ -0,0 +1,11 @@ + +//> using options -Wunused:all + +@main def run = + val veryUnusedVariable: Int = value // warn local + +package i20520: + private def veryUnusedMethod(x: Int): Unit = println() // warn param + private val veryUnusedVariableToplevel: Unit = println() // package members are accessible under separate compilation + +def value = 42 diff --git a/tests/pos/i20860.scala b/tests/warn/i20860.scala similarity index 74% rename from tests/pos/i20860.scala rename to tests/warn/i20860.scala index 1e1ddea11b75..b318d861fce0 100644 --- a/tests/pos/i20860.scala +++ b/tests/warn/i20860.scala @@ -1,3 +1,5 @@ +//> using options -Werror -Wunused:imports + def `i20860 use result to check selector bound`: Unit = import Ordering.Implicits.given Ordering[?] summon[Ordering[Seq[Int]]] diff --git a/tests/warn/i20951.scala b/tests/warn/i20951.scala new file mode 100644 index 000000000000..0ca82e1b8d66 --- /dev/null +++ b/tests/warn/i20951.scala @@ -0,0 +1,7 @@ +//> using options -Wunused:all +object Foo { + val dummy = 42 + def f(): Unit = Option(1).map((x: Int) => dummy) // warn + def g(): Unit = Option(1).map((x: Int) => ???) // warn + def main(args: Array[String]): Unit = {} +} diff --git a/tests/warn/i21420.scala b/tests/warn/i21420.scala index 0ee4aa3f28f6..65a288c7ae5b 100644 --- a/tests/warn/i21420.scala +++ b/tests/warn/i21420.scala @@ -1,4 +1,4 @@ -//> using options -Wunused:imports +//> using options -Werror -Wunused:imports object decisions4s{ trait HKD @@ -7,7 +7,7 @@ object decisions4s{ object DiagnosticsExample { import decisions4s.HKD - val _ = new HKD {} + case class Input[F[_]]() extends HKD import decisions4s.* - val _ = new DecisionTable {} + val decisionTable: DecisionTable = ??? } diff --git a/tests/warn/i21525.scala b/tests/warn/i21525.scala new file mode 100644 index 000000000000..aa156dc3960e --- /dev/null +++ b/tests/warn/i21525.scala @@ -0,0 +1,20 @@ +//> using options -Werror -Wunused:imports + +import scala.reflect.TypeTest + +trait A { + type B + type C <: B + + given instance: TypeTest[B, C] +} + +def f(a: A, b: a.B): Boolean = { + import a.C + b match { + case _: C => + true + case _ => + false + } +} diff --git a/tests/warn/i21809.scala b/tests/warn/i21809.scala new file mode 100644 index 000000000000..91e7a334b13b --- /dev/null +++ b/tests/warn/i21809.scala @@ -0,0 +1,17 @@ +//> using options -Wunused:imports + +package p { + package q { + import q.* // warn so long as we pass typer + class Test { + //override def toString = new C().toString + " for Test" + def d = D() + } + class D + } +} +package q { + class C { + override def toString = "q.C" + } +} diff --git a/tests/warn/i21860.unenum.scala b/tests/warn/i21860.unenum.scala index 7335e1b6851d..e4b282e35e76 100644 --- a/tests/warn/i21860.unenum.scala +++ b/tests/warn/i21860.unenum.scala @@ -3,7 +3,7 @@ sealed trait Corners { self: Figure => } sealed abstract class Shape extends Figure object Shape: - case object Triange extends Shape with Corners + case object Triangle extends Shape with Corners case object Square extends Shape with Corners case object Circle extends Shape case object Ellipsis extends Shape diff --git a/tests/warn/i21917.scala b/tests/warn/i21917.scala new file mode 100644 index 000000000000..cade4e90db3d --- /dev/null +++ b/tests/warn/i21917.scala @@ -0,0 +1,27 @@ +//> using options -Wunused:imports + +import Pet.Owner + +class Dog(owner: Owner) extends Pet(owner) { + import Pet.* // warn although unambiguous (i.e., it was disambiguated) + //import Car.* // ambiguous + + def bark(): String = "bite" + + def this(owner: Owner, goodDog: Boolean) = { + this(owner) + if (goodDog) println(s"$owner's dog is a good boy") + } + + val getOwner: Owner = owner +} + +class Pet(val owner: Owner) + +object Pet { + class Owner +} + +object Car { + class Owner +} diff --git a/tests/warn/i22233.scala b/tests/warn/i22233.scala new file mode 100644 index 000000000000..08caea1c25fb --- /dev/null +++ b/tests/warn/i22233.scala @@ -0,0 +1 @@ +extension (s: String) def length = 42 // warn diff --git a/tests/warn/i22371.scala b/tests/warn/i22371.scala new file mode 100644 index 000000000000..00f5b66695e0 --- /dev/null +++ b/tests/warn/i22371.scala @@ -0,0 +1,8 @@ +//> using options -Werror -Wunused:all +import scala.compiletime.deferred + +class Context + +trait Foo: + given context: Context = deferred + given () => Context = deferred diff --git a/tests/warn/i22440.check b/tests/warn/i22440.check new file mode 100644 index 000000000000..eaa357661a59 --- /dev/null +++ b/tests/warn/i22440.check @@ -0,0 +1,7 @@ +-- Warning: tests/warn/i22440.scala:4:12 ------------------------------------------------------------------------------- +4 |val _ = foo(1) // warn + | ^ + | Implicit parameters should be provided with a `using` clause. + | This code can be rewritten automatically under -rewrite -source 3.7-migration. + | To disable the warning, please use the following option: + | "-Wconf:msg=Implicit parameters should be provided with a `using` clause:s" diff --git a/tests/warn/i22440.scala b/tests/warn/i22440.scala new file mode 100644 index 000000000000..dfe88b5e5494 --- /dev/null +++ b/tests/warn/i22440.scala @@ -0,0 +1,4 @@ +//> using options -source 3.7 + +def foo(implicit x: Int) = x +val _ = foo(1) // warn diff --git a/tests/warn/i3323.scala b/tests/warn/i3323.scala new file mode 100644 index 000000000000..e409134ff0ba --- /dev/null +++ b/tests/warn/i3323.scala @@ -0,0 +1,8 @@ +//> using options -Werror +class Foo { + def foo[A](lss: List[List[A]]): Unit = { + lss match { + case xss: List[List[A]] => // no warn erasure + } + } +} diff --git a/tests/pos/patmat-exhaustive.scala b/tests/warn/patmat-exhaustive.scala similarity index 56% rename from tests/pos/patmat-exhaustive.scala rename to tests/warn/patmat-exhaustive.scala index 9e3cb7d8f615..a8f057664829 100644 --- a/tests/pos/patmat-exhaustive.scala +++ b/tests/warn/patmat-exhaustive.scala @@ -1,4 +1,4 @@ -//> using options -Xfatal-warnings -deprecation -feature +//> using options -Werror -deprecation -feature def foo: Unit = object O: @@ -8,5 +8,5 @@ def foo: Unit = val x: O.A = ??? x match - case x: B => ??? - case x: C => ??? + case _: B => ??? + case _: C => ??? diff --git a/tests/warn/scala2-t11681.scala b/tests/warn/scala2-t11681.scala index ae2187181ceb..5d752777f64c 100644 --- a/tests/warn/scala2-t11681.scala +++ b/tests/warn/scala2-t11681.scala @@ -23,7 +23,7 @@ trait BadAPI extends InterFace { a } override def call(a: Int, - b: String, // OK + b: String, // warn now c: Double): Int = { println(c) a @@ -33,7 +33,7 @@ trait BadAPI extends InterFace { override def equals(other: Any): Boolean = true // OK - def i(implicit s: String) = answer // ok + def i(implicit s: String) = answer // warn now /* def future(x: Int): Int = { @@ -59,10 +59,10 @@ class Revaluing(u: Int) { def f = u } // OK case class CaseyKasem(k: Int) // OK -case class CaseyAtTheBat(k: Int)(s: String) // ok +case class CaseyAtTheBat(k: Int)(s: String) // warn unused s trait Ignorance { - def f(readResolve: Int) = answer // ok + def f(readResolve: Int) = answer // warn now } class Reusing(u: Int) extends Unusing(u) // OK @@ -78,30 +78,30 @@ trait Unimplementation { trait DumbStuff { def f(implicit dummy: DummyImplicit) = answer // ok - def g(dummy: DummyImplicit) = answer // ok + def g(dummy: DummyImplicit) = answer // warn now } trait Proofs { def f[A, B](implicit ev: A =:= B) = answer // ok def g[A, B](implicit ev: A <:< B) = answer // ok - def f2[A, B](ev: A =:= B) = answer // ok - def g2[A, B](ev: A <:< B) = answer // ok + def f2[A, B](ev: A =:= B) = answer // warn now + def g2[A, B](ev: A <:< B) = answer // warn now } trait Anonymous { - def f = (i: Int) => answer // ok + def f = (i: Int) => answer // warn now def f1 = (_: Int) => answer // OK def f2: Int => Int = _ + 1 // OK - def g = for (i <- List(1)) yield answer // ok + def g = for (i <- List(1)) yield answer // no warn (that is a patvar) } trait Context[A] trait Implicits { - def f[A](implicit ctx: Context[A]) = answer // ok - def g[A: Context] = answer // OK + def f[A](implicit ctx: Context[A]) = answer // warn implicit param even though only marker + def g[A: Context] = answer // no warn bound that is marker only } -class Bound[A: Context] // OK +class Bound[A: Context] // no warn bound that is marker only object Answers { def answer: Int = 42 } diff --git a/tests/warn/t22507.scala b/tests/warn/t22507.scala new file mode 100644 index 000000000000..76ae9a2b9a8b --- /dev/null +++ b/tests/warn/t22507.scala @@ -0,0 +1,14 @@ + +//> using options -Werror -Wunused:privates + +case class BinaryFen(value: Array[Byte]) extends AnyVal: + + def read: Unit = + val reader = new Iterator[Byte]: + val inner = value.iterator + override inline def hasNext: Boolean = inner.hasNext // nowarn + override inline def next: Byte = if hasNext then inner.next else 0.toByte // nowarn + + if reader.hasNext then + val b: Byte = reader.next + println(b) diff --git a/tests/warn/tuple-exhaustivity.scala b/tests/warn/tuple-exhaustivity.scala new file mode 100644 index 000000000000..9060d112b197 --- /dev/null +++ b/tests/warn/tuple-exhaustivity.scala @@ -0,0 +1,6 @@ +//> using options -Werror -deprecation -feature + +def test(t: Tuple) = + t match + case Tuple() => + case h *: t => diff --git a/tests/warn/unused-can-equal.scala b/tests/warn/unused-can-equal.scala new file mode 100644 index 000000000000..6e38591ccef1 --- /dev/null +++ b/tests/warn/unused-can-equal.scala @@ -0,0 +1,16 @@ + +//> using options -Werror -Wunused:all + +import scala.language.strictEquality + +class Box[T](x: T) derives CanEqual: + def y = x + +def f[A, B](a: A, b: B)(using CanEqual[A, B]) = a == b // no warn + +def g = + import Box.given // no warn + "42".length + +@main def test() = println: + Box(1) == Box(1L) diff --git a/tests/warn/unused-locals.scala b/tests/warn/unused-locals.scala new file mode 100644 index 000000000000..10bf160fb717 --- /dev/null +++ b/tests/warn/unused-locals.scala @@ -0,0 +1,43 @@ +//> using options -Wunused:locals + +class Outer { + class Inner +} + +trait Locals { + def f0 = { + var x = 1 // warn + var y = 2 // no warn + y = 3 + y + y + } + def f1 = { + val a = new Outer // no warn + val b = new Outer // warn + new a.Inner + } + def f2 = { + var x = 100 // warn about it being a var + x + } +} + +object Types { + def l1() = { + object HiObject { def f = this } // warn + class Hi { // warn + def f1: Hi = new Hi + def f2(x: Hi) = x + } + class DingDongDoobie // warn + class Bippy // no warn + type Something = Bippy // no warn + type OtherThing = String // warn + (new Bippy): Something + } +} + +// breakage: local val x$1 in method skolemize is never used +case class SymbolKind(accurate: String, sanitized: String, abbreviation: String) { + def skolemize: SymbolKind = copy(accurate = s"$accurate skolem", abbreviation = s"$abbreviation#SKO") +} diff --git a/tests/warn/unused-params.scala b/tests/warn/unused-params.scala new file mode 100644 index 000000000000..5ef339c942ac --- /dev/null +++ b/tests/warn/unused-params.scala @@ -0,0 +1,159 @@ +//> using options -Wunused:params +// + +import Answers._ + +trait InterFace { + /** Call something. */ + def call(a: Int, b: String, c: Double): Int +} + +trait BadAPI extends InterFace { + def f(a: Int, + b: String, // warn + c: Double): Int = { + println(c) + a + } + @deprecated("no warn in deprecated API", since="yesterday") + def g(a: Int, + b: String, // no warn + c: Double): Int = { + println(c) + a + } + override def call(a: Int, + b: String, // warn + c: Double): Int = { + println(c) + a + } + + def meth(x: Int) = x + + override def equals(other: Any): Boolean = true // no warn + + def i(implicit s: String) = answer // warn + + /* + def future(x: Int): Int = { + val y = 42 + val x = y // maybe option to warn only if shadowed + x + } + */ +} + +// mustn't alter warnings in super +trait PoorClient extends BadAPI { + override def meth(x: Int) = ??? // no warn + override def f(a: Int, b: String, c: Double): Int = a + b.toInt + c.toInt +} + +class Unusing(u: Int) { // warn + def f = ??? +} + +class Valuing(val u: Int) // no warn + +class Revaluing(u: Int) { def f = u } // no warn + +case class CaseyKasem(k: Int) // no warn + +case class CaseyAtTheBat(k: Int)(s: String) // warn + +trait Ignorance { + def f(readResolve: Int) = answer // warn +} + +class Reusing(u: Int) extends Unusing(u) // no warn + +class Main { + def main(args: Array[String]): Unit = println("hello, args") // no warn +} + +trait Unimplementation { + def f(u: Int): Int = ??? // no warn for param in unimplementation +} + +trait DumbStuff { + def f(implicit dummy: DummyImplicit) = answer + def g(dummy: DummyImplicit) = answer // warn +} +trait Proofs { + def f[A, B](implicit ev: A =:= B) = answer + def g[A, B](implicit ev: A <:< B) = answer + def f2[A, B](ev: A =:= B) = answer // warn + def g2[A, B](ev: A <:< B) = answer // warn +} + +trait Anonymous { + def f = (i: Int) => answer // warn + + def f1 = (_: Int) => answer // no warn underscore parameter (a fresh name) + + def f2: Int => Int = _ + 1 // no warn placeholder syntax (a fresh name and synthetic parameter) + + def g = for (i <- List(1)) yield answer // no warn patvar elaborated as map.(i => 42) +} +trait Context[A] { def m(a: A): A = a } +trait Implicits { + def f[A](implicit ctx: Context[A]) = answer // warn + def g[A: Context] = answer // warn + def h[A](using Context[A]) = answer // warn +} +class Bound[A: Context] // warn +object Answers { + def answer: Int = 42 +} + +trait BadMix { self: InterFace => + def f(a: Int, + b: String, // warn + c: Double): Int = { + println(c) + a + } + @deprecated("no warn in deprecated API", since="yesterday") + def g(a: Int, + b: String, // no warn + c: Double): Int = { + println(c) + a + } + override def call(a: Int, + XXXX: String, // warn no longer excused because required by superclass + c: Double): Int = { + println(c) + a + } + + def meth(x: Int) = x + + override def equals(other: Any): Boolean = true // no warn + + def i(implicit s: String) = answer // warn +} + +class Unequal { + override def equals(other: Any) = toString.nonEmpty // warn +} + +class Seriously { + def f(s: Serializable) = toString.nonEmpty // warn explicit param of marker trait +} + +class TryStart(start: String) { + def FINALLY(end: END.type) = start // no warn for DSL taking a singleton +} + +object END + +object Optional: + extension (opt: Option.type) // no warn for extension of module + @annotation.experimental + inline def fromNullable[T](t: T | Null): Option[T] = Option(t).asInstanceOf[Option[T]] + +class Nested { + @annotation.unused private def actuallyNotUsed(fresh: Int, stale: Int) = fresh // no warn if owner is unused +} diff --git a/tests/warn/unused-privates.scala b/tests/warn/unused-privates.scala new file mode 100644 index 000000000000..8864bc16de2b --- /dev/null +++ b/tests/warn/unused-privates.scala @@ -0,0 +1,310 @@ +// +//> using options -deprecation -Wunused:privates,locals +// +class Bippy(a: Int, b: Int) { + private def this(c: Int) = this(c, c) // warn (DO warn, was NO) + private def bippy(x: Int): Int = bippy(x) // warn + private def boop(x: Int) = x+a+b // warn + final private val MILLIS1 = 2000 // warn, scala2: no warn, might have been inlined + final private val MILLIS2: Int = 1000 // warn + final private val HI_COMPANION: Int = 500 // no warn, accessed from companion + def hi() = Bippy.HI_INSTANCE +} +object Bippy { + def hi(x: Bippy) = x.HI_COMPANION + private val HI_INSTANCE: Int = 500 // no warn, accessed from instance + private val HEY_INSTANCE: Int = 1000 // warn + private lazy val BOOL: Boolean = true // warn +} + +class A(val msg: String) +class B1(msg: String) extends A(msg) +class B2(msg0: String) extends A(msg0) +class B3(msg0: String) extends A("msg") + +trait Accessors { + private var v1: Int = 0 // warn + private var v2: Int = 0 // warn, never set + private var v3: Int = 0 // warn, never got + private var v4: Int = 0 // no warn + + private var v5 = 0 // warn, never set + private var v6 = 0 // warn, never got + private var v7 = 0 // no warn + + def bippy(): Int = { + v3 = 3 + v4 = 4 + v6 = 6 + v7 = 7 + v2 + v4 + v5 + v7 + } +} + +class StableAccessors { + private var s1: Int = 0 // warn + private var s2: Int = 0 // warn, never set + private var s3: Int = 0 // warn, never got + private var s4: Int = 0 // no warn + + private var s5 = 0 // warn, never set + private var s6 = 0 // warn, never got + private var s7 = 0 // no warn + + def bippy(): Int = { + s3 = 3 + s4 = 4 + s6 = 6 + s7 = 7 + s2 + s4 + s5 + s7 + } +} + +trait DefaultArgs { + // DO warn about default getters for x2 and x3 + private def bippy(x1: Int, x2: Int = 10, x3: Int = 15): Int = x1 + x2 + x3 // warn // warn + + def boppy() = bippy(5, 100, 200) +} + +/* scala/bug#7707 Both usages warn default arg because using PrivateRyan.apply, not new. +case class PrivateRyan private (ryan: Int = 42) { def f = PrivateRyan() } +object PrivateRyan { def f = PrivateRyan() } +*/ + +class Outer { + class Inner +} + +trait Locals { + def f0 = { + var x = 1 // warn + var y = 2 + y = 3 + y + y + } + def f1 = { + val a = new Outer // no warn + val b = new Outer // warn + new a.Inner + } + def f2 = { + var x = 100 // warn about it being a var + x + } +} + +object Types { + private object Dongo { def f = this } // warn + private class Bar1 // warn + private class Bar2 // no warn + private type Alias1 = String // warn + private type Alias2 = String // no warn + def bippo = (new Bar2).toString + + def f(x: Alias2) = x.length + + def l1() = { + object HiObject { def f = this } // warn + class Hi { // warn + def f1: Hi = new Hi + def f2(x: Hi) = x + } + class DingDongDoobie // warn + class Bippy // no warn + type Something = Bippy // no warn + type OtherThing = String // warn + (new Bippy): Something + } +} + +trait Underwarn { + def f(): Seq[Int] + + def g() = { + val Seq(_, _) = f() // no warn + true + } +} + +class OtherNames { + private def x_=(i: Int): Unit = () // warn + private def x: Int = 42 // warn + private def y_=(i: Int): Unit = () // warn + private def y: Int = 42 + + def f = y +} + +case class C(a: Int, b: String, c: Option[String]) +case class D(a: Int) + +// patvars which used to warn as vals in older scala 2 +trait Boundings { + + def c = C(42, "hello", Some("world")) + def d = D(42) + + def f() = { + val C(x, y, Some(z)) = c: @unchecked // no warn + 17 + } + def g() = { + val C(x @ _, y @ _, Some(z @ _)) = c: @unchecked // no warn + 17 + } + def h() = { + val C(x @ _, y @ _, z @ Some(_)) = c: @unchecked // no warn for z? + 17 + } + + def v() = { + val D(x) = d // no warn + 17 + } + def w() = { + val D(x @ _) = d // no warn + 17 + } + +} + +trait Forever { + def f = { + val t = Option((17, 42)) + for { + ns <- t + (i, j) = ns // no warn + } yield (i + j) + } + def g = { + val t = Option((17, 42)) + for { + ns <- t + (i, j) = ns // no warn + } yield 42 // val emitted only if needed, hence nothing unused + } +} + +trait Ignorance { + private val readResolve = 42 // warn wrong signatured for special members +} + +trait CaseyKasem { + def f = 42 match { + case x if x < 25 => "no warn" + case y if toString.nonEmpty => "no warn" + y + case z => "warn" + } +} +trait CaseyAtTheBat { + def f = Option(42) match { + case Some(x) if x < 25 => "no warn" + case Some(y @ _) if toString.nonEmpty => "no warn" + case Some(z) => "warn" + case None => "no warn" + } +} + +class `not even using companion privates` + +object `not even using companion privates` { + private implicit class `for your eyes only`(i: Int) { // warn + def f = i + } +} + +class `no warn in patmat anonfun isDefinedAt` { + def f(pf: PartialFunction[String, Int]) = pf("42") + def g = f { + case s => s.length // no warn (used to warn case s => true in isDefinedAt) + } +} + +// this is the ordinary case, as AnyRef is an alias of Object +class `nonprivate alias is enclosing` { + class C + type C2 = C + private class D extends C2 // warn +} + +object `classof something` { + private class intrinsically + def f = classOf[intrinsically].toString() +} + +trait `scala 2 short comings` { + def f: Int = { + val x = 42 // warn + 17 + } +} + +class `issue 12600 ignore abstract types` { + type Abs +} + +class `t12992 enclosing def is unused` { + private val n = 42 + @annotation.unused def f() = n + 2 // unused code uses n +} + +class `recursive reference is not a usage` { + private def f(i: Int): Int = // warn + if (i <= 0) i + else f(i-1) + private class P { // warn + def f() = new P() + } +} + +class `absolve serial framework` extends Serializable: + import java.io.{IOException, ObjectInputStream, ObjectOutputStream, ObjectStreamException} + @throws(classOf[IOException]) + private def writeObject(stream: ObjectOutputStream): Unit = () + @throws(classOf[ObjectStreamException]) + private def writeReplace(): Object = ??? + @throws(classOf[ClassNotFoundException]) + @throws(classOf[IOException]) + private def readObject(stream: ObjectInputStream): Unit = () + @throws(classOf[ObjectStreamException]) + private def readObjectNoData(): Unit = () + @throws(classOf[ObjectStreamException]) + private def readResolve(): Object = ??? + +class `absolve ONLY serial framework`: + import java.io.{IOException, ObjectInputStream, ObjectOutputStream, ObjectStreamException} + @throws(classOf[IOException]) + private def writeObject(stream: ObjectOutputStream): Unit = () // warn + @throws(classOf[ObjectStreamException]) + private def writeReplace(): Object = new Object // warn + @throws(classOf[ClassNotFoundException]) + @throws(classOf[IOException]) + private def readObject(stream: ObjectInputStream): Unit = () // warn + @throws(classOf[ObjectStreamException]) + private def readObjectNoData(): Unit = () // warn + @throws(classOf[ObjectStreamException]) + private def readResolve(): Object = new Object // warn + +@throws(classOf[java.io.ObjectStreamException]) +private def readResolve(): Object = ??? // TODO warn +private def print() = println() // TODO warn +private val printed = false // TODO warn + +package locked: + private[locked] def locker(): Unit = () // TODO warn as we cannot distinguish unqualified private at top level + package basement: + private[locked] def shackle(): Unit = () // no warn as it is not top level at boundary + +object `i19998 refinement`: + trait Foo { + type X[a] + } + trait Bar[X[_]] { + private final type SelfX[a] = X[a] // was false positive + val foo: Foo { type X[a] = SelfX[a] } + } + +object `patvar is assignable`: + private var (i, j) = (42, 27) // no warn patvars under -Wunused:privates + println((i, j))