Skip to content

Commit

Permalink
Merge branch 'main' into wunused-disable-for-public-defs
Browse files Browse the repository at this point in the history
  • Loading branch information
szymon-rd authored Apr 13, 2023
2 parents 14dd04b + af8790c commit 8b4f389
Show file tree
Hide file tree
Showing 12 changed files with 128 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class CrossStageSafety extends TreeMapWithStages {
val healedType = healType(tree.srcPos)(tp1)
if healedType == tree.tpe then tree
else TypeTree(healedType).withSpan(tree.span)
case _: RefTree if tree.isType =>
case _: RefTree | _: SingletonTypeTree if tree.isType =>
val healedType = healType(tree.srcPos)(tree.tpe)
if healedType == tree.tpe then tree
else TypeTree(healedType).withSpan(tree.span)
Expand Down
50 changes: 35 additions & 15 deletions compiler/src/dotty/tools/dotc/transform/CheckUnused.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ 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


/**
Expand Down Expand Up @@ -109,6 +109,9 @@ class CheckUnused extends MiniPhase:
traverseAnnotations(tree.symbol)
if !tree.symbol.is(Module) then
ud.registerDef(tree)
if tree.name.mangledString.startsWith(nme.derived.mangledString + "$")
&& tree.typeOpt != NoType then
ud.registerUsed(tree.typeOpt.typeSymbol, None, true)
ud.addIgnoredUsage(tree.symbol)
}

Expand Down Expand Up @@ -308,7 +311,7 @@ object CheckUnused:
*
* See the `isAccessibleAsIdent` extension method below in the file
*/
private val usedInScope = MutStack(MutSet[(Symbol,Boolean, Option[Name])]())
private val usedInScope = MutStack(MutSet[(Symbol,Boolean, Option[Name], Boolean)]())
private val usedInPosition = MutSet[(SrcPos, Name)]()
/* unused import collected during traversal */
private val unusedImport = MutSet[ImportSelector]()
Expand Down Expand Up @@ -353,15 +356,16 @@ object CheckUnused:
* 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])(using Context): Unit =
def registerUsed(sym: Symbol, name: Option[Name], isDerived: Boolean = false)(using Context): Unit =
if !isConstructorOfSynth(sym) && !doNotRegister(sym) then
if sym.isConstructor && sym.exists then
registerUsed(sym.owner, None) // constructor are "implicitly" imported with the class
else
usedInScope.top += ((sym, sym.isAccessibleAsIdent, name))
usedInScope.top += ((sym.companionModule, sym.isAccessibleAsIdent, name))
usedInScope.top += ((sym.companionClass, sym.isAccessibleAsIdent, name))
name.map(n => usedInPosition += ((sym.sourcePos, n)))
usedInScope.top += ((sym, sym.isAccessibleAsIdent, name, isDerived))
usedInScope.top += ((sym.companionModule, sym.isAccessibleAsIdent, name, isDerived))
usedInScope.top += ((sym.companionClass, sym.isAccessibleAsIdent, name, isDerived))
if sym.sourcePos.exists then
name.map(n => usedInPosition += ((sym.sourcePos, n)))

