Skip to content

Scaladoc Overload Method Reference Fix Try 2 - #16

Closed
bvenners wants to merge 3 commits into
mainfrom
fix-scaladoc-reference-overload-methods-2
Closed

Scaladoc Overload Method Reference Fix Try 2#16
bvenners wants to merge 3 commits into
mainfrom
fix-scaladoc-reference-overload-methods-2

Conversation

@bvenners

Copy link
Copy Markdown
Collaborator

@cheeseng The new tests pass, but I was unable to study the code changes today. I will do so tomorrow morning, but I would like you to have the opportunity to take a look during your day.

I had asked one of my "friends" to fix the bug more generally. Here's what the friend said:

Summary                                                                                                                           
                                                                                                                                    
  Part 1: Fixed MemberLookup.scala                                                                                                  
                                                                                                                                    
  Replaced hardcoded type matching with a generic signature-based approach:                                                         
                                                                                                                                    
  1. Added parseSignatureParams() - Parses parameter types from method signatures, handling nested brackets correctly               
    - Input: "[A](b:scala.collection.mutable.Buffer[A],c:Int)"                                                                      
    - Output: List("scala.collection.mutable.Buffer", "Int")                                                                        
  2. Added extractSimpleTypeName() - Extracts simple type name from qualified types                                                 
    - Input: "scala.collection.mutable.Buffer[A]" → Output: "Buffer"                                                                
  3. Rewrote matchesParameterTypes() - Compares ALL parameter types in order (not just checking if "any contains")                  
  4. Removed the hardcoded extractParamTypeFromSignature() that only matched 10 collection types                                    
                                                                                                                                    
  Part 2: Added Tests to MemberLookupTests.scala                                                                                    
                                                                                                                                    
  - Added funOverload() helper to find specific overloads by parameter types                                                        
  - Added testOverloadedMethodLookup() with tests for:                                                                              
    - Collection types (Buffer, Map, Set, Seq, Iterator, Iterable, List, Properties)                                                
    - Primitive types (Int)                                                                                                         
    - Custom user-defined types (CustomTypeA, CustomTypeB)                                                                          
    - Multiple parameters in order (Buffer+Set vs Set+Buffer)                                                                       
    - Fallback behavior for queries without signatures                                                                              
                                                                                                                                    
  Part 3: Added Tests to QueryParserTests.scala                                                                                     
                                                                                                                                    
  - Nested generics: foo[A](x:Map[String,List[A]])*                                                                                 
  - Multiple parameters: bar(a:Int,b:String)*                                                                                       
  - Empty parameters: noArgs()*                                                                                                     
  - Complex nesting: complex[A](x:Map[String,Map[Int,List[A]]])*                                                                    
  - Custom types: process(x:tests.CustomType)*                                                                                      
                                                                                                                                    
  Part 4: Expanded overloadedMethods.scala                                                                                          
                                                                                                                                    
  Added new test classes:                                                                                                           
  - CustomTypeA, CustomTypeB - Custom types for testing                                                                             
  - CustomTypeOverloads - Methods overloaded with custom types                                                                      
  - MultiParamOverloads - Methods with multiple parameters in different orders                                                      
  - CustomTypeLinks, MultiParamLinks - Documentation links to test resolution                                                       

This is how you can run the new tests:

  Run all Scaladoc tests:                                                                                                           
  sbt scaladoc/test                                                                                                                 
                                                                                                                                    
  Run specific test classes:                                                                                                        
  # Query parser tests                                                                                                              
  sbt "scaladoc/testOnly *QueryParserTests*"                                                                                        
                                                                                                                                    
  # Member lookup tests (includes overload resolution tests)                                                                        
  sbt "scaladoc/testOnly *MemberLookupTests*"                                                                                       
                                                                                                                                    
  Run both test classes together:                                                                                                   
  sbt "scaladoc/testOnly *QueryParserTests* *MemberLookupTests*"

@bvenners
bvenners requested a review from cheeseng January 20, 2026 05:55
@bvenners bvenners self-assigned this Jan 20, 2026
@cheeseng

Copy link
Copy Markdown
Collaborator

Hmm, unfortunately when I run 'sbt clean test' now the following are failing:

[error] Failed: Total 537, Failed 5, Errors 0, Passed 527, Skipped 5, Ignored 6
[error] Failed tests:
[error] 	dotty.tools.dotc.FromTastyTests
[error] 	dotty.tools.dotc.IdempotencyTests
[error] 	dotty.tools.debug.DebugTests
[error] 	dotty.tools.dotc.CompilationTests
[error] (scala3-compiler-nonbootstrapped / Test / testOnly) sbt.TestsFailedException: Tests unsuccessful
[error] Total time: 1835 s (0:30:35.0), completed 20 Jan 2026, 17:45:31

I'll try to look into why they are failing.

@cheeseng

