Contract eta-expanded type constructors - #147
Merged
Merged
Conversation
rochala
force-pushed
the
fix/eta-expanded-lambdas
branch
from
August 23, 2026 14:47
50b10c9 to
fbf63d7
Compare
Passing `C` where `F[_]` is expected makes the compiler store the eta-expansion `[X] =>> C[X]` in TASTy, and the printer rendered it literally. That made the Scala 3 output worse than the Scala 2 output for the very same class, and inconsistent within a single line — `List` plain, the other constructor expanded: class NonEmptyList[+A] extends NonEmptyCollection[A, List, [A] =>> NonEmptyList[A]] ... class NonEmptyList[+A] extends NonEmptyCollection[A, List, NonEmptyList] ... // now Found by diffing the 2.13 stdlib read from pickles against the same library recompiled to TASTy: 51 of the 132 differing lines were this, the largest category, and every one favoured the Scala 2 rendering. Contracted only in argument position, and only for a lambda that passes its parameters straight through, in order, with no bounds of their own. The position restriction matters. A hand-written `type F = [A] =>> C[A]` is a different declaration from `type F = C` — its parameter is invariant where C's may not be — and nothing here can tell the two apart: they are structurally identical, and the parameter variance that separates them is package-private in tasty-query. Contracting everywhere would print two distinct sources identically. In argument position the expansion is the compiler's doing; as the right-hand side of an alias it is the author's, so that is left alone. Both directions are pinned by tests. `[A <: AnyRef] =>> F[A]` and `[A] =>> F[G[A]]` keep their rendering, as two pre-existing tests require. Verified against cats-core_3: cats.Applicative still shows its 11 composition lambdas. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
rochala
force-pushed
the
fix/eta-expanded-lambdas
branch
from
August 23, 2026 15:40
fbf63d7 to
75a18a0
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
First of the output-alignment fixes from the Scala 2 / Scala 3 stdlib diff. Independent of #146.
The bug
TASTy stores a type constructor passed where
F[_]is expected as the eta-expansion[A] =>> C[A], and the printer rendered it literally. So the Scala 3 output was worse than the Scala 2 output for the very same class — and inconsistent within a single line, withListplain and the other constructor expanded:This was 51 of the 132 differing lines in the stdlib comparison — the largest category, and every one favoured the Scala 2 rendering.
Scope of the contraction
Only a genuine eta-expansion: every parameter passed straight through, in order, with no bounds of its own. Two things deliberately keep their current rendering, and two pre-existing tests pin them:
[A <: AnyRef] =>> F[A]— the bound says somethingFdoes not.[A] =>> F[G[A]]— a composition, not a plain constructor.Verified against a real artifact:
cats.Applicativestill shows all 11 of its[α] =>> F[G[α]]composition lambdas.Verification
cats-core_3:2.10.0:NonEmptyListnow renders identically to its Scala 2 counterpart,=>>count 0;Applicativeunchanged at 11.lib.test372/372.CellarSelfBox) reproduces the case, and the test was confirmed to fail without the fix:eta-expansion not contracted: class CellarSelfBox[A] extends CellarBox[[A] =>> CellarSelfBox[A]].Remaining alignment items (verified, not in this PR)
scala.jdk.CollectionConvertersshows 20class *HasAs*wrappers but 0 of thedefs that attach them —PublicApiFilter.isSyntheticSymdrops them.scala.Charemitsval MaxValue:with a raw U+FFFF andval MinValue:with a raw NUL into the Markdown.Two categories from the diff turned out not to be cellar bugs: the
$default$Naccessors don't reproduce outside the experimental stdlib build (0 occurrences on cats-core_3, cats-effect_3, pureconfig), andtoString()vstoStringreflects genuinely different declarations in the two encodings.🤖 Generated with Claude Code