Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Phase assembly is circumspect about loose constraints #10856

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

Draft
wants to merge 1 commit into
base: 2.13.x
Choose a base branch
from

Conversation

som-snytt
Copy link
Contributor

@som-snytt som-snytt commented Sep 3, 2024

-Werror on bad phase config. Include core phase names when checking for typos in constraints.

Previous description that pertains to commits merged previously:

Don't issue "phase" warnings under Scaladoc, to minimize build noise. There is little peril in not warning, since errors in plugin configuration are likely to be seen already when compiling.

The motivating use case is that a typo in a "runsBefore pickler" constraint for Scala-js was corrected but warned anyway in Scaladoc, which has no pickler phase.

Previously to this follow-up PR, "better monadic for" would warn for "runsBefore patmat". This PR reverts the fix of adding mock components to Scaladoc just for these constraints. Instead, Scaladoc does not warn by default if a constraint does not name a component. (It will warn under -Vdebug.)

This is deemed useful and benign for the use cases described: a plugin "runsBefore" a compiler phase that does not exist in Scaladoc. The compiler warns and offers spelling advice.

Both tools warn if a plugin component is not installed because it is not "reachable" in the graph of phase constraints.

Note that is feasible for a component to have no "runsAfter" constraint, if another component "runsBefore" it.

The net difference with 2.13.14 is that the compiler will never silently omit a plugin phase. This PR follows 2.13.14 in being more circumspect about bad "runsBefore" constraints, but also warns about typos.

Follows up commits merged for scala/bug#13028

@scala-jenkins scala-jenkins added this to the 2.13.16 milestone Sep 3, 2024
@som-snytt som-snytt changed the title Tweak/scaladoc phases Ensure phase has a valid constraint Sep 3, 2024
@som-snytt som-snytt force-pushed the tweak/scaladoc-phases branch from a42d995 to ecdf2dd Compare September 3, 2024 17:52
@som-snytt som-snytt marked this pull request as ready for review September 3, 2024 17:52
@som-snytt som-snytt force-pushed the tweak/scaladoc-phases branch from ecdf2dd to a6b1d7d Compare September 3, 2024 18:00
@som-snytt som-snytt requested a review from lrytz September 3, 2024 18:01
@som-snytt som-snytt modified the milestones: 2.13.16, 2.13.15 Sep 3, 2024
@SethTisue SethTisue added the prio:blocker release blocker (used only by core team, only near release time) label Sep 4, 2024
@SethTisue SethTisue requested a review from sjrd September 4, 2024 07:26
log(s"newSettings: allArgs = $allArgs")
val (success, residual) = s.processArguments(allArgs, processAll = false)
assert(success && residual.isEmpty, s"Bad settings [${args.mkString(",")}], residual [${residual.mkString(",")}]")
}
// scaladoc has custom settings
def newSettings(): Settings = new Settings
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Call this newSettingsBase() or something like that? With the same name as the overload I would expect this method to be an alias for newSettings(args) with some default args; not a base method that newSettings(args) must call to create its instance of Settings.

@som-snytt som-snytt force-pushed the tweak/scaladoc-phases branch 2 times, most recently from 3b8ec27 to 53b6723 Compare September 5, 2024 20:07
@som-snytt
Copy link
Contributor Author

som-snytt commented Sep 5, 2024

I'll take another look when my eyes are fresh, but some notes:

  • I didn't fail outright on bad or missing "after", because "before erasure" seems fine in general (but then it should just say "after parser" explicitly)
  • I was wondering why adding "after parser" to everything does not work: the distances work out the same, but the code for sorting at the same "level" checks for "incoming edge from previous level" (instead of "which phases at this level are runsRightAfter"), the anomaly was plugin between packageobjects and typer
  • as lrytz noted, the existing "just make it follow parser" resulted in sorting by name of the "anchors" (namer is anchor of namer -> packageobjects -> typer)
  • validate now checks reachable, which is not guaranteed by "after" given cycles; for edge cases "before parser" or cycles, this warns but should fail
  • I tired of uncommenting the local debugging code
  • the unit test uses a large phase set, so linear search of names is slow

@som-snytt som-snytt force-pushed the tweak/scaladoc-phases branch 3 times, most recently from c0e8f7a to 335e949 Compare September 5, 2024 21:52
@som-snytt
Copy link
Contributor Author

appreciating the blue notation for updating a check file.

image

@som-snytt
Copy link
Contributor Author

         typer   4  the meat and potatoes: type the trees
            C1   0  C1 tests phase assembly
            C2   0  C2 tests phase assembly
superaccessors   7  add super accessors in traits and nested classes

