Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

Constructor parameters added by the compiler (inner classes, enums) make `getParameters()` / `getParameterTypes()` inconsistent and misplace parameter annotations

Open
#1,772 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

@SamuelAdigos is already working on this.

Since Oct 28, 2026.

  • #1774 by @SamuelAdigos — open

Assessment

Difficulty
5/5
Estimated time
Over a week
Newbie friendliness
48/100
Issue type
Feature
Clarity
Clearly specified
Activity status
Active
Tech stack
java
Domain
backend

Research direction

Start with JavaCodeUnit.getParameters() and getParameterTypes(), then trace how constructor descriptors, Signature, MethodParameters, and parameter annotations are imported; inspect the tests covering inner classes, enums, and annotation indexing, including the related issues #1088, #1759, and #1773. Implementing this requires coordinating parameter types, source/compiler-added parameter classification, and annotation placement. Done means constructor parameters match the class-file descriptor, retain available generic types, classify compiler-added parameters, and attach annotations to declared parameters.

Written by the indexing model from the issue text.

Description

Summary

For constructors of inner classes and enums, the compiler adds parameters that do not exist in the source code (the enclosing instance, the enum constant name and ordinal, captured local variables). Depending on whether the constructor also has a generic signature, JavaCodeUnit.getParameterTypes() and JavaCodeUnit.getParameters() sometimes include these parameters and sometimes not. In the case where they are included, parameter annotations end up on the wrong parameter (#1088).

The goal of this issue is to make getParameters() and getParameterTypes() consistent: one JavaParameter per parameter the constructor actually has in the class file, generic types where the class file provides them, and a way to tell which parameters were declared in the source code and which were added by the compiler.

Examples

@Retention(RUNTIME)
@interface Ann {}

class Outer {
    class InnerWithoutGenerics {
        InnerWithoutGenerics(@Ann String s) {}
    }

    class InnerWithGenerics {
        InnerWithGenerics(@Ann List<String> list) {}
    }
}

enum SomeEnum {
    A("x");

    SomeEnum(@Ann String s) {}
}

What ArchUnit (current main, imported with JDK 21) reports for the single constructor of each class:

class file descriptor getRawParameterTypes() getParameterTypes() getParameters() @Ann reported on
InnerWithoutGenerics (Outer, String) [Outer, String] [Outer, String] 2 parameters parameter 0, i.e. the Outer instance (wrong)
InnerWithGenerics (Outer, List) [Outer, List] [List<String>] 1 parameter parameter 0, i.e. List<String>
SomeEnum (String, int, String) [String, int, String] [String] 1 parameter parameter 0, i.e. String s

For comparison, java.lang.reflect.Constructor.getParameters() returns 2, 2 and 3 parameters respectively and reports @Ann on the last one in each case; with classes compiled by JDK 21, Parameter.isImplicit() is true for the Outer instance and Parameter.isSynthetic() for the enum name and ordinal.

So today:

  • getParameters().size() for two constructors of the same shape differs only because one of them has a generic parameter type.
  • getParameterTypes() and getParameters() are consistent with getRawParameterTypes() only in the first row.
  • In the first row the annotation is attached to the compiler-added parameter instead of the declared one. This is #1088; #1759 fixed the annotation index in isolation, and the review of that PR led to the broader picture described here.

Why this happens

A class file carries the constructor parameters in two places:

  • the method descriptor, which always lists every parameter, including the ones added by the compiler;
  • the optional Signature attribute, which lists the parameters as declared in the source code with their generic types. javac only writes it when the constructor needs it, and it omits the compiler-added parameters.

ArchUnit derives getRawParameterTypes() from the descriptor and getParameterTypes() / getParameters() from the Signature attribute if present, otherwise from the descriptor. Whether javac writes a Signature for a constructor without generics depends on the kind of compiler-added parameters: it writes one for enum constructors and for constructors of local classes that capture variables, but not for an inner class whose only extra parameter is the enclosing instance. That is why the three rows above differ.

A related quirk: if the Signature attribute is present but lists no parameters at all, as for an enum constructor without declared parameters (descriptor (String, int), Signature ()), ArchUnit cannot tell it from a missing attribute and falls back to the descriptor. So getParameters() has 2 entries for SomeEnum() {}, but 1 entry for SomeEnum(String s) {}.

Parameter annotations (RuntimeVisibleParameterAnnotations / RuntimeInvisibleParameterAnnotations) only count the parameters declared in the source code. When ArchUnit uses the descriptor to build getParameters(), annotation index i is applied to descriptor parameter i, which is shifted by the number of leading compiler-added parameters.

Proposal

  1. getParameters() returns one JavaParameter per descriptor parameter, so its size always equals getRawParameterTypes().size() and Constructor.getParameters().length.
  2. getParameterTypes() returns the type of each of these parameters: the generic type from the Signature attribute where the class file provides one, the raw type otherwise. It has the same size as getRawParameterTypes().
  3. Each JavaParameter tells whether it was declared in the source code or added by the compiler, with predicates to filter on it. The JVM specification and javax.lang.model.util.Elements.Origin distinguish three kinds: explicit (declared in source), mandated (not in source, but required by the language specification, e.g. the enclosing instance of an inner class), synthetic (an implementation detail of the compiler, e.g. enum name and ordinal, captured variables). Note that the Reflection API calls mandated parameters "implicit" (Parameter.isImplicit()), so the final naming needs some care.
  4. Parameter annotations are attached to the declared parameter they belong to.
  5. Where the class file has a MethodParameters attribute (written by javac 21+ for these constructors, or by any javac with -parameters), its flags are used as the source of truth. Otherwise the kind of each parameter is derived from the kind of class (enum, inner class, local or anonymous class in a static or non-static context) and from the difference between descriptor and Signature.

Example after the change: all three constructors above have as many parameters as their descriptor, getParameterTypes() reports [Outer, List<String>] for InnerWithGenerics and [String, int, String] for SomeEnum, and @Ann is reported on the last parameter in all three cases.

Compatibility

For constructors of inner, local and anonymous classes and of enums that have a Signature attribute, getParameterTypes() and getParameters() grow by the compiler-added parameters. Up to now getParameterTypes() mirrored Constructor.getGenericParameterTypes(); afterwards both methods have the size of Constructor.getParameters() instead. Rules that count or index constructor parameters of such classes may see different results, and can use the new predicates to restrict themselves to declared parameters. getRawParameterTypes(), getFullName() and access resolution are not affected, since they are already based on the descriptor.

Out of scope

The compiler-added parameters get their raw type from the descriptor, e.g. Outer and not Outer<T> for an inner class of a generic class. Providing generic types for them is tracked separately in #1773.

Dominant language
Java
Stars
3.9k
Forks
350
Avg merge
1d 21h
Merged PRs (30d)
16

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from TNG/ArchUnit

All issues in TNG/ArchUnit

Similar issues

More Java issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.