Suppress bindings for template instantiation, if suppressed for the definition.

PiperOrigin-RevId: 524108610
diff --git a/rs_bindings_from_cc/BUILD b/rs_bindings_from_cc/BUILD
index e4a680b..8432679 100644
--- a/rs_bindings_from_cc/BUILD
+++ b/rs_bindings_from_cc/BUILD
@@ -133,6 +133,9 @@
     name = "bazel_types",
     srcs = ["bazel_types.cc"],
     hdrs = ["bazel_types.h"],
+    visibility = [
+        ":__subpackages__",
+    ],
     deps = [
         "//common:string_type",
         "@absl//absl/log:check",
diff --git a/rs_bindings_from_cc/importers/BUILD b/rs_bindings_from_cc/importers/BUILD
index 9aee5fa..66da70c 100644
--- a/rs_bindings_from_cc/importers/BUILD
+++ b/rs_bindings_from_cc/importers/BUILD
@@ -25,6 +25,7 @@
         "@absl//absl/log:check",
         "@absl//absl/log:die_if_null",
         "//rs_bindings_from_cc:ast_convert",
+        "//rs_bindings_from_cc:bazel_types",
         "//rs_bindings_from_cc:decl_importer",
         "@llvm-project//clang:ast",
         "@llvm-project//clang:basic",
diff --git a/rs_bindings_from_cc/importers/cxx_record.cc b/rs_bindings_from_cc/importers/cxx_record.cc
index 38d46ac..de72298 100644
--- a/rs_bindings_from_cc/importers/cxx_record.cc
+++ b/rs_bindings_from_cc/importers/cxx_record.cc
@@ -12,6 +12,7 @@
 #include "absl/log/die_if_null.h"
 #include "absl/log/log.h"
 #include "rs_bindings_from_cc/ast_convert.h"
+#include "rs_bindings_from_cc/bazel_types.h"
 #include "clang/AST/ASTContext.h"
 #include "clang/AST/CXXInheritance.h"
 #include "clang/AST/Decl.h"
@@ -209,6 +210,7 @@
   clang::SourceLocation source_loc;
   std::optional<std::string> doc_comment;
   bool is_explicit_class_template_instantiation_definition = false;
+  std::optional<BazelLabel> defining_target;
   if (auto* specialization_decl =
           clang::dyn_cast<clang::ClassTemplateSpecializationDecl>(
               record_decl)) {
@@ -229,6 +231,20 @@
           ictx_.GetComment(specialization_decl->getSpecializedTemplate());
     }
     source_loc = specialization_decl->getBeginLoc();
+    /// Note: only specify defining_target if it's a template instantiation!
+    /// Explicit specializations are their own defining_target.
+    if (auto instantiation_source =
+            specialization_decl->getInstantiatedFrom()) {
+      clang::NamedDecl* decl;
+      if (auto* template_decl =
+              instantiation_source.dyn_cast<clang::ClassTemplateDecl*>()) {
+        decl = template_decl;
+      } else {
+        decl = instantiation_source
+                   .get<clang::ClassTemplatePartialSpecializationDecl*>();
+      }
+      defining_target = ictx_.GetOwningTarget(decl);
+    }
   } else {
     const clang::NamedDecl* named_decl = record_decl;
     if (record_decl->getName().empty()) {
@@ -301,6 +317,7 @@
       .mangled_cc_name = ictx_.GetMangledName(record_decl),
       .id = GenerateItemId(record_decl),
       .owning_target = ictx_.GetOwningTarget(record_decl),
+      .defining_target = std::move(defining_target),
       .doc_comment = std::move(doc_comment),
       .source_loc = ictx_.ConvertSourceLocation(source_loc),
       .unambiguous_public_bases = GetUnambiguousPublicBases(*record_decl),
diff --git a/rs_bindings_from_cc/ir.cc b/rs_bindings_from_cc/ir.cc
index a2b13ae..6e631b4 100644
--- a/rs_bindings_from_cc/ir.cc
+++ b/rs_bindings_from_cc/ir.cc
@@ -423,6 +423,7 @@
       {"mangled_cc_name", mangled_cc_name},
       {"id", id},
       {"owning_target", owning_target},
+      {"defining_target", defining_target},
       {"doc_comment", doc_comment},
       {"source_loc", source_loc},
       {"unambiguous_public_bases", unambiguous_public_bases},
diff --git a/rs_bindings_from_cc/ir.h b/rs_bindings_from_cc/ir.h
index aabb3b2..8430b15 100644
--- a/rs_bindings_from_cc/ir.h
+++ b/rs_bindings_from_cc/ir.h
@@ -557,6 +557,7 @@
 
   ItemId id;
   BazelLabel owning_target;
+  std::optional<BazelLabel> defining_target;
   std::optional<std::string> doc_comment;
   std::string source_loc;
   std::vector<BaseClass> unambiguous_public_bases;
diff --git a/rs_bindings_from_cc/ir.rs b/rs_bindings_from_cc/ir.rs
index bbea229..9fc5fce 100644
--- a/rs_bindings_from_cc/ir.rs
+++ b/rs_bindings_from_cc/ir.rs
@@ -523,6 +523,9 @@
     pub mangled_cc_name: Rc<str>,
     pub id: ItemId,
     pub owning_target: BazelLabel,
+    /// The target containing the template definition, if this is a templated
+    /// record type.
+    pub defining_target: Option<BazelLabel>,
     pub doc_comment: Option<Rc<str>>,
     pub source_loc: Rc<str>,
     pub unambiguous_public_bases: Vec<BaseClass>,
@@ -858,10 +861,13 @@
         }
     }
 