but -Vprint:5 works

@som-snytt
Copy link
Contributor Author

som-snytt commented Sep 8, 2024

crashes 2.13.14 creating the dot file

    override val runsRightAfter = None
    override val runsAfter = List("typer")
    override val runsBefore = List("typer")

Realized I don't need N plugins but one plugin with N components to test the permutations.

In Knuth, "boolean basics" says here's a table of operations, there are 16 of them. I was like what? so in addition to not assuming I know anything, I assume I know less than I thought I did.

Edit: well, this PR also throws for reporting.

error: scala.reflect.internal.FatalError: Phases form a cycle: typer -> superaccessors -> extmethods -> pickler -> refchecks -> patmat -> C2 -> typer

for

    override val runsRightAfter = Option("patmat")
    override val runsAfter = List("typer")
    override val runsBefore = List("typer")

where I think we don't want to ignore a before constraint that correctly names a phase but the constraint can't be satisfied. (The original ticket 8755 mentions this behavior of runsRightAfter.)

Naturally it also detects the other case

error: scala.reflect.internal.FatalError: Phases form a cycle: typer -> C4 -> typer

I think I already knew 2.13.14 scalac fails on these obvious constraints.

    override val runsRightAfter = None
    override val runsAfter = List("typer")
    override val runsBefore = List("erasure")

error: scala.reflect.internal.FatalError: Phase erasure can't follow explicitouter, created phase-order.dot

So we don't want to remain bug-compatible, but merely convenience-compatible for Scaladoc.

Worth adding that on linked ticket 7905 ten years ago, I'd already noticed that generating the phase graph file throws.

The OG also complains that before constraint is not sufficient. This PR accepts C7 with no after constraint (only before terminal) because it is reachable through C8. I'm about to change that and preserve that one bit of buggy behavior, because Lukas said to, and because it is easy to describe: a runs-after constraint is required. TODO that's incorrect, 2.13.14 has the same behavior; scaladoc says warning: dropping dependency on node with no phase object: patmat but does not drop the phase (which runs). Maybe I misunderstood Lukas's comment, as I haven't tried it out yet.

         typer   4  the meat and potatoes: type the trees
            C1   0  C1 tests phase assembly
            C8   0  C8 is before C7 which specifies no after
superaccessors   7  add super accessors in traits and nested classes
            C7   0  C7 tests phase assembly if only a before constraint
    extmethods   9  add extension methods for inline classes

@SethTisue
Copy link
Member

I feel bad even suggesting this, but perhaps it would be better to revert the original PR entirely, in order not to be under time pressure, then try to land a revised version in 2.13.16.

@som-snytt
Copy link
Contributor Author

som-snytt commented Sep 8, 2024

@SethTisue I see your point, but it's just shaking out a few edge cases. That's not going to change by waiting.

I'll add the comprehensive test for review of all possible behaviors.

However, it does belie the current practice that "it doesn't matter if we merge PRs because nothing will get tested until the week before the release." "Cramming" is what makes Scala a so-called "academic language", I just realized.

@som-snytt
Copy link
Contributor Author

I am the last to understand that scala-js exposes different components for scaladoc:

    if (global.isInstanceOf[doc.ScaladocGlobal]) {
      List[NscPluginComponent](PrepInteropComponent)
    } else {
      List[NscPluginComponent](PreTyperComponentComponent, PrepInteropComponent,
          ExplicitInnerJSComponent, ExplicitLocalJSComponent, GenCodeComponent)
    }

so there is no mysterious convenience in trimming them. I haven't refreshed my memory about backend phases yet.

@SethTisue
Copy link
Member

SethTisue commented Sep 9, 2024

It's a good counterargument to observe that the only additional plugins, not in the community build, that are likely to get tested with this, and only at release time, are Scala.js, Scala Native, and my own Fortify plugin.

@som-snytt
Copy link
Contributor Author

som-snytt commented Sep 9, 2024

I'm just going to finish Jane the Virgin before I push another commit, but I realize Scala 2 is basically EOL so you have your priorities, it's really no skin off my nose. Edit: that was followed by a Charmed marathon.

@sjrd
Copy link
Member

sjrd commented Sep 9, 2024

I am the last to understand that scala-js exposes different components for scaladoc:

Huh. I had completely forgotten about that. Sorry for leading the instigation in the wrong direction. 🙁

@som-snytt
Copy link
Contributor Author

Celebrating 11 yrs since scala-js plugin.

addConstraint(p.phaseName, before, Follows)
constrained = true
}
// if it doesn't follow a phase in the run, replace this component with a no-op that follows start
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there are two phases where p2 runsAfter p1 and p1 runsAfter typo, then only p1 is replaced by a noop.

