Avoid some duplicate bridge methods

Work around a long-standing compilation ordering bridge method
generation bug, and pave the way for ignoring bridges across
compilation boundaries during separation compilation (which is
good news for ijar and turbine).

Ported from I72c0293bf0784af2ac1b653fc1a0b3d3ff3d5ce6.

Bug: 302395149
Test: toolchain/jdk/build/build-openjdk21-linux.sh
Change-Id: I70e9682c4253e4b5e19741606bc838e1c45c8cbe
diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TransTypes.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TransTypes.java
index 36d3a2b..116a17c 100644
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TransTypes.java
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TransTypes.java
@@ -76,6 +76,8 @@
     private final Resolve resolve;
     private final CompileStates compileStates;
 
+    private final boolean skipDuplicateBridges;
+
     @SuppressWarnings("this-escape")
     protected TransTypes(Context context) {
         context.put(transTypesKey, this);
@@ -87,6 +89,8 @@
         types = Types.instance(context);
         make = TreeMaker.instance(context);
         resolve = Resolve.instance(context);
+        Source source = Source.instance(context);
+        skipDuplicateBridges = Options.instance(context).getBoolean("skipDuplicateBridges", false);
         annotate = Annotate.instance(context);
         attr = Attr.instance(context);
     }
@@ -358,6 +362,9 @@
                                        MethodSymbol impl,
                                        Type dest) {
             if (impl != method) {
+                if (skipBridge(method, impl, dest)) {
+                    return false;
+                }
                 // If either method or impl have different erasures as
                 // members of dest, a bridge is needed.
                 Type method_erasure = method.erasure(types);
@@ -398,6 +405,41 @@
             return types.isSameType(erasure(types.memberType(type, method)),
                                     erasure);
         }
+        /**
+         * Returns true if a bridge should be skipped because we expect it to be declared in
+         * a super-type.
+         *
+         * @param method The symbol for which a bridge might have to be added
+         * @param impl The implementation of method
+         * @param dest The type in which the bridge would go
+         */
+        private boolean skipBridge(MethodSymbol method,
+                                   MethodSymbol impl,
+                                   Type dest) {
+            if (!skipDuplicateBridges) {
+                return false;
+            }
+            if (dest.tsym == impl.owner) {
+                // the method is implemented in the current class; we need to bridge it here
+                return false;
+            }
+            if (!types.isSubtype(
+                    types.erasure(impl.owner.type), types.erasure(method.owner.type))) {
+                // the method is implemented in some supertype that is not a subtype of
+                // the declaring type, so the bridge will not have been created in the
+                // implementing class
+                return false;
+            }
+            if (!impl.overrides(method, (TypeSymbol) impl.owner, types, true)) {
+                // the method is implementing in a supertype that is also a subtype
+                // of the declaring type, but the method's signature in the implementing
+                // class does not override its signature in the declaring class
+                return false;
+            }
+            // we don't need to consider visibility for accessibility bridges, because
+            // that happens on a separate code path
+            return true;
+        }
 
     void addBridges(DiagnosticPosition pos,
                     TypeSymbol i,