Retain grouping/uses instantiation vectors 48/104748/36
authorSamuel Schneider <samuel.schneider@pantheon.tech>
Fri, 3 Mar 2023 07:42:50 +0000 (08:42 +0100)
committerRobert Varga <nite@hq.sk>
Tue, 19 Dec 2023 10:06:44 +0000 (10:06 +0000)
We need the ability fo find all instantiations of a grouping for
closed-world analysis of BindingRuntimeTypes.

This analysis is need to determine, for example:
- possible types of 'type leafref's pointing outside a grouping,
  to determine which Binding/DOM codecs are applicable
- YANG/Binding overload mapping, i.e. whether a 'container'
  defined in a grouping is instantiated only once or multiple
  types, to use a strongly-bound CodecDataObject it the former case

This patch exposes a GroupingRuntimeType.instantiations(), which exposes
exactly this information.

DefaultGroupingRuntimeType stores this information in the form of
a set of vectors, each pointing either to another grouping or to a
concrete instantiation.

GeneratorReactor collects this information sufficiently early so that it
can be also used to perform partial closed-world analysis during
compile-time.

JIRA: MDSAL-669
Change-Id: I2e21a6b93ce30d9bd1022be5747d44663b6198fc
Signed-off-by: Samuel Schneider <samuel.schneider@pantheon.tech>
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
binding/mdsal-binding-generator/src/main/java/org/opendaylight/mdsal/binding/generator/impl/reactor/AbstractCompositeGenerator.java
binding/mdsal-binding-generator/src/main/java/org/opendaylight/mdsal/binding/generator/impl/reactor/GeneratorReactor.java
binding/mdsal-binding-generator/src/main/java/org/opendaylight/mdsal/binding/generator/impl/reactor/GroupingGenerator.java
binding/mdsal-binding-generator/src/main/java/org/opendaylight/mdsal/binding/generator/impl/rt/DefaultGroupingRuntimeType.java
binding/mdsal-binding-generator/src/test/java/org/opendaylight/mdsal/binding/generator/impl/Mdsal669Test.java [new file with mode: 0644]
binding/mdsal-binding-generator/src/test/resources/mdsal669.yang [new file with mode: 0644]
binding/mdsal-binding-runtime-api/src/main/java/org/opendaylight/mdsal/binding/runtime/api/GroupingRuntimeType.java

index 20cdd0146e375791cdb7c0d63e0abc2f816b97be..5d74b61ad083ffde5e27683cd134c432daa13ada 100644 (file)
@@ -14,6 +14,7 @@ import static java.util.Objects.requireNonNull;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Set;
 import java.util.stream.Collectors;
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
@@ -266,6 +267,29 @@ public abstract class AbstractCompositeGenerator<S extends EffectiveStatement<?,
         groupings = List.copyOf(tmp);
     }
 
