-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
In selector check, prefix of reference must match import qualifier #20894
base: main
Are you sure you want to change the base?
Conversation
Further tweaks forthcoming. |
@som-snytt how much work do you think it still requires? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The first commit is a bit suspicious, and might be the cause of the CI failures.
The second commit looks good and might pass more easily if submitted as an independent PR.
I'll submit commit 2 separately, as I was about to do except it's one line. I'll follow up the fix for commit 1 after the American holiday. |
634fdb0
to
741473e
Compare
The previous test fails were because the new prefix check (in I'll clean up and also look for improvements. |
aa1526f
to
b6c0fb6
Compare
09d418a
to
75d4d24
Compare
75d4d24
to
0f4be8a
Compare
Rebased and split out commits for easier review. This includes adding the prefix type to This is the July commit and not the August rewrite, which fixes scope bugs by leveraging normal miniphase traversal and avoiding the special traverser; which allows putting Example extra test fixed in August:
As the comment reminds me, it incorrectly detects the superconstructor ref as resolved by the nested import. Also note that the inner wildcard is not ambiguous because both imports resolve to the same symbol. But a further improvement would be to warn that the inner import is, if not unused, then spurious. Edit: redrafting to add the August rewrites. |
Cherry-picked some old commits. As a reminder to future self, the mega phase was broken because CheckShadowing sees the nested X as shadowing.
The fix [sic] is to ignore everything nested because it ignores constructor-owned X and M. [Previous attempt had been to short-circuit transformDeep so CheckShadowing doesn't see subtrees of "other" trees.] The fix [also sic] to superclass context noted in previous comment is also novel: since all the "context" approximation is for the purpose of import tracking, it doesn't need to capture all the subtlety of a true super class constructor context (with class parameters in scope). Instead, just detect that a reference occurred in a parent tree, and then kick it upstairs in popScope. The mechanism for tracking The "inner traverser" is removed in favor of matching "other" trees and transforming their parts. I see there was some long discussion about this. |
|
That is Thanksgiving Day shortly after the national election, of course. |
Don't hesitate to ping me when you would like me to take another look. I noticed you were still actively making changes, so I didn't pay attention to every push. |
eb1bf06
to
e8ca0cc
Compare
I see I was in the middle of adding commits a month ago and did not implement the speculation from my previous comment. My brain glazes over when I look at this PR. Also I have to re-read "getting started as a contributor" to understand "testing your changes". But I see this PR includes showing vulpix results more nicely. To reduce the heuristics which are dubious or less-strongly motivated, this reverts #16865 and warns in overrides. The original motivation in Scala 2 was that an override is constrained in its signature and shouldn't be required to use every parameter. Maybe that dates from before This keeps the "trivial method" heuristic: don't warn on
however, it ought to be possible to audit suppressed warnings (of all kinds and mechanisms). |
9fac2ae
to
e7422fa
Compare
dbd7c5b
to
fb6fa47
Compare
squashed because couldn't be bothered to rebase the conflict. I saved a branch in case. Still need to fix the unpickle test failure. Then I will book the progress to avoid further conflicts. More accommodations are needed for compiletime. |
e85b80f
to
5df9e7d
Compare
This breaks the unpickler test, or rather this is what unbreaks it:
I don't have bandwidth to investigate why. |
5df9e7d
to
f77e46a
Compare
@sjrd This is "ready for review." I've addressed all your concerns from July 3. PR is squashed, but previous work is mostly of archaeological interest.
It walks
It attempts to track nested There are various tiresome mechanisms which are expected to just work without tweaking options. Another principle is to minimize surface area with other components; this is restricted to a few attachments. A few minor clean-ups remain, including ensuring that There are 100 LOC for deleting unused imports which should be moved to a cupboard, as it is supplementary, to be brought out on special occasions; it's a port from Scala 2. |
f77e46a
to
470db35
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suspicious change of permissions on this file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I noticed it: I had touched then reverted the file but haven't fixed this permissions glitch yet. I'd like to figure out what lapse of gitfu caused it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shell history says that instead of git reset, I grabbed the raw file from github. Aha! That was in a period of rolling back unnecessary & distracting tweaks for this PR.
tests/warn/i15503f.scala
Outdated
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) // make Type param unused after typer |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this a TODO?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's the same as the previous case, but the parameter using Type
is unused, but does not warn under -Wunused:implicits
. Now I'm not sure why. It's not named and Type
has no declared members.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Apparently something to do with quote pickling, not printed by -Vprint. Is there a tool for showing trees that I don't know about? How can I reason about data structures that are invisible?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To follow up, the TODO was that I wanted to show the implicit arg unused, but didn't know enough about quoting to do it; in addition, I lacked tooling just to show me the Ident
tree in question.
tests/warn/i15503kb/power.scala
Outdated
@@ -0,0 +1,15 @@ | |||
|
|||
object Power: | |||
import scala.concurrent.* // warn [taps mic] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does "[taps mic]" mean?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make sure it's warning. Inside these tests, if it's testing that elements do not warn, then you'd never know if warnings were turned off (for whatever feature). In retrospect, I'm leaning toward preferring a fixed corpus which is run under various settings: then all the code is tried under all the options, in some combination. (The fixed corpus need not be one huge file, but a test group.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've begun (recently) to add -Werror
in tests/warn to signal that no warnings are expected. That assists clarity. If there is a warning, though, you don't get a nice vulpix explanation.
) || ( | ||
sel.isWildcard && sel.isGiven | ||
&& imp.expr.tpe.allMembers.exists(_.symbol.isCanEqual) | ||
|| imp.expr.tpe.member(sel.name.toTermName).hasAltWith(_.symbol.isCanEqual) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm very confused about the relative precedence of &&
and ||
in this method. They seem fishy. Consider adding relevant parentheses and/or indentation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd also like to delete that whole thing: I don't think strict no implicit warn
is useful if the check is working correctly (as it ought to be), and also I'm not sure CanEqual
needs special treatment any more.CanEqual
under strictEquality
remains problematic. There is no residuum to indicate how it was resolved.
(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}` */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/** Is it the first import clause in a statement? `a.x` in `import a.x, b,{y, z}` */ | |
/** Is it the first import clause in a statement? `a.x` in `import a.x, b.{y, z}` */ |
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
!
loopOnNormalizedPrefixes(tree.typeOpt.normalizedPrefix, depth = 0) | ||
ud.registerUsed(tree.symbol, Some(tree.name)) | ||
} | ||
// resolve if inlined at the position of the call, or is zero extent summon |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment does not seem to account for the first condition: refInfos.inlined.isEmpty
. That's getting me confused.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is the normal case, the tree is not an inline expansion. But now I think most Scala 3 code is inlined. Also this reverse-engineering from the synthetic position was just an observation; I'm not sure this is robust.
resolveUsage(tree.tpe.classSymbol, tree.name, tree.tpe.importPrefix.skipPackageObject) | ||
tree | ||
|
||
// import x.y; x may be rewritten x.y, also import x.z as y |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"x may be rewritten x.y" looks weird? What does that mean?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that is https://github.com/scala/scala3/blob/3.6.3/compiler/src/dotty/tools/dotc/typer/Typer.scala#L586 except the comment should say y is rewritten.
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) => // if tycon.typeSymbol == defn.TypeTestClass |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Leftover comment?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
documentary? It has already checked for TypeTest.unapply, so the extractor should be a TypeTest[A, B], where the user wrote case _: B
.
Some combination of redundant code and comments should help me remember later what I was working out, for some definition of "working memory".
case _ if tree.isType => | ||
//println(s"OTHER TYPE ${tree.getClass} ${tree.show}") | ||
case _ => | ||
//println(s"OTHER ${tree.getClass} ${tree.show}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clean up?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
putting them back under a line comment was the clean-up!
|
||
type MessageInfo = (UnusedSymbol, SrcPos, String) // string is origin or empty | ||
|
||
def warnings(using Context): Array[MessageInfo] = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't looked at this method. It seems daunting. Is there any way to make it less dense / more approachable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I'll undertake that in a separate commit. Probably just a method for each case (params, privates, imports etc). I'll also move the "rewrite" logic out of the way. I did verify that the rewrite correctly edits the imports in the repo.
Prefer context functions for brevity. Avoid intermediate collections. Check scope type Use type test of prefix for usages in scope Style tweaks in CheckUnused Attachment tracks derivation No warn serialization methods Assume tpd Refactor traverser to miniphase callbacks Avoid tracking red herrings Mono Mega Phase Consider superclass context Filter member of refinement, handle Annotated TypeTree is usage of simple name Handle quotes and splices Tighten allowance for serialization methods Restore inferred type is not a usage, noprefix is in import Warn for top level private Import given takes NoPrefix usage Don't ignore params of public methods Accept updated semanticdb output Show misplaced warn comment, unfulfilled expectations Rewrite Restore functionality Patvars Absolution of canonical names defn.LanguageFeatureMetaAnnot tweak conditions for warning Handle match types No transparent inline exclusion Rewrite imports, supply origin No warn inline proxy Original of literal Typos in build Turn off boolean setting Use canonical names Tweak param test and test for import precedence Do resolve imports post inlining Excuse only empty interfaces as unused bound Adjust duplicate test, don't forgive anonfun Detect summon inline Restore previous CheckShadowing with tweaks Use result of TypeTest Maybe just ignore in inline defs Avoid warnings Different coping mechanism No warn only toplevel imports, absolve more patvars by selector Update semanticdb metac.expect Empty block is trivial Warn unassigned mutable patvars Tweak warning
470db35
to
add05d0
Compare
@sjrd I appreciate the review. I have addressed the tweaks and also a first refactor of the The cache logic was flawed: now it caches the I will add commits for further accommodation of You may wish to wait for more tests, or review at your leisure. I'll ping you again if I've exhausted my resources. |
308e3e9
to
20ccfe3
Compare
I commented that
|
This PR changes the
CheckUnused
phase to rely on theMiniPhase
API (instead of custom traversal). That improves fidelity toContext
(instead of approximate scoping).The phase should work seamlessly with subsequent linting phases (currently,
CheckShadowed
).It is a goal of the PR to eliminate false reports. It is also a goal not to regress previous work on efficiency.
A remaining limitation of the current approach is that contexts don't provide a nesting level. Practically, this means that for a wildcard import nested below a higher precedence named import, the wildcard is deemed "unused". (A more general tool for "managing" or "formatting" imports could do more to pick a preferred scope.)
This PR adds
-Wunused:patvars
, as forward-ported from Scala 2: it relies on attachments for some details about desugaring, but otherwise uses positions (where only the original patvar has a non-synthetic position).As in Scala 2, it does not warn about patvars with the "canonical" name of a case class element (this is complicated by type tests and the quotes API); other exclusions are to be ported, such as "name derived from the match selector".
Support is added for
-Wconf:origin=full.path.selector
, as in Scala 2. That allows, for example:to exclude certain blessed imports from warnings, or to work around false positives (should they arise).
Support is added to
-rewrite
unused imports. There are no options to "format"; instead, textual deletions preserve existing formatting, except that blank lines are removed and braces removed when there is only one selector.Notable fixes are to support
compiletime
andinline
; there are more fixes to pursue in this area.The commits are not organized around these changes;
commits are preserved here just for comparison to previous art, so that useful existing behaviors do not regress.Fixes #19657
Fixes #20520
Fixes #19998
Fixes #18313
Fixes #17371
Fixes #18708
Fixes #21917
Fixes #21420
Fixes #20951
Fixes #19252
Fixes #18289
Fixes #17667
Fixes #17252
Fixes #21807
Fixes #17753
Fixes #17318
Fixes #18564
Fixes #22376
Fixes #21525