/** Register a symbol that should be ignored */
def addIgnoredUsage(sym: Symbol)(using Context): Unit =
Expand Down Expand Up @@ -417,15 +421,15 @@ object CheckUnused:
// used symbol in this scope
val used = usedInScope.pop().toSet
// used imports in this scope
val imports = impInScope.pop().toSet
val imports = impInScope.pop()
val kept = used.filterNot { t =>
val (sym, isAccessible, optName) = t
val (sym, isAccessible, optName, isDerived) = t
// keep the symbol for outer scope, if it matches **no** import
// This is the first matching wildcard selector
var selWildCard: Option[ImportSelector] = None

val exists = imports.exists { imp =>
sym.isInImport(imp, isAccessible, optName) match
sym.isInImport(imp, isAccessible, optName, isDerived) match
case None => false
case optSel@Some(sel) if sel.isWildcard =>
if selWildCard.isEmpty then selWildCard = optSel
Expand Down Expand Up @@ -479,6 +483,7 @@ object CheckUnused:
if ctx.settings.WunusedHas.explicits then
explicitParamInScope
.filterNot(d => d.symbol.usedDefContains)
.filterNot(d => usedInPosition.exists { case (pos, name) => d.span.contains(pos.span) && name == d.symbol.name})
.filterNot(d => containsSyntheticSuffix(d.symbol))
.map(d => d.namePos -> WarnTypes.ExplicitParams).toList
else
Expand Down Expand Up @@ -596,16 +601,31 @@ object CheckUnused:
}

/** Given an import and accessibility, return an option of selector that match import<->symbol */
private def isInImport(imp: tpd.Import, isAccessible: Boolean, symName: Option[Name])(using Context): Option[ImportSelector] =
private def isInImport(imp: tpd.Import, isAccessible: Boolean, symName: Option[Name], isDerived: Boolean)(using Context): Option[ImportSelector] =
val tpd.Import(qual, sels) = imp
val qualHasSymbol = qual.tpe.member(sym.name).alternatives.map(_.symbol).contains(sym)
val dealiasedSym = dealias(sym)
val simpleSelections = qual.tpe.member(sym.name).alternatives
val typeSelections = sels.flatMap(n => qual.tpe.member(n.name.toTypeName).alternatives)
val termSelections = sels.flatMap(n => qual.tpe.member(n.name.toTermName).alternatives)
val selectionsToDealias = typeSelections ::: termSelections
val qualHasSymbol = simpleSelections.map(_.symbol).contains(sym) || (simpleSelections ::: selectionsToDealias).map(_.symbol).map(dealias).contains(dealiasedSym)
def selector = sels.find(sel => (sel.name.toTermName == sym.name || sel.name.toTypeName == sym.name) && symName.map(n => n.toTermName == sel.rename).getOrElse(true))
def dealiasedSelector = if(isDerived) sels.flatMap(sel => selectionsToDealias.map(m => (sel, m.symbol))).collect {
case (sel, sym) if dealias(sym) == dealiasedSym => sel
}.headOption else None
def wildcard = sels.find(sel => sel.isWildcard && ((sym.is(Given) == sel.isGiven) || sym.is(Implicit)))
if qualHasSymbol && !isAccessible && sym.exists then
selector.orElse(wildcard) // selector with name or wildcard (or given)
if qualHasSymbol && (!isAccessible || sym.isRenamedSymbol(symName)) && sym.exists then
selector.orElse(dealiasedSelector).orElse(wildcard) // selector with name or wildcard (or given)
else
None

private def isRenamedSymbol(symNameInScope: Option[Name])(using Context) =
sym.name != nme.NO_NAME && symNameInScope.exists(_.toSimpleName != sym.name.toSimpleName)

private def dealias(symbol: Symbol)(using Context): Symbol =
if(symbol.isType && symbol.asType.denot.isAliasType) then
symbol.asType.typeRef.dealias.typeSymbol
else symbol
/** Annotated with @unused */
private def isUnusedAnnot(using Context): Boolean =
sym.annotations.exists(a => a.symbol == ctx.definitions.UnusedAnnot)
Expand Down Expand Up @@ -660,7 +680,7 @@ object CheckUnused:

extension (memDef: tpd.MemberDef)
private def isValidMemberDef(using Context): Boolean =
!memDef.symbol.isUnusedAnnot && !memDef.symbol.isAllOf(Flags.AccessorCreationFlags) && !memDef.name.isWildcard
!memDef.symbol.isUnusedAnnot && !memDef.symbol.isAllOf(Flags.AccessorCreationFlags) && !memDef.name.isWildcard && !memDef.symbol.owner.is(Extension)

private def isValidParam(using Context): Boolean =
val sym = memDef.symbol
Expand Down
5 changes: 2 additions & 3 deletions docs/_docs/reference/changed-features/lazy-vals-init.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ title: Lazy Vals Initialization
nightlyOf: https://docs.scala-lang.org/scala3/reference/changed-features/lazy-vals-init.html
---

Scala 3 implements [Version 6](https://docs.scala-lang.org/sips/improved-lazy-val-initialization.html#version-6---no-synchronization-on-this-and-concurrent-initialization-of-fields)
of the [SIP-20] improved lazy vals initialization proposal.
Scala 3 implements Version 6 of the [SIP-20] improved lazy vals initialization proposal.

## Motivation

Expand Down Expand Up @@ -77,4 +76,4 @@ recursive lazy vals is undefined (initialization may result in a deadlock).

* [SIP-20]

[SIP-20]: https://docs.scala-lang.org/sips/improved-lazy-val-initialization.html
[SIP-20]: https://github.com/scala/improvement-proposals/pull/19
28 changes: 22 additions & 6 deletions docs/_docs/reference/metaprogramming/tasty-inspect.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,33 @@ title: "TASTy Inspection"
nightlyOf: https://docs.scala-lang.org/scala3/reference/metaprogramming/tasty-inspect.html
---

```scala
libraryDependencies += "org.scala-lang" %% "scala3-tasty-inspector" % scalaVersion.value
```

TASTy files contain the full typed tree of a class including source positions
and documentation. This is ideal for tools that analyze or extract semantic
information from the code. To avoid the hassle of working directly with the TASTy
information from the code.

To avoid the hassle of working directly with the TASTy
file we provide the `Inspector` which loads the contents and exposes it
through the TASTy reflect API.

## Inspecting TASTy files
We also showcase TASTyViz, a visualiser for tasty, useful for debugging and checking your understanding of TASTy

## TASTyViz

<!-- Keep synced with https://github.com/scala/docs.scala-lang/blob/main/scala3/guides/tasty-overview.md -->

TASTyViz is a tool to inspect TASTy files visually.
At the time of writing, it is still in the early stages of developement, therefore you can expect missing functionality and less-than-ideal user experience, but it could still prove useful when debugging.
You can check it out [here](https://github.com/shardulc/tastyviz).

## `Inspector`

`Inspector` is a tool which provides API access to TASTy.

You can add the depency to your sbt build like so:
```scala
libraryDependencies += "org.scala-lang" %% "scala3-tasty-inspector" % scalaVersion.value
```


To inspect the trees of a TASTy file a consumer can be defined in the following way.

Expand Down
2 changes: 1 addition & 1 deletion docs/_docs/reference/new-types/union-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ title: "Union Types"
nightlyOf: https://docs.scala-lang.org/scala3/reference/new-types/union-types.html
---

A union type `A | B` has as values all values of type `A` and also all values of type `B`.
A union type `A | B` includes all values of both types.


```scala
Expand Down
8 changes: 8 additions & 0 deletions scaladoc/resources/dotty_res/styles/theme/layout/header.css
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,14 @@
align-items: center;
}

.logo-container .project-logo {
max-width: 40px;
}

.logo-container .project-logo img {
max-width: 100%;
}

#mobile-menu-toggle {
display: none;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class Revaluing(u: Int) { def f = u } // OK

case class CaseyKasem(k: Int) // OK

case class CaseyAtTheBat(k: Int)(s: String) // error
case class CaseyAtTheBat(k: Int)(s: String) // ok

trait Ignorance {
def f(readResolve: Int) = answer // ok
Expand Down
9 changes: 8 additions & 1 deletion tests/neg-custom-args/fatal-warnings/i15503g.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,11 @@ object Foo {
/* --- Trivial method check --- */
private def g1(x: Int) = 1 // OK
private def g2(x: Int) = ??? // OK
}
}

package foo.test.i17101:
type Test[A] = A
extension[A] (x: Test[A]) { // OK
def value: A = x
def causesIssue: Unit = println("oh no")
}
43 changes: 41 additions & 2 deletions tests/neg-custom-args/fatal-warnings/i15503i.scala
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ package foo.test.possibleclasses:
k: Int, // OK
private val y: Int // OK /* Kept as it can be taken from pattern */
)(
s: Int, // error /* But not these */
s: Int,
val t: Int, // OK
private val z: Int // error
)
Expand Down Expand Up @@ -135,7 +135,7 @@ package foo.test.possibleclasses.withvar:
k: Int, // OK
private var y: Int // OK /* Kept as it can be taken from pattern */
)(
s: Int, // error /* But not these */
s: Int,
var t: Int, // OK
private var z: Int // error
)
Expand Down Expand Up @@ -277,3 +277,42 @@ package foo.test.i16679b:
import Foo.x
case class CoolClass(i: Int)
println(summon[myPackage.CaseClassName[CoolClass]])

package foo.test.i17156:
package a:
trait Foo[A]
object Foo:
inline def derived[T]: Foo[T] = new Foo{}

package b:
import a.Foo
type Xd[A] = Foo[A]

package c:
import b.Xd
trait Z derives Xd


package foo.test.i17175:
val continue = true
def foo =
for {
i <- 1.until(10) // OK
if continue
} {
println(i)
}

package foo.test.i17117:
package example {
object test1 {
val test = "test"
}

object test2 {

import example.test1 as t1

val test = t1.test
}
}
5 changes: 5 additions & 0 deletions tests/neg-macros/i8887.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import scala.quoted._

def expr[X](x: Any)(using Quotes): Expr[Any] =
'{ foo[x.type] } // error
def foo[X]: Any = ???
4 changes: 1 addition & 3 deletions tests/neg-macros/quote-this-a.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ class Foo {

def f(using Quotes): Unit = '{
def bar[T](x: T): T = x
bar[
this.type // error
] {
bar[this.type] {
this // error
}
}
Expand Down
3 changes: 3 additions & 0 deletions tests/pos-macros/i8887.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import scala.quoted._
inline def foo(x: Any): Any = ${ expr[x.type] }
def expr[X](using Quotes): Expr[Any] = ???

0 comments on commit 8b4f389

Please sign in to comment.