Support converting attr to starlark values Bug: 528225104 Change-Id: I09b8cb5762f99fd217ffc951905328246a6a6964 Reviewed-on: https://gn-review.googlesource.com/c/gn/+/23820 Reviewed-by: Cole Faust <colefaust@google.com> Reviewed-by: Philipp Wollermann <philwo@google.com> Commit-Queue: Matt Stark <msta@google.com> Reviewed-by: Matt Stark <msta@google.com>
diff --git a/src/gn/starlark/Cargo.lock b/src/gn/starlark/Cargo.lock index dc75e26..52ad4e0 100644 --- a/src/gn/starlark/Cargo.lock +++ b/src/gn/starlark/Cargo.lock
@@ -116,6 +116,7 @@ "either", "starlark", "starlark_derive", + "testutils", "thiserror", "types", ]
diff --git a/src/gn/starlark/crates/attr/Cargo.toml b/src/gn/starlark/crates/attr/Cargo.toml index 32ded57..56b9433 100644 --- a/src/gn/starlark/crates/attr/Cargo.toml +++ b/src/gn/starlark/crates/attr/Cargo.toml
@@ -16,3 +16,6 @@ allocative = { workspace = true } types = { path = "../types" } either = { workspace = true } + +[dev-dependencies] +testutils = { path = "../testutils" }
diff --git a/src/gn/starlark/crates/attr/src/allow_files.rs b/src/gn/starlark/crates/attr/src/allow_files.rs index fca90f8..14c3295 100644 --- a/src/gn/starlark/crates/attr/src/allow_files.rs +++ b/src/gn/starlark/crates/attr/src/allow_files.rs
@@ -42,14 +42,14 @@ } impl AllowFiles { - pub(crate) fn validate(&self, path: &str) -> starlark::Result<()> { + pub(crate) fn matches(&self, path: &str) -> bool { match self { - Self::None => Err(crate::Error::NotALabel(path.to_owned()).into()), - Self::All => Ok(()), + Self::None => false, + Self::All => true, Self::Some(exts) => { let p = std::path::Path::new(path); let file_name = p.file_name().and_then(|e| e.to_str()).unwrap_or(""); - if exts.iter().any(|ext| { + exts.iter().any(|ext| { if ext.starts_with('.') { file_name.ends_with(ext) } else { @@ -57,11 +57,21 @@ .strip_suffix(ext) .is_some_and(|prefix| prefix.is_empty() || prefix.ends_with('.')) } - }) { + }) + }, + } + } + + pub(crate) fn validate(&self, path: &str) -> starlark::Result<()> { + match self { + Self::None => Err(crate::Error::NotALabel(path.to_owned()).into()), + Self::All => Ok(()), + Self::Some(exts) => { + if self.matches(path) { Ok(()) } else { Err(crate::Error::DisallowedExtension { - file: p.to_path_buf(), + file: std::path::Path::new(path).to_path_buf(), allowed: exts.clone(), } .into())
diff --git a/src/gn/starlark/crates/attr/src/errors.rs b/src/gn/starlark/crates/attr/src/errors.rs index f697bdd..f6f2e8e 100644 --- a/src/gn/starlark/crates/attr/src/errors.rs +++ b/src/gn/starlark/crates/attr/src/errors.rs
@@ -5,6 +5,7 @@ use std::path::PathBuf; use starlark::values::UnpackValueError; +use types::Label; /// Errors returned by target attribute validation and coercion. #[derive(thiserror::Error, Debug, Clone)] @@ -33,6 +34,12 @@ DuplicateLabel(crate::LabelOrFile), #[error("got {0}, want value in signed 32-bit range")] Int32Expected(i64), + #[error("target `{0}` must produce a single output file")] + MustProduceSingleFile(Label), + #[error( + "target `{target}` does not produce any outputs matching allowed extensions: {allowed:?}" + )] + NoMatchingOutputs { target: Label, allowed: Vec<String> }, } impl From<Error> for starlark::Error {
diff --git a/src/gn/starlark/crates/attr/src/lib.rs b/src/gn/starlark/crates/attr/src/lib.rs index c3819d6..93bb68a 100644 --- a/src/gn/starlark/crates/attr/src/lib.rs +++ b/src/gn/starlark/crates/attr/src/lib.rs
@@ -9,6 +9,7 @@ pub mod globals; pub mod schema; pub mod traits; +pub mod value; pub use allow_files::AllowFiles; pub use attr::{Attr, LabelOrFile};
diff --git a/src/gn/starlark/crates/attr/src/value.rs b/src/gn/starlark/crates/attr/src/value.rs new file mode 100644 index 0000000..485a6d0 --- /dev/null +++ b/src/gn/starlark/crates/attr/src/value.rs
@@ -0,0 +1,694 @@ +// Copyright 2026 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +use allocative::Allocative; +use starlark::{ + collections::{SmallMap, SmallSet}, + values::{dict::Dict, Heap, Value}, +}; +use types::{File, LabelRef, TargetRef as _}; + +use crate::{schema::AttrSchema, Attr, Session}; + +/// AttrValue is the value that will appear in ctx.(attr|file|files).foo +#[derive(Clone, Debug, Allocative)] +pub struct AttrValue<'v> { + /// A single resolved `File` value. + /// Only present if allow_single_file is True. + /// If allow_single_file is true, is either a file or the *starlark* none. + pub file: Option<Value<'v>>, + /// A list of resolved `File` values representing all files resolved for + /// this attribute. Only present for attr.label[_list] attrs. + pub files: Option<Value<'v>>, + /// The coerced value of the attribute itself. + pub attr: Value<'v>, +} + +impl Attr { + /// Resolves the attribute value against the session to collect any provided + /// files and outputs an `AttrValue`. + pub fn to_value<'v, S: Session>( + &self, + schema: &AttrSchema, + session: &S, + current_toolchain: &LabelRef, + heap: &Heap<'v>, + ) -> starlark::Result<AttrValue<'v>> { + let mut unique_files = SmallSet::new(); + let attr = + self.resolve_and_collect(schema, session, current_toolchain, heap, &mut unique_files)?; + + let files_list: Vec<Value<'v>> = unique_files.into_iter().map(|f| heap.alloc(f)).collect(); + + Ok(AttrValue { + attr, + file: match &schema.allow_files { + crate::AllowFilesSchema::Single(_) => { + if files_list.len() == 1 { + Some(files_list[0]) + } else { + match self { + Self::Label(None) => Some(Value::new_none()), + Self::Label(Some(crate::LabelOrFile::Label(lbl))) => { + return Err(starlark::Error::new_other( + crate::Error::MustProduceSingleFile(lbl.clone()), + )); + }, + _ => unreachable!( + "files_list.len() != 1 is only possible for Label attributes" + ), + } + } + }, + _ => None, + }, + files: match schema.kind { + crate::schema::AttrKind::Label | crate::schema::AttrKind::LabelList => { + Some(heap.alloc(files_list)) + }, + _ => None, + }, + }) + } + + fn resolve_and_collect<'v, S: Session>( + &self, + schema: &AttrSchema, + session: &S, + current_toolchain: &LabelRef, + heap: &Heap<'v>, + files: &mut SmallSet<File>, + ) -> starlark::Result<Value<'v>> { + match self { + Self::Bool(b) => Ok(Value::new_bool(*b)), + Self::Int(i) => Ok(heap.alloc(*i)), + Self::String(s) => Ok(heap.alloc(s.as_str())), + Self::IntList(l) => Ok(heap.alloc(l.clone())), + Self::StringList(l) => Ok(heap.alloc(l.clone())), + Self::StringListDict(d) => { + let mut res = SmallMap::with_capacity(d.len()); + for (k, v) in d { + res.insert_hashed( + heap.alloc(k.as_str()).get_hashed().unwrap(), + heap.alloc(v.clone()), + ); + } + Ok(heap.alloc(Dict::new(res))) + }, + Self::Label(None) => Ok(Value::new_none()), + Self::Label(Some(lf)) => { + Self::resolve_label_or_file(lf, schema, session, current_toolchain, heap, files) + }, + Self::LabelList(l) => { + let resolved_list = l + .iter() + .map(|lf| { + Self::resolve_label_or_file( + lf, + schema, + session, + current_toolchain, + heap, + files, + ) + }) + .collect::<starlark::Result<Vec<_>>>()?; + Ok(heap.alloc(resolved_list)) + }, + Self::StringDict(d) => { + let mut res = SmallMap::with_capacity(d.len()); + for (k, v) in d { + res.insert_hashed( + heap.alloc(k.as_str()).get_hashed().unwrap(), + heap.alloc(v.as_str()), + ); + } + Ok(heap.alloc(Dict::new(res))) + }, + Self::LabelKeyedStringDict(d) => { + let mut res = SmallMap::with_capacity(d.len()); + for (k, v) in d { + res.insert_hashed( + Self::resolve_label_or_file( + k, + schema, + session, + current_toolchain, + heap, + files, + )? + .get_hashed()?, + heap.alloc(v.as_str()), + ); + } + Ok(heap.alloc(Dict::new(res))) + }, + Self::StringKeyedLabelDict(d) => { + let mut res = SmallMap::with_capacity(d.len()); + for (k, v) in d { + res.insert_hashed( + heap.alloc(k.as_str()).get_hashed().unwrap(), + Self::resolve_label_or_file( + v, + schema, + session, + current_toolchain, + heap, + files, + )?, + ); + } + Ok(heap.alloc(Dict::new(res))) + }, + Self::LabelListDict(d) => { + let mut res = SmallMap::with_capacity(d.len()); + for (k, v) in d { + let resolved_list = v + .iter() + .map(|lf| { + Self::resolve_label_or_file( + lf, + schema, + session, + current_toolchain, + heap, + files, + ) + }) + .collect::<starlark::Result<Vec<_>>>()?; + res.insert_hashed( + heap.alloc(k.as_str()).get_hashed().unwrap(), + heap.alloc(resolved_list), + ); + } + Ok(heap.alloc(Dict::new(res))) + }, + } + } + + /// Resolves a label or file object: + /// * If it's a label, resolves it to a target. + /// * For attr.label[_list], DefaultInfo.files is expanded into files. + /// * If it's a file, returns itself and collects the file to files. + fn resolve_label_or_file<'v, S: Session>( + lf: &crate::LabelOrFile, + schema: &AttrSchema, + session: &S, + current_toolchain: &LabelRef, + heap: &Heap<'v>, + files: &mut SmallSet<File>, + ) -> starlark::Result<Value<'v>> { + match lf { + crate::LabelOrFile::Label(lbl) => { + let target = session.get_target(lbl.as_ref(), *current_toolchain); + if let Some(matcher) = schema.file_matcher() { + match matcher { + crate::allow_files::AllowFiles::Some(exts) => { + if !target.outputs().iter().any(|f| matcher.matches(f.as_str())) { + return Err(starlark::Error::new_other( + crate::Error::NoMatchingOutputs { + target: lbl.clone(), + allowed: exts.clone(), + }, + )); + } + }, + _ => {}, + } + } + for f in target.outputs() { + files.insert(f.clone()); + } + Ok(heap.alloc(target)) + }, + crate::LabelOrFile::File(f) => { + if let Some(matcher) = schema.file_matcher() { + matcher.validate(f.as_str())?; + } + files.insert(f.clone()); + Ok(heap.alloc(f.clone())) + }, + } + } +} + +#[cfg(test)] +mod tests { + use std::collections::HashSet; + + use starlark::{ + environment::Module, + values::{list::UnpackList, UnpackValue as _, ValueLike as _}, + }; + use testutils::{FakeSession, FakeTarget, FakeTargetRef}; + use types::{Label, PackageRef}; + + use super::*; + use crate::{ + allow_files::AllowFiles, + cfg::AttrCfg, + schema::{AllowFilesSchema, AttrKind}, + }; + + #[test] + fn test_to_value_basic() { + let session = FakeSession::new(); + let schema = AttrSchema { + kind: AttrKind::Bool, + default: None, + disallow_empty: false, + allow_files: AllowFilesSchema::None, + cfg: AttrCfg::CurrentToolchain, + doc: String::new(), + }; + + Module::with_temp_heap(|module| { + let heap = module.heap(); + let AttrValue { attr, file, files } = Attr::Bool(true) + .to_value( + &schema, + &session, + &session.default_toolchain.as_ref(), + &heap, + ) + .unwrap(); + + assert!(attr.unpack_bool().unwrap()); + assert!(file.is_none()); + assert!(files.is_none()); + }); + } + + #[test] + fn test_to_value_label_no_files() { + let session = FakeSession::new(); + let target_label = Label::new( + PackageRef::new("//foo").unwrap().to_owned(), + "bar".to_owned(), + ); + + let schema = AttrSchema { + kind: AttrKind::Label, + default: None, + disallow_empty: false, + allow_files: AllowFilesSchema::None, + cfg: AttrCfg::CurrentToolchain, + doc: String::new(), + }; + + Module::with_temp_heap(|module| { + let heap = module.heap(); + let AttrValue { attr, file, files } = + Attr::Label(Some(crate::LabelOrFile::Label(target_label.clone()))) + .to_value( + &schema, + &session, + &session.default_toolchain.as_ref(), + &heap, + ) + .unwrap(); + + // The resolved value should be the Target object + let resolved_target = attr.downcast_ref::<FakeTargetRef>().unwrap(); + assert!(resolved_target.registered_deps().is_empty()); + assert!(resolved_target.outputs().is_empty()); + assert!(file.is_none()); + + assert_eq!( + UnpackList::<&File>::unpack_value_err(files.unwrap()) + .unwrap() + .items, + Vec::<&File>::new() + ); + + // Target has outputs -> they should be collected to files even when allow_files + // is None. + let file1 = File::new("foo.txt"); + session.insert_target( + target_label.clone(), + FakeTargetRef::new(FakeTarget { + outputs: vec![file1.clone()], + ..Default::default() + }), + ); + + let AttrValue { files, file, .. } = + Attr::Label(Some(crate::LabelOrFile::Label(target_label.clone()))) + .to_value( + &schema, + &session, + &session.default_toolchain.as_ref(), + &heap, + ) + .unwrap(); + + assert_eq!( + UnpackList::<&File>::unpack_value_err(files.unwrap()) + .unwrap() + .items, + vec![&file1] + ); + assert_eq!(file, None) + }); + } + + #[test] + fn test_to_value_label_allow_files_many() { + let session = FakeSession::new(); + let target_label = Label::new( + PackageRef::new("//foo").unwrap().to_owned(), + "bar".to_owned(), + ); + let label_only_file = File::new("label_only.cc"); + let overlap = File::new("overlap.cc"); + let file_only_file = File::new("file_only.cc"); + + // Target outputs out.cc and overlap.h + let dep = FakeTargetRef::new(FakeTarget { + outputs: vec![label_only_file.clone(), overlap.clone()], + ..Default::default() + }); + session.insert_target(target_label.clone(), dep.clone()); + + let schema = AttrSchema { + kind: AttrKind::LabelList, + default: None, + disallow_empty: false, + allow_files: AllowFilesSchema::Many(AllowFiles::All), + cfg: AttrCfg::CurrentToolchain, + doc: String::new(), + }; + + Module::with_temp_heap(|module| { + let heap = module.heap(); + let AttrValue { attr, file, files } = Attr::LabelList(vec![ + crate::LabelOrFile::Label(target_label.clone()), + crate::LabelOrFile::File(file_only_file.clone()), + crate::LabelOrFile::File(overlap.clone()), + ]) + .to_value( + &schema, + &session, + &session.default_toolchain.as_ref(), + &heap, + ) + .unwrap(); + + let attr_list = UnpackList::<Value>::unpack_value_err(attr).unwrap().items; + assert_eq!(attr_list.len(), 3); + assert_eq!( + <&FakeTargetRef>::unpack_value_err(attr_list[0]).unwrap(), + &dep + ); + assert_eq!( + <&File>::unpack_value_err(attr_list[1]).unwrap(), + &file_only_file + ); + assert_eq!(<&File>::unpack_value_err(attr_list[2]).unwrap(), &overlap); + + assert!(file.is_none()); + // Verifying files resolved correctly: target outputs + the direct file (with + // overlap deduplicated!) + assert_eq!( + UnpackList::<&File>::unpack_value_err(files.unwrap()) + .unwrap() + .items, + vec![&label_only_file, &overlap, &file_only_file] + ); + }); + } + + #[test] + fn test_to_value_label_allow_files_single() { + let session = FakeSession::new(); + let target_label = Label::new( + PackageRef::new("//foo").unwrap().to_owned(), + "bar".to_owned(), + ); + let file1 = File::new("out.cc"); + + let schema = AttrSchema { + kind: AttrKind::Label, + default: None, + disallow_empty: false, + allow_files: AllowFilesSchema::Single(AllowFiles::All), + cfg: AttrCfg::CurrentToolchain, + doc: String::new(), + }; + + Module::with_temp_heap(|module| { + let heap = module.heap(); + + // Target has exactly 1 output file -> succeeds + session.insert_target( + target_label.clone(), + FakeTargetRef::new(FakeTarget { + outputs: vec![file1.clone()], + ..Default::default() + }), + ); + + let path_resolver = types::PathResolver::new_for_testing(); + let starlark_val = heap.alloc(":bar"); + let attr = Attr::create( + &schema, + Some(starlark_val), + PackageRef::new("//foo").unwrap(), + &path_resolver, + ) + .unwrap(); + + let source_target = FakeTargetRef::default(); + attr.register_dependencies( + &session, + source_target.clone(), + session.default_toolchain.as_ref(), + ); + + assert_eq!( + source_target.registered_deps(), + HashSet::from([(target_label.clone(), session.default_toolchain.clone())]) + ); + + let AttrValue { + file, + files, + attr: resolved_attr, + } = attr + .to_value( + &schema, + &session, + &session.default_toolchain.as_ref(), + &heap, + ) + .unwrap(); + + let file_val = file.unwrap(); + let single_file = file_val.downcast_ref::<File>().unwrap(); + assert_eq!(single_file, &file1); + + assert_eq!( + UnpackList::<&File>::unpack_value_err(files.unwrap()) + .unwrap() + .items, + vec![&file1] + ); + + let resolved_target = resolved_attr.downcast_ref::<FakeTargetRef>().unwrap(); + assert_eq!(resolved_target.outputs(), vec![file1.clone()]); + + // Direct File -> succeeds + let AttrValue { + file, + files, + attr: _, + } = Attr::Label(Some(crate::LabelOrFile::File(file1.clone()))) + .to_value( + &schema, + &session, + &session.default_toolchain.as_ref(), + &heap, + ) + .unwrap(); + + let file_val = file.unwrap(); + let single_file = file_val.downcast_ref::<File>().unwrap(); + assert_eq!(single_file, &file1); + + assert_eq!( + UnpackList::<&File>::unpack_value_err(files.unwrap()) + .unwrap() + .items, + vec![&file1] + ); + + // Target has 2 outputs -> fails + session.insert_target( + target_label.clone(), + FakeTargetRef::new(FakeTarget { + outputs: vec![file1.clone(), File::new("out.h")], + attrs: vec![], + ..Default::default() + }), + ); + + let res = Attr::Label(Some(crate::LabelOrFile::Label(target_label.clone()))).to_value( + &schema, + &session, + &session.default_toolchain.as_ref(), + &heap, + ); + assert_eq!( + res.unwrap_err().to_string(), + "target `//foo:bar` must produce a single output file" + ); + + // Target has no outputs -> fails + session.insert_target( + target_label.clone(), + FakeTargetRef::new(FakeTarget { + outputs: vec![], + ..Default::default() + }), + ); + + let res_empty = Attr::Label(Some(crate::LabelOrFile::Label(target_label.clone()))) + .to_value( + &schema, + &session, + &session.default_toolchain.as_ref(), + &heap, + ); + assert_eq!( + res_empty.unwrap_err().to_string(), + "target `//foo:bar` must produce a single output file" + ); + }); + } + + #[test] + fn test_to_value_label_keyed_string_dict() { + let session = FakeSession::new(); + let target_label = Label::new( + PackageRef::new("//foo").unwrap().to_owned(), + "bar".to_owned(), + ); + + let schema = AttrSchema { + kind: AttrKind::LabelKeyedStringDict, + default: None, + disallow_empty: false, + allow_files: AllowFilesSchema::None, + cfg: AttrCfg::CurrentToolchain, + doc: String::new(), + }; + + Module::with_temp_heap(|module| { + let heap = module.heap(); + session.insert_target( + target_label.clone(), + FakeTargetRef::new(FakeTarget { + outputs: vec![], + ..Default::default() + }), + ); + + let mut dict = SmallMap::new(); + dict.insert( + crate::LabelOrFile::Label(target_label.clone()), + "value1".to_owned(), + ); + + let AttrValue { attr, file, files } = Attr::LabelKeyedStringDict(dict) + .to_value( + &schema, + &session, + &session.default_toolchain.as_ref(), + &heap, + ) + .unwrap(); + + assert!(file.is_none()); + assert!(files.is_none()); + + let resolved_dict = starlark::values::dict::DictRef::from_value(attr).unwrap(); + let keys: Vec<Value> = resolved_dict.iter().map(|(k, _)| k).collect(); + assert_eq!(keys.len(), 1); + let resolved_target = keys[0].downcast_ref::<FakeTargetRef>().unwrap(); + assert_eq!(resolved_target.outputs(), Vec::<File>::new()); + }); + } + + #[test] + fn test_to_value_label_allow_files_matching() { + let session = FakeSession::new(); + let target_label = Label::new( + PackageRef::new("//foo").unwrap().to_owned(), + "bar".to_owned(), + ); + + let schema = AttrSchema { + kind: AttrKind::Label, + default: None, + disallow_empty: false, + allow_files: AllowFilesSchema::Many(AllowFiles::Some(vec![".cc".to_owned()])), + cfg: AttrCfg::CurrentToolchain, + doc: String::new(), + }; + + Module::with_temp_heap(|module| { + let heap = module.heap(); + + // Target outputs foo.cc and foo.h -> succeeds (at least one matches) and both + // files are collected + let file1 = File::new("foo.cc"); + let file2 = File::new("foo.h"); + session.insert_target( + target_label.clone(), + FakeTargetRef::new(FakeTarget { + outputs: vec![file1.clone(), file2.clone()], + ..Default::default() + }), + ); + + let AttrValue { files, .. } = + Attr::Label(Some(crate::LabelOrFile::Label(target_label.clone()))) + .to_value( + &schema, + &session, + &session.default_toolchain.as_ref(), + &heap, + ) + .unwrap(); + + assert_eq!( + UnpackList::<&File>::unpack_value_err(files.unwrap()) + .unwrap() + .items, + vec![&file1, &file2] + ); + + // Target outputs only foo.h -> fails (no matching outputs) + session.insert_target( + target_label.clone(), + FakeTargetRef::new(FakeTarget { + outputs: vec![file2.clone()], + ..Default::default() + }), + ); + + let res = Attr::Label(Some(crate::LabelOrFile::Label(target_label.clone()))).to_value( + &schema, + &session, + &session.default_toolchain.as_ref(), + &heap, + ); + + assert_eq!( + res.unwrap_err().to_string(), + "target `//foo:bar` does not produce any outputs matching allowed extensions: [\".cc\"]" + ); + }); + } +}
diff --git a/src/gn/starlark/crates/testutils/src/target.rs b/src/gn/starlark/crates/testutils/src/target.rs index a60a968..d0be24c 100644 --- a/src/gn/starlark/crates/testutils/src/target.rs +++ b/src/gn/starlark/crates/testutils/src/target.rs
@@ -4,6 +4,7 @@ use std::{ collections::HashSet, + hash::Hasher, sync::{Arc, Mutex}, }; @@ -11,10 +12,10 @@ use attr::Attr; use starlark::{ starlark_simple_value, - values::{ProvidesStaticType, StarlarkValue}, + values::{ProvidesStaticType, StarlarkValue, Value, ValueLike}, }; use starlark_derive::{starlark_value, NoSerialize}; -use types::{File, Label, TargetRef}; +use types::{File, IPromiseToImplementStarlarkEqAndHash, Label, TargetRef}; /// A fake target struct for testing. #[derive(Debug, Allocative, Default)] @@ -76,8 +77,27 @@ } } +impl IPromiseToImplementStarlarkEqAndHash for FakeTargetRef {} + #[starlark_value(type = "Target")] -impl<'v> StarlarkValue<'v> for FakeTargetRef {} +impl<'v> StarlarkValue<'v> for FakeTargetRef { + fn equals(&self, other: Value<'v>) -> starlark::Result<bool> { + if let Some(other) = other.downcast_ref::<Self>() { + Ok(Arc::ptr_eq(&self.0, &other.0)) + } else { + Ok(false) + } + } + + fn write_hash( + &self, + hasher: &mut starlark::collections::StarlarkHasher, + ) -> starlark::Result<()> { + let ptr = Arc::as_ptr(&self.0) as usize; + hasher.write_usize(ptr); + Ok(()) + } +} impl TargetRef for FakeTargetRef { fn outputs(&self) -> Vec<File> {
diff --git a/src/gn/starlark/crates/types/src/lib.rs b/src/gn/starlark/crates/types/src/lib.rs index 40e565f..5a9d617 100644 --- a/src/gn/starlark/crates/types/src/lib.rs +++ b/src/gn/starlark/crates/types/src/lib.rs
@@ -26,5 +26,5 @@ pub use package_ref::PackageRef; pub use path_resolver::PathResolver; pub use session::Session; -pub use target_ref::TargetRef; +pub use target_ref::{IPromiseToImplementStarlarkEqAndHash, TargetRef}; pub use unpacked_owned_value::UnpackedOwnedValue;
diff --git a/src/gn/starlark/crates/types/src/target_ref.rs b/src/gn/starlark/crates/types/src/target_ref.rs index 30f4f42..bdbcf40 100644 --- a/src/gn/starlark/crates/types/src/target_ref.rs +++ b/src/gn/starlark/crates/types/src/target_ref.rs
@@ -6,11 +6,18 @@ use crate::File; +/// Unfortunately while we could specify that Eq and Hash are implemented, there +/// is no way to delegate starlark's equality and hash function to it +/// automatically. +pub trait IPromiseToImplementStarlarkEqAndHash {} + /// An interface for a target in the build graph. /// /// Since the real Target involves a lot of C++ interop, this allows us to /// decouple the target from C++ -pub trait TargetRef: for<'v> StarlarkValue<'v> + for<'v> AllocValue<'v> + Clone { +pub trait TargetRef: + for<'v> StarlarkValue<'v> + for<'v> AllocValue<'v> + Clone + IPromiseToImplementStarlarkEqAndHash +{ /// Returns the output files produced by this target. fn outputs(&self) -> Vec<File>;