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

`getAllInvolvedRawTypes()` sometimes returns incorrect type variable bounds

Open
#1,449 3 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
42/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Stale
Tech stack
java
Domain
backend, testing

Research direction

The report identifies getAllInvolvedRawTypes() and provides a reproducing JUnit test in TypeVariableTest, including nested classes with shadowed type parameters. Start by locating that method and tracing how type variables in imported supertypes resolve their bounds; use the supplied test scenario to reproduce the issue. Done means the test’s expected bounds are reported for every nested class, with zero assertion failures.

Written by the indexing model from the issue text.

Description

Scenario:

  • a generic method or class ("scope" for short) has a type parameter called T with type bounds B1, i.e. T extends B1;
  • this scope has a nested class C with a type parameter also called T (which shadows the T in the enclosing scope) with type bounds B2;
  • the type parameter T is used in the extends or implementes clause of class C, e.g. C<T extends B2> implements List<T>.

Problem:

  • when calling getAllInvolvedRawTypes() on the supertype(s) of C (in the above example, List<T>), the type parameter T sometimes incorrectly resolves to the shadowed type parameter T (the one in the enclosing scope), and thus its bounds are incorrectly reported as B1 instead of B2.

Full example code:

package typevariabletest;

import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import org.assertj.core.api.SoftAssertions;
import org.junit.jupiter.api.Test;

import java.util.ArrayList;
import java.util.Set;
import java.util.stream.Stream;

import static java.util.stream.Collectors.toSet;

class TypeVariableTest {

    static class Bound0 {}
    static class Bound1 {}
    static class Bound2 {}
    static class Bound3 {}
    static class Bound4 {}
    static class Bound5 {}
    static class Bound6 {}
    static class Bound7 {}
    static class Bound8 {}
    static class Bound9 {}

    static class C0<T extends Bound0> extends ArrayList<T> {
        static class C1<T extends Bound1> extends ArrayList<T> {
            static class C2<T extends Bound2> extends ArrayList<T> {
                static class C3<T extends Bound3> extends ArrayList<T> {
                    static class C4<T extends Bound4> extends ArrayList<T> {
                        static class C5<T extends Bound5> extends ArrayList<T> {
                            static class C6<T extends Bound6> extends ArrayList<T> {
                                static class C7<T extends Bound7> extends ArrayList<T> {
                                    static class C8<T extends Bound8> extends ArrayList<T> {
                                        static class C9<T extends Bound9> extends ArrayList<T> {
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    @Test
    void test() {
        final SoftAssertions softly = new SoftAssertions();
        final String BASE_CLASS_NAME = TypeVariableTest.class.getName();

        final JavaClasses javaClasses = new ClassFileImporter().importPackagesOf(TypeVariableTest.class);
        String enclosingClassName = BASE_CLASS_NAME;

        for (int i = 0; i <= 9; i++) {
            final String className = enclosingClassName + "$C" + i;
            final JavaClass javaClass = javaClasses.get(className);

            final Set<String> expected = Set.of(
                    "java.util.ArrayList",
                    BASE_CLASS_NAME + "$Bound" + i
            );

            final Set<String> actual = Stream.concat(
                            javaClass.getInterfaces().stream(),
                            javaClass.getSuperclass().stream())
                    .flatMap(supertype -> supertype
                            .getAllInvolvedRawTypes()  // sometimes contains bounds of the *shadowed*
                                                       // type parameter, not the actual (shadowing) one,
                                                       // e.g. `Bound0` instead of `Bound1`,
                                                       // or even `Bound4` instead of `Bound9`
                            .stream())
                    .map(JavaClass::getFullName)
                    .collect(toSet());

            softly
                    .assertThat(actual)
                    .as(className)
                    .containsExactlyInAnyOrderElementsOf(expected);

            enclosingClassName = className;
        }

        softly.assertAll();
    }
}

The above JUnit test performs 10 soft assertions, of which typically 7 or 8 fail (the expected state is 0 failures).

Tested with ArchUnit 1.4.0.

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

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.