Here's another approach, I'll clean it up and adjust / add tests: https://github.com/scala/scala/compare/2.13.x...lrytz:scala:pr10856?expand=1

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed the noop to a list of names to filter out.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that a change that you didn't push yet?

@lrytz
Copy link
Member

lrytz commented Sep 9, 2024

-Werror doesn't work because the warnings are issued early, then Run.compileUnits resets the reporter (and its warning count)

@som-snytt som-snytt force-pushed the tweak/scaladoc-phases branch from 78d1dc5 to 1f5df3f Compare September 10, 2024 03:47
@som-snytt som-snytt changed the title Ensure phase has a valid constraint Phase assembly is circumspect about loose constraints Sep 10, 2024
@som-snytt som-snytt force-pushed the tweak/scaladoc-phases branch 2 times, most recently from 66c8adc to 40b45a6 Compare September 10, 2024 06:35
@som-snytt
Copy link
Contributor Author

Threw in a -Werror check, similar to the check at compileSources. "Something went wrong so summarize now."

I did not yet add the test for "two components are dropped".

I intended to comment in the minor refactor in PhaseAssembly.traverse that filtering for distance >= 0 eliminates the unreachable (dropped) component(s).

I could polish more, as there are a few spots where I can't see my face.

private object TestComponent extends PluginComponent {
val global: Ploogin.this.global.type = Ploogin.this.global
override val runsBefore = List("jvm")
val runsAfter = List("typer", "refchecks")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, #10856 drops the phase because one of the runsAfter constraints is invalid. I think either way is fine here.

2.13.14 scaladoc drops the "refchecks" constraint and runs the phase after "typer".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I should have documented it: if runsAfter means "depends on", then all those phases are required, and not "at least one".

@lrytz
Copy link
Member

lrytz commented Sep 10, 2024

@som-snytt in the interest of moving 2.13.15 ahead I'd like to focus on what we need for that, so I submitted a minimal diff (#10858). Can we go ahead with this for now?

The remaining changes from my PR to this one: https://github.com/lrytz/scala/compare/pr10856light...lrytz:scala:pr10856?expand=1

What we can address after 2.13.15

  • detect duplicate phase names. weirdly, SubComponent overrides equals to compare the phase name, so phases with the same name don't end up in Global.phasesSet
  • -Werror for phase assembly warnings

Can you summarize what else is in the remaining diff? Performance improvements, debug logging, code cleanups.

@som-snytt
Copy link
Contributor Author

som-snytt commented Sep 10, 2024

On duplicate names, I mention somewhere that Scala 3 uses a fixed "spine" of phases which supports name duplication. One could explore how options should work that rely on names. Scala 3 options don't use phase id.

Also phase replacement was potentially a feature.

@som-snytt som-snytt removed the prio:blocker release blocker (used only by core team, only near release time) label Sep 11, 2024
@som-snytt som-snytt modified the milestones: 2.13.15, 2.13.16 Sep 11, 2024
@som-snytt som-snytt force-pushed the tweak/scaladoc-phases branch 2 times, most recently from 14c42e4 to 84d4940 Compare November 3, 2024 02:49
@SethTisue SethTisue marked this pull request as draft November 3, 2024 11:41
@som-snytt som-snytt marked this pull request as ready for review November 6, 2024 22:24
@som-snytt som-snytt requested a review from lrytz November 6, 2024 22:32
val help = if (knownNames.contains(name)) "" else
knownNames.filter(util.EditDistance.levenshtein(name, _) < 3).toList match {
case Nil => ""
case close => s" - did you mean ${util.StringUtil.oxford(close, "or")}?"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mayte this doesn't carry its weight? can we do without?

addConstraint(p.phaseName, end.phaseName, Follows)
}
if (warnAll)
dropped.foreach(p => warnings.addOne(s"Component ${p} dropped due to bad constraint"))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this warning seems redundant, at least in the check files

val purgedConstraints = constraints.filterInPlace {
case (from, to, w) => !dropped.contains(from) && !dropped.contains(to)
}.toList
new DependencyGraph(start.phaseName, phases.iterator.map(p => p.phaseName -> p).toMap).tap { graph =>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's the advantage of the new approach of collecting constraints before creating the DependencyGraph?

@som-snytt som-snytt force-pushed the tweak/scaladoc-phases branch from 84d4940 to a36cd77 Compare November 18, 2024 08:24
@SethTisue SethTisue marked this pull request as draft December 12, 2024 18:08
@SethTisue SethTisue modified the milestones: 2.13.16, 2.13.17 Dec 12, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants