mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Fold attribute names out of their constant inputs when the graph compiles
`compute_layouts` resolves every name-from-input write against the text its name input carries, so a folded meta is indistinguishable from a marker node's and the layout holds a `&'static str` from there on. That leaves nowhere for a name to be computed, which is the whole of the rule: a name input that is not a constant is refused right here, per node, with the path the editor pins its diagnostic to. One name carries one value type, checked across the census and the names a node folds together, so no graph can declare one field at two widths. `compute_layouts` returns those refusals rather than panicking. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -67,6 +67,7 @@ fn inputs(parsed: &ParsedNodeFn, fields: &[&ParsedField], generics: &[Ident]) ->
|
||||
shape: item_shape(&element, depth, &field.attribute_reads, generics),
|
||||
subject: subject(index, field, carrier_subject, routing.as_ref()),
|
||||
lend: matches!(&field.ty, ParsedFieldType::Regular(RegularParsedField { lend: Some(_), .. })),
|
||||
name_source: crate::parsing::named_source(&element),
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
@@ -274,6 +275,7 @@ pub(crate) fn layout_meta_tokens(node: &Node, element_spec: TokenStream2, core_t
|
||||
quote!(#core_types::record::InputReads { input: #index, reads: ::std::vec![#(#descs),*] })
|
||||
});
|
||||
let writes = field_writes(&node.output.shape.attrs, core_types);
|
||||
let named_writes = named_field_writes(node, core_types);
|
||||
let removes = node.output.removes.iter().map(|attr| {
|
||||
let marker = &attr.marker;
|
||||
let level = attr.level;
|
||||
@@ -290,6 +292,8 @@ pub(crate) fn layout_meta_tokens(node: &Node, element_spec: TokenStream2, core_t
|
||||
reads: ::std::vec![#(#reads),*],
|
||||
element: #element_spec,
|
||||
writes: ::std::vec![#(#writes),*],
|
||||
named_writes: ::std::vec![#(#named_writes),*],
|
||||
folded_names: ::std::vec![],
|
||||
removes: ::std::vec![#(#removes),*],
|
||||
level_delta: #level_delta,
|
||||
folded: #folded,
|
||||
@@ -354,6 +358,7 @@ pub(crate) fn folded_subject(node: &Node) -> Option<(u8, u8)> {
|
||||
fn field_writes(attrs: &[LevelAttr], core_types: &TokenStream2) -> Vec<TokenStream2> {
|
||||
attrs
|
||||
.iter()
|
||||
.filter(|attr| crate::parsing::named_marker(&attr.marker).is_none())
|
||||
.map(|attr| {
|
||||
let marker = &attr.marker;
|
||||
let level = attr.level;
|
||||
@@ -362,6 +367,30 @@ fn field_writes(attrs: &[LevelAttr], core_types: &TokenStream2) -> Vec<TokenStre
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Emits one `NamedWrite` per name-generic write, pairing the template minted
|
||||
/// from the concrete value type with the input its placeholder's name sits at.
|
||||
fn named_field_writes(node: &Node, core_types: &TokenStream2) -> Vec<TokenStream2> {
|
||||
node.output
|
||||
.shape
|
||||
.attrs
|
||||
.iter()
|
||||
.filter_map(|attr| {
|
||||
let (placeholder, value) = crate::parsing::named_marker(&attr.marker)?;
|
||||
let input = name_input(node, &placeholder)? as u8;
|
||||
let level = attr.level;
|
||||
Some(quote!(#core_types::record::NamedWrite::of::<#placeholder, #value>(#input, #level)))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// The input position carrying `placeholder`'s name, which is the parameter
|
||||
/// declared at that placeholder.
|
||||
pub(crate) fn name_input(node: &Node, placeholder: &Type) -> Option<usize> {
|
||||
node.inputs
|
||||
.iter()
|
||||
.position(|input| input.name_source.as_ref().is_some_and(|declared| declared == placeholder))
|
||||
}
|
||||
|
||||
fn level_delta(node: &Node) -> i8 {
|
||||
// A folded subject contributes no base layout, so the delta is relative to
|
||||
// the fresh (empty) base.
|
||||
@@ -527,6 +556,10 @@ pub(crate) struct Input {
|
||||
pub(crate) subject: bool,
|
||||
/// Written `&T`; the kernel borrows the evaluated element.
|
||||
pub(crate) lend: bool,
|
||||
/// The placeholder this input names, written `Named<X>`. Such an input
|
||||
/// carries constant text the compiler folds into a layout name, so it is
|
||||
/// never evaluated per lane.
|
||||
pub(crate) name_source: Option<Type>,
|
||||
}
|
||||
|
||||
/// `Lazy` = `impl Node<..>`, the kernel drives it.
|
||||
|
||||
@@ -110,6 +110,42 @@ pub(crate) fn remove_attr_marker(ty: &Type) -> Option<Type> {
|
||||
marker_of(ty, "RemoveAttr")
|
||||
}
|
||||
|
||||
/// Splits a `Named<X, V>` marker into its placeholder and value type. A write
|
||||
/// of one takes its name from the input the placeholder is declared at rather
|
||||
/// than from the marker, so the name folds at graph compile time.
|
||||
pub(crate) fn named_marker(ty: &Type) -> Option<(Type, Type)> {
|
||||
let mut args = named_arguments(ty)?.into_iter();
|
||||
let (placeholder, value) = (args.next()?, args.next()?);
|
||||
args.next().is_none().then_some((placeholder, value))
|
||||
}
|
||||
|
||||
/// The placeholder a `Named<X>` parameter declares. Such a parameter is the
|
||||
/// name source for every `Attr<Named<X, _>>` the signature writes, and crosses
|
||||
/// the wire as constant text.
|
||||
pub(crate) fn named_source(ty: &Type) -> Option<Type> {
|
||||
let mut args = named_arguments(ty)?.into_iter();
|
||||
let placeholder = args.next()?;
|
||||
args.next().is_none().then_some(placeholder)
|
||||
}
|
||||
|
||||
fn named_arguments(ty: &Type) -> Option<Vec<Type>> {
|
||||
let Type::Path(path) = ty else { return None };
|
||||
let segment = path.path.segments.last()?;
|
||||
if segment.ident != "Named" {
|
||||
return None;
|
||||
}
|
||||
let PathArguments::AngleBracketed(args) = &segment.arguments else { return None };
|
||||
Some(
|
||||
args.args
|
||||
.iter()
|
||||
.filter_map(|argument| match argument {
|
||||
GenericArgument::Type(ty) => Some(ty.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
fn marker_of(ty: &Type, wrapper: &str) -> Option<Type> {
|
||||
let Type::Path(path) = ty else { return None };
|
||||
let segment = path.path.segments.last()?;
|
||||
|
||||
Reference in New Issue
Block a user