+    // Iterate over a some generators recursively, linking them to the GroupingGenerators they use. GroupingGenerators
+    // are skipped and added to unprocessedGroupings for later processing.
+    final void linkUsedGroupings(final Set<GroupingGenerator> skippedChildren) {
+        // Link to used groupings IFF we have a corresponding generated Java class
+        switch (classPlacement()) {
+            case NONE:
+            case PHANTOM:
+                break;
+            default:
+                for (var grouping : groupings()) {
+                    grouping.addUser(this);
+                }
+        }
+
+        for (var child : childGenerators) {
+            if (child instanceof GroupingGenerator grouping) {
+                skippedChildren.add(grouping);
+            } else if (child instanceof AbstractCompositeGenerator<?, ?> composite) {
+                composite.linkUsedGroupings(skippedChildren);
+            }
+        }
+    }
+
     final void startUsesAugmentLinkage(final List<AugmentRequirement> requirements) {
         for (var child : childGenerators) {
             if (child instanceof UsesAugmentGenerator uses) {
index fde702375b2184a882cf0084b3a839bc0020a19c..57d477d87b234f33879b7cf584a74676fe6639c2 100644 (file)
@@ -17,6 +17,7 @@ import com.google.common.collect.Maps;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Deque;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.function.Function;
@@ -182,15 +183,20 @@ public final class GeneratorReactor extends GeneratorContext implements Mutable
          */
         linkDependencies(children);
 
-        // Step five: resolve all 'type leafref' and 'type identityref' statements, so they point to their
-        //            corresponding Java type representation.
+        // Step 5: resolve grouping usage, so that each GroupingGenerator has links to their instantiation sites and
+        //         any unused
+        resolveGroupingUsers();
+        freezeGroupingUsers(children);
+
+        // Step 6: resolve all 'type leafref' and 'type identityref' statements, so they point to their corresponding
+        //         Java type representation.
         bindTypeDefinition(children);
 
-        // Step six: walk all composite generators and link ChildOf/ChoiceIn relationships with parents. We have taken
-        //           care of this step during tree construction, hence this now a no-op.
+        // Step 7: walk all composite generators and link ChildOf/ChoiceIn relationships with parents. We have taken
+        //         care of this step during tree construction, hence this now a no-op.
 
         /*
-         * Step seven: assign java packages and JavaTypeNames
+         * Step 8: assign java packages and JavaTypeNames
          *
          * This is a really tricky part, as we have large number of factors to consider:
          * - we are mapping grouping, typedef, identity and schema tree namespaces into Fully Qualified Class Names,
@@ -222,11 +228,13 @@ public final class GeneratorReactor extends GeneratorContext implements Mutable
             }
         } while (haveUnresolved);
 
-        // Step eight: generate actual Types
-        //
-        // We have now properly cross-linked all generators and have assigned their naming roots, so from this point
-        // it looks as though we are performing a simple recursive execution. In reality, though, the actual path taken
-        // through generators is dictated by us as well as generator linkage.
+        /*
+         * Step 9: generate actual Types
+         *
+         * We have now properly cross-linked all generators and have assigned their naming roots, so from this point
+         * it looks as though we are performing a simple recursive execution. In reality, though, the actual path taken
+         * through generators is dictated by us as well as generator linkage.
+         */
         for (var module : children) {
             module.ensureType(builderFactory);
         }
@@ -418,4 +426,58 @@ public final class GeneratorReactor extends GeneratorContext implements Mutable
             stack.pop();
         }
     }
+
+    private void resolveGroupingUsers() {
+        // Primary pass on modules, collecting all groupings which were left unprocessed
+        // TODO: use a plain List
+        final var remaining = new HashSet<GroupingGenerator>();
+        for (var module : children) {
+            module.linkUsedGroupings(remaining);
+        }
+        LOG.debug("Grouping pass 1 found {} groupings", remaining.size());
+
+        // Secondary passes: if any unprocessed groupings have been marked as used, process their children, potentially
+        //                   adding more work
+        int passes = 2;
+        int processed;
+        do {
+            // Do not process groupings again unless we make some progress
+            processed = 0;
+
+            final var found = new HashSet<GroupingGenerator>();
+            final var it = remaining.iterator();
+            while (it.hasNext()) {
+                final var next = it.next();
+                if (next.hasUser()) {
+                    // Process this grouping and remember we need to iterate again, as groupings we have already visited
+                    // may become used as a side-effect.
+                    it.remove();
+                    next.linkUsedGroupings(found);
+                    processed++;
+                }
+            }
+
+            final var foundSize = found.size();
+            LOG.debug("Grouping pass {} processed {} and found {} grouping(s)", passes, processed, foundSize);
+            if (foundSize != 0) {
+                // we have some more groupings to process, shove them into the next iteration
+                remaining.addAll(found);
+            }
+
+            passes++;
+        } while (processed != 0);
+
+        LOG.debug("Grouping usage completed after {} pass(es) with unused {} grouping(s)", passes, remaining.size());
+    }
+
+    private static void freezeGroupingUsers(final Iterable<? extends Generator> parent) {
+        for (var child : parent) {
+            if (child instanceof AbstractCompositeGenerator<?, ?> composite) {
+                if (composite instanceof GroupingGenerator grouping) {
+                    grouping.freezeUsers();
+                }
+                freezeGroupingUsers(composite);
+            }
+        }
+    }
 }