Copy link
Copy Markdown
Collaborator

Superseded by #18 .

@cheeseng cheeseng closed this Jan 23, 2026
cheeseng pushed a commit that referenced this pull request Mar 7, 2026
Fix scala#22628

Super call like `super.m()` were emitted with `invokespecial`
where owner = `fun.symbol.owner` (the method declaration owner).

However, that is wrong for some inherited Java members:
the declaration owner can be less accessible than the direct superclass at the Scala call site. In that case it causes `IllegalAccessError` at runtime.

This aligns with the normal call behavior, see: scala@fa42b81

**example**

In the example below, `fun.symbol.owner` for `super.close()` is `InnerServerCallBase` (where `close` is declared) that is not accessible from `mycode` package.

```scala
// mylib/InnerServerCallBase.java (package-private)
package mylib;
abstract class InnerServerCallBase implements ServerCall {
  @OverRide public void close() {
    System.out.println("InnerServerCallBase.close");
  }
}

// mylib/PublicServerCallBase.java
package mylib;
public abstract class PublicServerCallBase extends InnerServerCallBase {}

// mycode/MyServerCall.scala
package mycode
class MyServerCall extends mylib.PublicServerCallBase:
  override def close(): Unit =
    println("MyServerCall.close")
    super.close()
```

As a result, Scala3 emits the following Java bytecode where `invokespecial` on non-accessible class method. And it causes `IllegalAccessError` at runtime.

Scala 3 (before):
```java
  0: getstatic     #18  // Field scala/Predef$.MODULE$:Lscala/Predef$;
  3: ldc           #20  // String MyServerCall.close
  5: invokevirtual #24  // Method scala/Predef$.println:(Ljava/lang/Object;)V
  8: aload_0
  9: invokespecial #28  // Method mylib/InnerServerCallBase.close:()V
 12: return
```

We should generate `invokespecial` on the call-site qualifier type instead, like Scala 2.13 emits.

Scala 2.13
```
  0: getstatic     #16  // Field scala/Predef$.MODULE$:Lscala/Predef$;
  3: ldc           #18  // String MyServerCall.close
  5: invokevirtual #22  // Method scala/Predef$.println:(Ljava/lang/Object;)V
  8: aload_0
  9: invokespecial #24  // Method mylib/PublicServerCallBase.close:()V
 12: return
```

This commit fixes the issue by using call-site qualifier type for invokespecial on super call.
cheeseng pushed a commit that referenced this pull request Mar 7, 2026
Fix scala#22628 
scala#24084 as well ?

Super call like `super.m()` were emitted with `invokespecial` where
owner = `fun.symbol.owner` (the method declaration owner).

However, that is wrong for some inherited Java members: the declaration
owner can be less accessible than the direct superclass at the Scala
call site. In that case it causes `IllegalAccessError` at runtime.

This aligns with the normal call behavior, see:
scala@fa42b81

**example**

In the example below, `fun.symbol.owner` for `super.close()` is
`InnerServerCallBase` (where `close` is declared) that is not accessible
from `mycode` package.

```scala
// mylib/InnerServerCallBase.java (package-private)
package mylib;
abstract class InnerServerCallBase implements ServerCall {
  @OverRide public void close() {
    System.out.println("InnerServerCallBase.close");
  }
}

// mylib/PublicServerCallBase.java
package mylib;
public abstract class PublicServerCallBase extends InnerServerCallBase {}

// mycode/MyServerCall.scala
package mycode
class MyServerCall extends mylib.PublicServerCallBase:
  override def close(): Unit =
    println("MyServerCall.close")
    super.close()
```

As a result, Scala3 emits the following Java bytecode where
`invokespecial` on non-accessible class method. And it causes
`IllegalAccessError` at runtime.

Scala 3 (before):
```java
  0: getstatic     #18  // Field scala/Predef$.MODULE$:Lscala/Predef$;
  3: ldc           #20  // String MyServerCall.close
  5: invokevirtual #24  // Method scala/Predef$.println:(Ljava/lang/Object;)V
  8: aload_0
  9: invokespecial #28  // Method mylib/InnerServerCallBase.close:()V
 12: return
```

We should generate `invokespecial` on the call-site qualifier type
instead, like Scala 2.13 emits.

Scala 2.13
```
  0: getstatic     #16  // Field scala/Predef$.MODULE$:Lscala/Predef$;
  3: ldc           #18  // String MyServerCall.close
  5: invokevirtual #22  // Method scala/Predef$.println:(Ljava/lang/Object;)V
  8: aload_0
  9: invokespecial #24  // Method mylib/PublicServerCallBase.close:()V
 12: return
```

This commit fixes the issue by using call-site qualifier type for
invokespecial on super call.

---

Huge thanks to @migesok for creating a reproducible example 🙌
https://github.com/migesok/scala3-bug-super-IllegalAccessError
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.

2 participants