Skip to content

Commit b1aa385

Browse files
authored
Try and stabilize Reflect.scala sortInPlace (#4287)
Tries to fix the instability in https://github.com/com-lihaoyi/mill/actions/runs/12719802399/job/35460719291?pr=4272 It seems `sortInPlaceWith` uses `Ordering.fromLessThan`, which means it must return `false` when both sides are equal. The previous implementation returned `true` when both `getDeclaringClass` and `getReturnType` were equal, which violates that invariant and could possible cause that crash depending on how the sort executes
1 parent e027188 commit b1aa385

File tree

2 files changed

+31
-13
lines changed

2 files changed

+31
-13
lines changed

.github/workflows/pre-build.yml

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,27 @@ jobs:
2626
java-version: ${{ inputs.java-version }}
2727
distribution: temurin
2828

29+
# For normal PR jobs, just checkout the base_ref the PR is against
2930
- uses: actions/checkout@v4
3031
with:
3132
ref: ${{ github.base_ref }}
32-
if: ${{ !(github.event.action == 'push' && github.repository != 'com-lihaoyi/mill') }}
33+
if: ${{ !(github.event_name == 'push' && github.repository != 'com-lihaoyi/mill') }}
3334

35+
# For fork push jobs, first checkout the version being pushed, then look for the
36+
# merge-base where the current version forks off from the upstream main branch
3437
- uses: actions/checkout@v4
35-
with:
36-
ref: main
37-
repository: https://github.com/com-lihaoyi/mill
38-
if: ${{ github.event.action == 'push' && github.repository != 'com-lihaoyi/mill' }}
38+
with: { fetch-depth: 0 }
39+
if: ${{ github.event_name == 'push' && github.repository != 'com-lihaoyi/mill' }}
40+
41+
- run: |
42+
git fetch https://github.com/com-lihaoyi/mill main
43+
MERGE_BASE=$(git merge-base FETCH_HEAD HEAD)
44+
# pretty-print the path between the FETCH_HEAD (main), HEAD, and the merge-base
45+
git log --graph --pretty=format:"%h %d %ar %s %n" --ancestry-path $MERGE_BASE^1..HEAD --ancestry-path $MERGE_BASE^1..FETCH_HEAD
46+
47+
git checkout $MERGE_BASE
48+
shell: bash
49+
if: ${{ github.event_name == 'push' && github.repository != 'com-lihaoyi/mill' }}
3950
4051
- run: echo temurin:${{ inputs.java-version }} > .mill-jvm-version
4152

main/define/src/mill/define/Reflect.scala

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ private[mill] object Reflect {
2929
if isLegalIdentifier(n) && (m.getModifiers & Modifier.STATIC) == 0
3030
} yield (m, n)
3131

32+
private val classSeqOrdering =
33+
Ordering.Implicits.seqOrdering[Seq, Class[_]]((c1, c2) =>
34+
if (c1 == c2) 0 else if (c1.isAssignableFrom(c2)) 1 else -1
35+
)
36+
3237
def reflect(
3338
outer: Class[_],
3439
inner: Class[_],
@@ -56,19 +61,21 @@ private[mill] object Reflect {
5661
// which messes up the comparison since all forwarders will have the
5762
// same `getDeclaringClass`. To handle these scenarios, also sort by
5863
// return type, so we can identify the most specific override
59-
64+
//
65+
// 3. A class can have multiple methods with same name/return-type if they
66+
// differ in parameter types, so sort by those as well
6067
arr.sortInPlaceWith((m1, m2) =>
61-
if (m1.getDeclaringClass.equals(m2.getDeclaringClass)) {
62-
m1.getReturnType.isAssignableFrom(m2.getReturnType)
68+
if (m1.getName != m2.getName) m1.getName < m2.getName
69+
else if (m1.getDeclaringClass != m2.getDeclaringClass) {
70+
!m1.getDeclaringClass.isAssignableFrom(m2.getDeclaringClass)
71+
} else if (m1.getReturnType != m2.getReturnType) {
72+
!m1.getReturnType.isAssignableFrom(m2.getReturnType)
6373
} else {
64-
m1.getDeclaringClass.isAssignableFrom(m2.getDeclaringClass)
74+
classSeqOrdering.lt(m1.getParameterTypes, m2.getParameterTypes)
6575
}
6676
)
6777

68-
val res = arr.reverseIterator.distinctBy(_.getName).toArray
69-
// Sometimes `getMethods` returns stuff in odd orders, make sure to sort for determinism
70-
res.sortInPlaceBy(_.getName)
71-
res
78+
arr.distinctBy(_.getName)
7279
}
7380

7481
// For some reason, this fails to pick up concrete `object`s nested directly within

0 commit comments

Comments
 (0)