index 8697f97bc39a1d1aeb2e28ce457229bbc4067a1e..5df077961327ecf20f0cbc43277adac74dc34f2a 100644 (file)
@@ -7,12 +7,12 @@
  */
 package org.opendaylight.mdsal.binding.generator.impl.reactor;
 
-import static com.google.common.base.Verify.verify;
-
+import com.google.common.base.VerifyException;
+import java.util.ArrayList;
 import java.util.List;
+import java.util.stream.Collectors;
 import org.opendaylight.mdsal.binding.generator.impl.rt.DefaultGroupingRuntimeType;
 import org.opendaylight.mdsal.binding.model.api.GeneratedType;
-import org.opendaylight.mdsal.binding.model.api.type.builder.GeneratedTypeBuilder;
 import org.opendaylight.mdsal.binding.model.api.type.builder.GeneratedTypeBuilderBase;
 import org.opendaylight.mdsal.binding.model.ri.BindingTypes;
 import org.opendaylight.mdsal.binding.runtime.api.AugmentRuntimeType;
@@ -26,10 +26,35 @@ import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
  * Generator corresponding to a {@code grouping} statement.
  */
 final class GroupingGenerator extends AbstractCompositeGenerator<GroupingEffectiveStatement, GroupingRuntimeType> {
+    // Linkage towards concrete data tree instantiations of this grouping. This can contain two different kinds of
+    // generators:
+    // - GroupingGenerators which provide next step in the linkage
+    // - other composite generators, which are the actual instantiations
+    private List<AbstractCompositeGenerator<?, ?>> users;
+
     GroupingGenerator(final GroupingEffectiveStatement statement, final AbstractCompositeGenerator<?, ?> parent) {
         super(statement, parent);
     }
 
+    void addUser(final AbstractCompositeGenerator<?, ?> user) {
+        if (users == null) {
+            // We are adding the first user: allocate a small set and notify the groupings we use that we are a user
+            users = new ArrayList<>();
+            for (var grouping : groupings()) {
+                grouping.addUser(this);
+            }
+        }
+        users.add(user);
+    }
+
+    boolean hasUser() {
+        return users != null;
+    }
+
+    void freezeUsers() {
+        users = users == null ? List.of() : users.stream().distinct().collect(Collectors.toUnmodifiableList());
+    }
+
     @Override
     StatementNamespace namespace() {
         return StatementNamespace.GROUPING;
@@ -42,13 +67,13 @@ final class GroupingGenerator extends AbstractCompositeGenerator<GroupingEffecti
 
     @Override
     GeneratedType createTypeImpl(final TypeBuilderFactory builderFactory) {
-        final GeneratedTypeBuilder builder = builderFactory.newGeneratedTypeBuilder(typeName());
+        final var builder = builderFactory.newGeneratedTypeBuilder(typeName());
         builder.addImplementsType(BindingTypes.DATA_OBJECT);
         narrowImplementedInterface(builder);
         addUsesInterfaces(builder, builderFactory);
         addGetterMethods(builder, builderFactory);
 
-        final ModuleGenerator module = currentModule();
+        final var module = currentModule();
         module.addQNameConstant(builder, statement().argument());
 
         annotateDeprecatedIfNecessary(builder);
@@ -65,15 +90,26 @@ final class GroupingGenerator extends AbstractCompositeGenerator<GroupingEffecti
     @Override
     CompositeRuntimeTypeBuilder<GroupingEffectiveStatement, GroupingRuntimeType> createBuilder(
             final GroupingEffectiveStatement statement) {
+        final var local = users;
+        if (local == null) {
+            throw new VerifyException(this + " has unresolved users");
+        }
+
+        final var vectors = local.stream()
+            .map(AbstractCompositeGenerator::getRuntimeType)
+            .distinct()
+            .collect(Collectors.toUnmodifiableList());
+
         return new CompositeRuntimeTypeBuilder<>(statement) {
             @Override
             GroupingRuntimeType build(final GeneratedType type, final GroupingEffectiveStatement statement,
                     final List<RuntimeType> children, final List<AugmentRuntimeType> augments) {
                 // Groupings cannot be targeted by 'augment'
-                verify(augments.isEmpty(), "Unexpected augments %s", augments);
-                return new DefaultGroupingRuntimeType(type, statement, children);
+                if (augments.isEmpty()) {
+                    return new DefaultGroupingRuntimeType(type, statement, children, vectors);
+                }
+                throw new VerifyException("Unexpected augments " + augments);
             }
         };
     }
-
 }
index 5b81473851784efb1d746533f8f17acf81f9b089..a0abdcefbb02c7de44aebd796a250a253f4364e6 100644 (file)
@@ -7,16 +7,49 @@
  */
 package org.opendaylight.mdsal.binding.generator.impl.rt;
 
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
+import org.eclipse.jdt.annotation.Nullable;
 import org.opendaylight.mdsal.binding.model.api.GeneratedType;
+import org.opendaylight.mdsal.binding.runtime.api.CompositeRuntimeType;
 import org.opendaylight.mdsal.binding.runtime.api.GroupingRuntimeType;
 import org.opendaylight.mdsal.binding.runtime.api.RuntimeType;
 import org.opendaylight.yangtools.yang.model.api.stmt.GroupingEffectiveStatement;
 
 public final class DefaultGroupingRuntimeType extends AbstractCompositeRuntimeType<GroupingEffectiveStatement>
         implements GroupingRuntimeType {
+    /**
+     * These are vectors towards concrete instantiations of this type -- i.e. the manifestation in the effective data
+     * tree. Each item in this list represents either:
+     * <ul>
+     *   <li>a concrete instantiation, or<li>
+     *   <li>another {@link GroupingRuntimeType}</li>
+     * </ul>
+     * We use these vectors to create {@link #instantiations()}.
+     */
+    private final @Nullable Object instantiationVectors;
+
     public DefaultGroupingRuntimeType(final GeneratedType bindingType, final GroupingEffectiveStatement statement,
-            final List<RuntimeType> children) {
+            final List<RuntimeType> children, final List<? extends CompositeRuntimeType> instantiationVectors) {
         super(bindingType, statement, children);
+        this.instantiationVectors = switch (instantiationVectors.size()) {
+            case 0 -> null;
+            case 1 -> Objects.requireNonNull(instantiationVectors.get(0));
+            default -> instantiationVectors.stream().map(Objects::requireNonNull).toArray(CompositeRuntimeType[]::new);
+        };
+    }
+
+    @Override
+    public List<CompositeRuntimeType> directUsers() {
+        final var local = instantiationVectors;
+        if (local == null) {
+            return List.of();
+        } else if (local instanceof CompositeRuntimeType[] array) {
+            return Collections.unmodifiableList(Arrays.asList(array));
+        } else {
+            return List.of((CompositeRuntimeType) local);
+        }
     }
 }
diff --git a/binding/mdsal-binding-generator/src/test/java/org/opendaylight/mdsal/binding/generator/impl/Mdsal669Test.java b/binding/mdsal-binding-generator/src/test/java/org/opendaylight/mdsal/binding/generator/impl/Mdsal669Test.java
new file mode 100644 (file)
index 0000000..4bf407d
--- /dev/null
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2023 PANTHEON.tech, s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.mdsal.binding.generator.impl;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+
+import java.util.Arrays;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.junit.jupiter.api.Test;
+import org.opendaylight.mdsal.binding.model.api.JavaTypeName;
+import org.opendaylight.mdsal.binding.runtime.api.BindingRuntimeTypes;
+import org.opendaylight.mdsal.binding.runtime.api.GroupingRuntimeType;
+import org.opendaylight.mdsal.binding.runtime.api.RuntimeType;
+import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
+
+class Mdsal669Test {
+    private static final BindingRuntimeTypes RUNTIME_TYPES = new DefaultBindingRuntimeGenerator()
+        .generateTypeMapping(YangParserTestUtils.parseYangResource("/mdsal669.yang"));
+
+    @Test
+    void barIsUsed() {
+        assertInstances(JavaTypeName.create("org.opendaylight.yang.gen.v1.mdsal669.norev", "Bar"),
+            JavaTypeName.create("org.opendaylight.yang.gen.v1.mdsal669.norev", "Foo"),
+            JavaTypeName.create("org.opendaylight.yang.gen.v1.mdsal669.norev", "Target1"),
+            JavaTypeName.create("org.opendaylight.yang.gen.v1.mdsal669.norev.used.augmented", "ToBeAugmented1"),
+            JavaTypeName.create("org.opendaylight.yang.gen.v1.mdsal669.norev.used.augmented.indirect",
+                "ToBeAugmented1"));
+    }
+
+    @Test
+    void bazIsUsedByOneAndTwo() {
+        assertInstances(JavaTypeName.create("org.opendaylight.yang.gen.v1.mdsal669.norev", "Baz"),
+            JavaTypeName.create("org.opendaylight.yang.gen.v1.mdsal669.norev", "One"),
+            JavaTypeName.create("org.opendaylight.yang.gen.v1.mdsal669.norev", "Two"));
+    }
+
+    @Test
+    void unusedIsNotUsed() {
+        assertInstances(JavaTypeName.create("org.opendaylight.yang.gen.v1.mdsal669.norev", "Unused"));
+    }
+
+    @Test
+    void fooAsStringIsNotUsed() {
+        assertInstances(JavaTypeName.create("org.opendaylight.yang.gen.v1.mdsal669.norev", "FooAsString"));
+    }
+
+    @Test
+    void unusedBarIsNotUsed() {
+        assertInstances(JavaTypeName.create("org.opendaylight.yang.gen.v1.mdsal669.norev", "UnusedBar"));
+    }
+
+    @Test
+    void unusedAugmendIsNotUsed() {
+        assertInstances(JavaTypeName.create("org.opendaylight.yang.gen.v1.mdsal669.norev", "UnusedAugmented"));
+    }
+
+    @Test
+    void unusedIntermediateAugmentedIsNotUsed() {
+        assertInstances(
+            JavaTypeName.create("org.opendaylight.yang.gen.v1.mdsal669.norev", "UnusedIntermediateAugmentedUser"));
+        assertInstances(
+            JavaTypeName.create("org.opendaylight.yang.gen.v1.mdsal669.norev", "UnusedIntermediateAugmented"));
+    }
+
+    @Test
+    void usedAugmentedIndirectIsUsed() {
+        assertInstances(JavaTypeName.create("org.opendaylight.yang.gen.v1.mdsal669.norev", "UsedAugmentedIndirectGrp"),
+            JavaTypeName.create("org.opendaylight.yang.gen.v1.mdsal669.norev", "UsedAugmentedIndirectUser"));
+        assertInstances(JavaTypeName.create("org.opendaylight.yang.gen.v1.mdsal669.norev", "UsedAugmentedIndirect"),
+            JavaTypeName.create("org.opendaylight.yang.gen.v1.mdsal669.norev", "UsedAugmentedIndirectUser"));
+    }
+
+    @Test
+    void usedAugmentedIsUsed() {
+        assertInstances(JavaTypeName.create("org.opendaylight.yang.gen.v1.mdsal669.norev", "UsedAugmented"),
+            JavaTypeName.create("org.opendaylight.yang.gen.v1.mdsal669.norev", "UsedAugmentedUser"));
+    }
+
+    @Test
+    void toBeAugmentedIsUsed() {
+        assertInstances(JavaTypeName.create("org.opendaylight.yang.gen.v1.mdsal669.norev", "ToBeAugmented"),
+            JavaTypeName.create("org.opendaylight.yang.gen.v1.mdsal669.norev", "UsedAugmentedIndirectUser"),
+            JavaTypeName.create("org.opendaylight.yang.gen.v1.mdsal669.norev", "UsedAugmentedUser"));
+    }
+
+    private static void assertInstances(final JavaTypeName groupingTypeName, final JavaTypeName... instanceTypeNames) {
+        assertEquals(
+            Arrays.stream(instanceTypeNames).map(Mdsal669Test::assertType).collect(Collectors.toSet()),
+            Set.copyOf(assertGrouping(groupingTypeName).instantiations()));
+    }
+
+    private static GroupingRuntimeType assertGrouping(final JavaTypeName typeName) {
+        return assertInstanceOf(GroupingRuntimeType.class, assertType(typeName));
+    }
+
+    private static RuntimeType assertType(final JavaTypeName typeName) {
+        return RUNTIME_TYPES.findSchema(typeName).orElseThrow();
+    }
+}
diff --git a/binding/mdsal-binding-generator/src/test/resources/mdsal669.yang b/binding/mdsal-binding-generator/src/test/resources/mdsal669.yang
new file mode 100644 (file)
index 0000000..eb88850
--- /dev/null
@@ -0,0 +1,157 @@
+module mdsal669 {
+  namespace mdsal669;
+  prefix mdsal669;
+
+  grouping bar {
+    container bar {
+      leaf-list bar {
+        type leafref {
+          path ../../foo;
+        }
+      }
+    }
+  }
+
+  container foo {
+    leaf foo {
+      type instance-identifier;
+    }
+
+    uses bar;
+  }
+
+  grouping baz {
+    leaf baz {
+      type leafref {
+        path "../bar";
+      }
+    }
+  }
+
+  container one {
+    leaf bar {
+      type string;
+    }
+    uses baz;
+  }
+
+  container two {
+    leaf bar {
+      type uint16;
+    }
+    uses baz;
+  }
+
+  grouping unused {
+    leaf foo {
+      type leafref {
+        path "../bar";
+      }
+    }
+  }
+
+  // this grouping is used ...
+  grouping foo-as-string {
+    leaf foo {
+      type string;
+    }
+
+    uses bar;
+  }
+
+  // ... but this grouping is not ...
+  grouping unused-bar {
+    container qux {
+      // ... and hence this is not count as an instantiation
+      uses foo-as-string;
+    }
+  }
+
+  // Direct use via augment
+  container target;
+
+  augment /target {
+    leaf foo {
+      type uint32;
+    }
+
+    uses bar;
+  }
+
+  // Multiple use cases for uses/augment: this is the base grouping
+  grouping to-be-augmented {
+    container to-be-augmented;
+  }
+
+  // This grouping is not used
+  grouping unused-augmented {
+    uses to-be-augmented {
+      augment to-be-augmented {
+        leaf foo {
+          type boolean;
+        }
+
+        uses bar;
+      }
+    }
+  }
+
+  // This grouping is used only ...
+  grouping unused-intermediate-augmented {
+    uses to-be-augmented {
+      augment to-be-augmented {
+        leaf foo {
+          type uint64;
+        }
+
+        uses bar;
+      }
+    }
+  }
+
+  // ... by this grouping, which itself is not used
+  grouping unused-intermediate-augmented-user {
+    uses unused-intermediate-augmented;
+  }
+
+  // This grouping is used directly ...
+  grouping used-augmented {
+    uses to-be-augmented {
+      augment to-be-augmented {
+        leaf foo {
+          type uint8;
+        }
+
+        uses bar;
+      }
+    }
+  }
+
+  // ... by this container
+  container used-augmented-user {
+    uses used-augmented;
+  }
+
+  // ... this grouping is used ...
+  grouping used-augmented-indirect {
+    uses to-be-augmented {
+      augment to-be-augmented {
+        leaf foo {
+          type empty;
+        }
+
+        uses bar;
+      }
+    }
+  }
+
+  // ... by another grouping, which itself is used ...
+  grouping used-augmented-indirect-grp {
+    uses used-augmented-indirect;
+  }
+
+  // ... by this container
+  container used-augmented-indirect-user {
+    uses used-augmented-indirect-grp;
+  }
+}
index 02c62a2800187b70c02c5781043eb55992ef8980..6e7b31c9049a99a53a3e32c6aef58ffb6de0c65d 100644 (file)
@@ -7,6 +7,10 @@
  */
 package org.opendaylight.mdsal.binding.runtime.api;
 
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.yangtools.yang.model.api.stmt.GroupingEffectiveStatement;
 
 /**
@@ -15,4 +19,89 @@ import org.opendaylight.yangtools.yang.model.api.stmt.GroupingEffectiveStatement
 public interface GroupingRuntimeType extends CompositeRuntimeType {
     @Override
     GroupingEffectiveStatement statement();
+
+    /**
+     * Return the set of all concrete data tree instantiations of this {@code grouping}. This is necessary to completely
+     * resolve type information for {@code leafref}s.
+     *
+     * <p>
+     * As an example, consider {@link GroupingRuntimeType} of {@code grouping baz} and it's instantiations roots
+     * {@code container one} and {@code container two} define in these three models:
+     * <pre>{@code
+     *   module baz {
+     *     namespace baz;
+     *     prefix baz;
+     *
+     *     grouping baz {
+     *       leaf baz {
+     *         type leafref {
+     *           path "../bar";
+     *         }
+     *       }
+     *     }
+     *   }
+     *
+     *   module one {
+     *     namespace one;
+     *     prefix one;
+     *     import baz { prefix baz; }
+     *
+     *     container one {
+     *       leaf bar {
+     *         type string;
+     *       }
+     *       uses baz:baz;
+     *     }
+     *   }
+     *
+     *   module two {
+     *     namespace two;
+     *     prefix two;
+     *     import baz { prefix baz; }
+     *
+     *     container two {
+     *       leaf bar {
+     *         type uint16;
+     *       }
+     *       uses baz:baz;
+     *     }
+     *   }
+     * }</pre>
+     *
+     * <p>
+     * Since these are separate modules, each of them can be part of its own compilation unit and therefore
+     * {@code grouping baz} compile-time analysis cannot definitely determine the return type of {@code getBaz()} and
+     * must fall back to {@code Object}.
+     *
+     * <p>
+     * At run-time, though, we have a closed world, and therefore we can provide accurate information about
+     * instantiation sites: this method will return the {@link CompositeRuntimeType}s for {@code one} and {@code two}.
+     * We can then use this information to know that {@code getBaz()} can either be a {@code String} or an
+     * {@code Uint32} and which type is appropriate at a particular point in YANG data tree.
+     *
+     * @return The set instantiated {@link CompositeRuntimeType}s which use this grouping
+     */
+    default @NonNull List<CompositeRuntimeType> instantiations() {
+        final var users = directUsers();
+        return switch (users.size()) {
+            case 0 -> List.of();
+            case 1 -> {
+                final var user = users.get(0);
+                yield user instanceof GroupingRuntimeType grouping ? grouping.instantiations() : List.of(user);
+            }
+            default -> users.stream()
+                .flatMap(user -> user instanceof GroupingRuntimeType grouping ? grouping.instantiations().stream()
+                    : Stream.of(user))
+                .distinct()
+                .collect(Collectors.toUnmodifiableList());
+        };
+    }
+
+    /**
+     * Support method for {@link #instantiations()}. This method's return, unlike {@link #instantiations()} can contain
+     * other {@link GroupingRuntimeType}s.
+     *
+     * @return Direct users of this grouping
+     */
+    @NonNull List<CompositeRuntimeType> directUsers();
 }