-    /// Returns the target that this was defined in.
+    /// Returns the target that this was defined in, if it was defined somewhere
+    /// other than `owning_target()`.
     pub fn defining_target(&self) -> Option<&BazelLabel> {
-        // TODO(b/266727458): return the template target where applicable.
-        self.owning_target()
+        match self {
+            Item::Record(record) => record.defining_target.as_ref(),
+            _ => None,
+        }
     }
 
     /// Returns the target that this should generate source code in.
diff --git a/rs_bindings_from_cc/ir_from_cc_test.rs b/rs_bindings_from_cc/ir_from_cc_test.rs
index ec2b346..650f33f 100644
--- a/rs_bindings_from_cc/ir_from_cc_test.rs
+++ b/rs_bindings_from_cc/ir_from_cc_test.rs
@@ -2358,27 +2358,28 @@
     assert_ir_matches!(
         ir,
         quote! {
-           Record {
-               rs_name: "DerivedClass",
-               cc_name: "DerivedClass",
-               mangled_cc_name: "12DerivedClass",
-               id: ItemId(...),
-               owning_target: BazelLabel("//test:testing_target"),
-               doc_comment: Some(...),
-               source_loc: "Generated from: google3/ir_from_cc_virtual_header.h;l=15",
-               unambiguous_public_bases: [],
-               fields: [Field {
-                   identifier: Some("derived_field"), ...
-                   offset: 32, ...
-               }], ...
-               size: 8,
-               original_cc_size: 8,
-               alignment: 4,
-               is_derived_class: true,
-               override_alignment: true,
-               ...
-           }
-        }
+               Record {
+                   rs_name: "DerivedClass",
+                   cc_name: "DerivedClass",
+                   mangled_cc_name: "12DerivedClass",
+                   id: ItemId(...),
+                   owning_target: BazelLabel("//test:testing_target"),
+                   defining_target: None,
+                   doc_comment: Some(...),
+                   source_loc: "Generated from: google3/ir_from_cc_virtual_header.h;l=15",
+                   unambiguous_public_bases: [],
+                   fields: [Field {
+                       identifier: Some("derived_field"), ...
+                       offset: 32, ...
+                   }], ...
+                   size: 8,
+                   original_cc_size: 8,
+                   alignment: 4,
+                   is_derived_class: true,
+                   override_alignment: true,
+                   ...
+               }
+            }
     );
     // Verify that the NestedStruct is unsupported (this is mostly verification
     // that the test input correctly sets up the test scenario;  the real
diff --git a/rs_bindings_from_cc/src_code_gen.rs b/rs_bindings_from_cc/src_code_gen.rs
index 40abda4..87aa469 100644
--- a/rs_bindings_from_cc/src_code_gen.rs
+++ b/rs_bindings_from_cc/src_code_gen.rs
@@ -289,7 +289,7 @@
         return false;
     }
 
-    // ## Returning structs be value.
+    // ## Returning structs by value.
     //
     // Returning a struct by value requires an explicit thunk, because
     // `rs_bindings_from_cc` may not preserve the ABI of structs (e.g. when
@@ -2810,13 +2810,15 @@
 #[must_use]
 fn has_bindings(db: &dyn BindingsGenerator, item: &Item) -> HasBindings {
     let ir = db.ir();
-    if let Some(defining_target) = item.defining_target() {
-        let missing_features =
-            crubit_features_for_item(item) - ir.target_crubit_features(defining_target);
+    // We refuse to generate bindings if either the definition of an item, or
+    // instantiation (if it is a template) an item are in a translation unit which
+    // doesn't have the required Crubit features.
+    for target in item.defining_target().into_iter().chain(item.owning_target()) {
+        let missing_features = crubit_features_for_item(item) - ir.target_crubit_features(target);
         if !missing_features.is_empty() {
             return HasBindings::No(NoBindingsReason::MissingRequiredFeatures {
                 missing_features,
-                target: defining_target.clone(),
+                target: target.clone(),
             });
         }
     }
diff --git a/rs_bindings_from_cc/test/crubit_features/test.rs b/rs_bindings_from_cc/test/crubit_features/test.rs
index ce690f1..9690334 100644
--- a/rs_bindings_from_cc/test/crubit_features/test.rs
+++ b/rs_bindings_from_cc/test/crubit_features/test.rs
@@ -41,7 +41,6 @@
     /// in other headers should respect the template _definition_ and its
     /// API promises.
     #[test]
-    #[ignore] // TODO(b/266727458): implement this
     fn aliases_dont_expose_disabled_templates() {
         assert!(
             !type_exists!(alias_enabled::AliasedDisabledTemplate),