mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-05 08:38:12 +08:00
Delete the Class taxonomy in favor of the intent IR
This commit is contained in:
@@ -12,7 +12,7 @@ use crate::shader_nodes::{ShaderCodegen, ShaderTokens};
|
|||||||
|
|
||||||
mod classify;
|
mod classify;
|
||||||
mod entries;
|
mod entries;
|
||||||
mod ir;
|
pub(crate) mod ir;
|
||||||
mod metadata;
|
mod metadata;
|
||||||
pub(crate) use classify::*;
|
pub(crate) use classify::*;
|
||||||
use entries::entries_tokens;
|
use entries::entries_tokens;
|
||||||
@@ -681,7 +681,7 @@ pub(crate) struct NodeFields<'a> {
|
|||||||
pub(crate) struct_type_params: Vec<Ident>,
|
pub(crate) struct_type_params: Vec<Ident>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn, model: &Option<NodeModel>, fields: NodeFields) -> syn::Result<NodePlan> {
|
pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn, model: &Option<Dialect>, fields: NodeFields) -> syn::Result<NodePlan> {
|
||||||
let core_types = crate_ident.gcore()?;
|
let core_types = crate_ident.gcore()?;
|
||||||
|
|
||||||
let ctx_param = context_param(parsed);
|
let ctx_param = context_param(parsed);
|
||||||
@@ -692,8 +692,8 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
let Some(model) = model.as_ref() else {
|
let Some(model) = model.as_ref() else {
|
||||||
return Ok(NodePlan::default());
|
return Ok(NodePlan::default());
|
||||||
};
|
};
|
||||||
let async_fn = matches!(model.dialect, Dialect::AsyncFn);
|
let async_fn = matches!(*model, Dialect::AsyncFn);
|
||||||
let future_kernel = matches!(model.dialect, Dialect::Future | Dialect::FutureInterrupt);
|
let future_kernel = matches!(*model, Dialect::Future | Dialect::FutureInterrupt);
|
||||||
let async_source = async_fn || future_kernel;
|
let async_source = async_fn || future_kernel;
|
||||||
let node = crate::codegen::ir::build(parsed);
|
let node = crate::codegen::ir::build(parsed);
|
||||||
let kind = crate::codegen::ir::node_kind(&node);
|
let kind = crate::codegen::ir::node_kind(&node);
|
||||||
@@ -826,7 +826,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
(false, None) if flip => syn::parse_quote!(#core_types::record::RecordValue<'__record>),
|
(false, None) if flip => syn::parse_quote!(#core_types::record::RecordValue<'__record>),
|
||||||
(false, None) => slot_value_type(&parsed.output_type),
|
(false, None) => slot_value_type(&parsed.output_type),
|
||||||
};
|
};
|
||||||
let raw_lazy = matches!(model.dialect, Dialect::Poll);
|
let raw_lazy = matches!(*model, Dialect::Poll);
|
||||||
let injected_name = |ident: &Ident| async_source && (ident == "_runtime" || ident == "_source");
|
let injected_name = |ident: &Ident| async_source && (ident == "_runtime" || ident == "_source");
|
||||||
let where_predicates: Vec<TokenStream2> = parsed.where_clause.iter().flat_map(|clause| clause.predicates.iter()).map(|predicate| quote!(#predicate)).collect();
|
let where_predicates: Vec<TokenStream2> = parsed.where_clause.iter().flat_map(|clause| clause.predicates.iter()).map(|predicate| quote!(#predicate)).collect();
|
||||||
|
|
||||||
@@ -1294,7 +1294,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
false => quote!(#core_types::node::StatusCell::new()),
|
false => quote!(#core_types::node::StatusCell::new()),
|
||||||
};
|
};
|
||||||
let kernel_call = quote!(self::#fn_name(__input #(, &self.#data_names)* #(, #call_args)*));
|
let kernel_call = quote!(self::#fn_name(__input #(, &self.#data_names)* #(, #call_args)*));
|
||||||
let lift = match model.dialect {
|
let lift = match *model {
|
||||||
Dialect::Interrupt => quote! {
|
Dialect::Interrupt => quote! {
|
||||||
match #kernel_call {
|
match #kernel_call {
|
||||||
Ok(value) => __cell.finish(value),
|
Ok(value) => __cell.finish(value),
|
||||||
@@ -1443,7 +1443,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
true => Vec::new(),
|
true => Vec::new(),
|
||||||
false => reads_of(0).into_iter().map(|(slot, read)| read_binding(slot, read, quote!(__src_rec))).collect(),
|
false => reads_of(0).into_iter().map(|(slot, read)| read_binding(slot, read, quote!(__src_rec))).collect(),
|
||||||
};
|
};
|
||||||
let kernel_value = match model.dialect {
|
let kernel_value = match *model {
|
||||||
Dialect::Interrupt => quote! {
|
Dialect::Interrupt => quote! {
|
||||||
match #record_kernel_call {
|
match #record_kernel_call {
|
||||||
Ok(__value) => __value,
|
Ok(__value) => __value,
|
||||||
@@ -1507,7 +1507,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
let flip_tail = flip.then(|| {
|
let flip_tail = flip.then(|| {
|
||||||
if matches!(model.dialect, Dialect::Poll) {
|
if matches!(*model, Dialect::Poll) {
|
||||||
return match &carried_prelude {
|
return match &carried_prelude {
|
||||||
Some(prelude) => quote! {
|
Some(prelude) => quote! {
|
||||||
#prelude
|
#prelude
|
||||||
@@ -1523,7 +1523,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
let kernel_value = match model.dialect {
|
let kernel_value = match *model {
|
||||||
Dialect::Interrupt => quote! {
|
Dialect::Interrupt => quote! {
|
||||||
match #kernel_call {
|
match #kernel_call {
|
||||||
Ok(value) => value,
|
Ok(value) => value,
|
||||||
@@ -1594,7 +1594,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
),
|
),
|
||||||
None => (quote!(), pending_return.clone()),
|
None => (quote!(), pending_return.clone()),
|
||||||
};
|
};
|
||||||
let acquire = match model.dialect {
|
let acquire = match *model {
|
||||||
Dialect::FutureInterrupt => quote! {
|
Dialect::FutureInterrupt => quote! {
|
||||||
let __future = match #kernel_call {
|
let __future = match #kernel_call {
|
||||||
Ok(future) => future,
|
Ok(future) => future,
|
||||||
|
|||||||
@@ -1,53 +1,32 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// How a record node's primary input lowers.
|
/// How a record node's primary input lowers: `None` writes a fresh record,
|
||||||
|
/// `Token` carries the element bytes through as `ElToken`, `Read` reads a
|
||||||
|
/// concrete element at offset 0. The element and write set fold from the IR.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub(crate) enum RecordCarrier {
|
pub(crate) enum RecordCarrier {
|
||||||
/// `_: ()`: no carrier edge, the kernel writes a fresh record.
|
|
||||||
None,
|
None,
|
||||||
/// An unbounded generic returned in the element position: the element
|
Token,
|
||||||
/// bytes carry through the copy plan and the kernel sees `ElToken`.
|
Read,
|
||||||
Token(Ident),
|
|
||||||
/// An element type read at offset 0, monomorphized per its
|
|
||||||
/// implementations list where generic.
|
|
||||||
Read(Type),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The record io of a node fn: how the carrier lowers, the element write,
|
/// A well-formed record-io node: only the carrier form is retained, so
|
||||||
/// and the markers written and removed. Present exactly when the signature
|
/// [`skips_carrier`] can gate the fresh-record path. Malformed record io yields
|
||||||
/// declares attribute reads or writes in a shape the record tier supports;
|
/// `None` from [`record_shape`] and generates no node impl.
|
||||||
/// malformed record io is reported by validation and generates no node impl.
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub(crate) struct RecordShape {
|
pub(crate) struct RecordShape {
|
||||||
pub(crate) carrier: RecordCarrier,
|
pub(crate) carrier: RecordCarrier,
|
||||||
pub(crate) element_write: Option<Type>,
|
|
||||||
pub(crate) write_markers: Vec<Type>,
|
|
||||||
pub(crate) removes: Vec<Type>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RecordShape {
|
impl RecordShape {
|
||||||
pub(crate) fn skips_carrier(&self) -> bool {
|
pub(crate) fn skips_carrier(&self) -> bool {
|
||||||
matches!(self.carrier, RecordCarrier::None)
|
matches!(self.carrier, RecordCarrier::None)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn carries_element(&self) -> bool {
|
|
||||||
self.element_write.is_none()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The record-tier lowering a node fn resolves to. Exactly one class per node,
|
|
||||||
/// computed once by [`analyze`]; every downstream fragment reads the class
|
|
||||||
/// instead of recomputing the classification predicates.
|
|
||||||
pub(crate) enum Class {
|
|
||||||
RecordIo(RecordShape),
|
|
||||||
Routing(RoutingIo),
|
|
||||||
Flip { carrier: bool },
|
|
||||||
Opaque,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The effect/return axis of a node's kernel, resolved once from the signature.
|
/// The effect/return axis of a node's kernel, resolved once from the signature.
|
||||||
/// Orthogonal to [`Class`]: it selects the eval tail (finish / merge / spawn)
|
/// It selects the eval tail (finish / merge / spawn) and the kernel signature
|
||||||
/// and the kernel signature wrapping across every class.
|
/// wrapping across every node kind.
|
||||||
#[derive(Clone, Copy, PartialEq)]
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
pub(crate) enum Dialect {
|
pub(crate) enum Dialect {
|
||||||
Sync,
|
Sync,
|
||||||
@@ -71,33 +50,22 @@ pub(crate) fn dialect(parsed: &ParsedNodeFn) -> Dialect {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The result of classifying a node fn. A node with no supported lowering
|
/// The dialect of a node fn that lowers to a `Node` impl, or `None` when no
|
||||||
/// (an async node with lazy inputs, malformed record io, or a signature no
|
/// lowering supports the signature (an async node with lazy inputs, malformed
|
||||||
/// class accepts) yields `None` and generates a struct and metadata but no
|
/// record io, or a shape no kind accepts). The kind itself is derived from the
|
||||||
/// `Node` impl.
|
/// intent IR ([`crate::codegen::ir::node_kind`]); this only gates support.
|
||||||
pub(crate) struct NodeModel {
|
pub(crate) fn analyze(parsed: &ParsedNodeFn) -> Option<Dialect> {
|
||||||
pub(crate) class: Class,
|
|
||||||
pub(crate) dialect: Dialect,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn analyze(parsed: &ParsedNodeFn) -> Option<NodeModel> {
|
|
||||||
if parsed.is_async && parsed.fields.iter().any(|field| matches!(field.ty, ParsedFieldType::Node(_))) {
|
if parsed.is_async && parsed.fields.iter().any(|field| matches!(field.ty, ParsedFieldType::Node(_))) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let class = if let Some(shape) = record_shape(parsed) {
|
let supported = if record_shape(parsed).is_some() {
|
||||||
Class::RecordIo(shape)
|
true
|
||||||
} else if has_record_io(parsed) {
|
} else if has_record_io(parsed) {
|
||||||
return None;
|
return None;
|
||||||
} else if let Some(routing) = routing_io(parsed) {
|
|
||||||
Class::Routing(routing)
|
|
||||||
} else if record_flip(parsed) {
|
|
||||||
Class::Flip { carrier: flip_carrier(parsed) }
|
|
||||||
} else if record_opaque(parsed) {
|
|
||||||
Class::Opaque
|
|
||||||
} else {
|
} else {
|
||||||
return None;
|
routing_io(parsed).is_some() || record_flip(parsed) || record_opaque(parsed)
|
||||||
};
|
};
|
||||||
Some(NodeModel { class, dialect: dialect(parsed) })
|
supported.then(|| dialect(parsed))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The tail form of a node's eval, selected from its class and dialect: forward
|
/// The tail form of a node's eval, selected from its class and dialect: forward
|
||||||
@@ -317,43 +285,43 @@ pub(crate) fn record_shape(parsed: &ParsedNodeFn) -> Option<RecordShape> {
|
|||||||
let ParsedFieldType::Regular(RegularParsedField { ty, lend: None, implementations, .. }) = &carrier_field.ty else {
|
let ParsedFieldType::Regular(RegularParsedField { ty, lend: None, implementations, .. }) = &carrier_field.ty else {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
let carrier = match ty {
|
let token = match ty {
|
||||||
Type::Tuple(tuple) if tuple.elems.is_empty() => RecordCarrier::None,
|
Type::Tuple(tuple) if tuple.elems.is_empty() => None,
|
||||||
ty => match implementations.is_empty().then(|| unbounded_generic(parsed, ty)).flatten() {
|
ty => match implementations.is_empty().then(|| unbounded_generic(parsed, ty)).flatten() {
|
||||||
Some(token) => RecordCarrier::Token(token),
|
Some(token) => Some(token),
|
||||||
None => {
|
None => {
|
||||||
if contains_open_generic(parsed, ty) {
|
if contains_open_generic(parsed, ty) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
RecordCarrier::Read(ty.clone())
|
None
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
let (element, write_markers, removes) = match writes {
|
let carrier = match ty {
|
||||||
|
Type::Tuple(tuple) if tuple.elems.is_empty() => RecordCarrier::None,
|
||||||
|
_ if token.is_some() => RecordCarrier::Token,
|
||||||
|
_ => RecordCarrier::Read,
|
||||||
|
};
|
||||||
|
let (element, _, removes) = match writes {
|
||||||
Some(RecordWrites { element, markers, removes }) => (element, markers, removes),
|
Some(RecordWrites { element, markers, removes }) => (element, markers, removes),
|
||||||
None => (value, Vec::new(), Vec::new()),
|
None => (value, Vec::new(), Vec::new()),
|
||||||
};
|
};
|
||||||
let element_write = match &carrier {
|
match &token {
|
||||||
RecordCarrier::Token(token) => match bare_ident(&element) {
|
Some(token) => {
|
||||||
Some(ident) if ident == token => None,
|
if !matches!(bare_ident(&element), Some(ident) if ident == token) {
|
||||||
_ => return None,
|
return None;
|
||||||
},
|
}
|
||||||
_ => {
|
}
|
||||||
|
None => {
|
||||||
if contains_open_generic(parsed, &element) {
|
if contains_open_generic(parsed, &element) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
Some(element)
|
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
if matches!(carrier, RecordCarrier::None) && !removes.is_empty() {
|
if matches!(carrier, RecordCarrier::None) && !removes.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
Some(RecordShape {
|
Some(RecordShape { carrier })
|
||||||
carrier,
|
|
||||||
element_write,
|
|
||||||
write_markers,
|
|
||||||
removes,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn is_poll_kernel(output: &Type) -> bool {
|
pub(crate) fn is_poll_kernel(output: &Type) -> bool {
|
||||||
|
|||||||
@@ -391,7 +391,7 @@ pub(crate) enum Effect {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::codegen::classify::{Class, Dialect, analyze, context_param, dialect};
|
use crate::codegen::classify::{Dialect, analyze, context_param, dialect, record_flip, record_opaque, unbounded_generic};
|
||||||
use crate::parsing::parse_node_fn;
|
use crate::parsing::parse_node_fn;
|
||||||
use proc_macro2::TokenStream as TokenStream2;
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
use quote::{ToTokens, quote};
|
use quote::{ToTokens, quote};
|
||||||
@@ -427,53 +427,87 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn facts_from_class(class: &Class, fields: &[&ParsedField]) -> Facts {
|
/// The kinds a supported node resolves to, from the classify predicates in
|
||||||
|
/// `analyze`'s order; the frozen oracle the IR's `node_kind` must reproduce.
|
||||||
|
struct Kinds {
|
||||||
|
record_io: bool,
|
||||||
|
routing: bool,
|
||||||
|
flip: bool,
|
||||||
|
opaque: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn kinds(parsed: &ParsedNodeFn) -> Kinds {
|
||||||
|
let record_io = record_shape(parsed).is_some();
|
||||||
|
let routing = !record_io && routing_io(parsed).is_some();
|
||||||
|
let flip = !record_io && !routing && record_flip(parsed);
|
||||||
|
let opaque = !record_io && !routing && !flip && record_opaque(parsed);
|
||||||
|
Kinds { record_io, routing, flip, opaque }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn skips_carrier(parsed: &ParsedNodeFn) -> bool {
|
||||||
|
record_shape(parsed).is_some_and(|shape| shape.skips_carrier())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn routing_generic(parsed: &ParsedNodeFn) -> Option<Ident> {
|
||||||
|
kinds(parsed).routing.then(|| routing_io(parsed).map(|routing| routing.generic)).flatten()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn token_carrier(parsed: &ParsedNodeFn) -> bool {
|
||||||
|
let element = record_writes(&slot_value_type(&parsed.output_type)).map_or_else(|| slot_value_type(&parsed.output_type), |writes| writes.element);
|
||||||
|
kinds(parsed).record_io && unbounded_generic(parsed, &element).is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn facts_from_signature(parsed: &ParsedNodeFn) -> Facts {
|
||||||
|
let fields: Vec<&ParsedField> = parsed.fields.iter().filter(|field| !field.is_data_field).collect();
|
||||||
let source_ty = |field: &ParsedField| match &field.ty {
|
let source_ty = |field: &ParsedField| match &field.ty {
|
||||||
ParsedFieldType::Node(NodeParsedField { output_type, .. }) => output_type.clone(),
|
ParsedFieldType::Node(NodeParsedField { output_type, .. }) => output_type.clone(),
|
||||||
ParsedFieldType::Regular(RegularParsedField { ty, .. }) => ty.clone(),
|
ParsedFieldType::Regular(RegularParsedField { ty, .. }) => ty.clone(),
|
||||||
};
|
};
|
||||||
match class {
|
let kinds = kinds(parsed);
|
||||||
Class::Flip { carrier } => Facts {
|
if kinds.flip {
|
||||||
sources: if *carrier { vec![0] } else { vec![] },
|
Facts {
|
||||||
|
sources: if flip_carrier(parsed) { vec![0] } else { vec![] },
|
||||||
carried: false,
|
carried: false,
|
||||||
writes: vec![],
|
writes: vec![],
|
||||||
removes: vec![],
|
removes: vec![],
|
||||||
delta: 0,
|
delta: 0,
|
||||||
},
|
|
||||||
Class::Opaque => {
|
|
||||||
let record = fields.iter().position(|field| matches!(&field.ty, ParsedFieldType::Node(NodeParsedField { output_type, .. }) if is_record_value(output_type)));
|
|
||||||
Facts {
|
|
||||||
sources: record.into_iter().collect(),
|
|
||||||
carried: true,
|
|
||||||
writes: vec![],
|
|
||||||
removes: vec![],
|
|
||||||
delta: 0,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Class::Routing(routing) => Facts {
|
} else if kinds.opaque {
|
||||||
sources: fields.iter().enumerate().filter(|(_, field)| bare_ident(&source_ty(field)) == Some(&routing.generic)).map(|(index, _)| index).collect(),
|
let record = fields.iter().position(|field| matches!(&field.ty, ParsedFieldType::Node(NodeParsedField { output_type, .. }) if is_record_value(output_type)));
|
||||||
|
Facts {
|
||||||
|
sources: record.into_iter().collect(),
|
||||||
carried: true,
|
carried: true,
|
||||||
writes: vec![],
|
writes: vec![],
|
||||||
removes: vec![],
|
removes: vec![],
|
||||||
delta: 0,
|
delta: 0,
|
||||||
},
|
}
|
||||||
Class::RecordIo(shape) => Facts {
|
} else if kinds.routing {
|
||||||
sources: if shape.skips_carrier() { vec![] } else { vec![0] },
|
let generic = routing_generic(parsed).expect("routing has a generic");
|
||||||
carried: shape.carries_element(),
|
Facts {
|
||||||
writes: markers(&shape.write_markers),
|
sources: fields.iter().enumerate().filter(|(_, field)| bare_ident(&source_ty(field)) == Some(&generic)).map(|(index, _)| index).collect(),
|
||||||
removes: markers(&shape.removes),
|
carried: true,
|
||||||
|
writes: vec![],
|
||||||
|
removes: vec![],
|
||||||
delta: 0,
|
delta: 0,
|
||||||
},
|
}
|
||||||
|
} else {
|
||||||
|
let (write_markers, removes) = record_writes(&slot_value_type(&parsed.output_type)).map_or((Vec::new(), Vec::new()), |writes| (writes.markers, writes.removes));
|
||||||
|
Facts {
|
||||||
|
sources: if skips_carrier(parsed) { vec![] } else { vec![0] },
|
||||||
|
carried: token_carrier(parsed),
|
||||||
|
writes: markers(write_markers.iter()),
|
||||||
|
removes: markers(removes.iter()),
|
||||||
|
delta: 0,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn assert_bridge(attr: TokenStream2, item: TokenStream2) -> Node {
|
fn assert_bridge(attr: TokenStream2, item: TokenStream2) -> Node {
|
||||||
let mut parsed = parse_node_fn(attr, item).unwrap();
|
let mut parsed = parse_node_fn(attr, item).unwrap();
|
||||||
parsed.replace_impl_trait_in_input();
|
parsed.replace_impl_trait_in_input();
|
||||||
let model = analyze(&parsed).expect("representative resolves to a class");
|
analyze(&parsed).expect("representative resolves to a supported node");
|
||||||
let fields: Vec<&ParsedField> = parsed.fields.iter().filter(|field| !field.is_data_field).collect();
|
|
||||||
let node = build(&parsed);
|
let node = build(&parsed);
|
||||||
assert_eq!(facts_from_ir(&node), facts_from_class(&model.class, &fields));
|
assert_eq!(facts_from_ir(&node), facts_from_signature(&parsed));
|
||||||
node
|
node
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -536,15 +570,18 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The frozen `field_role` classification the IR bindings must reproduce.
|
/// The frozen `field_role` classification the IR bindings must reproduce.
|
||||||
fn reference_label(parsed: &ParsedNodeFn, class: &Class, raw: bool, index: usize, field: &ParsedField) -> &'static str {
|
fn reference_label(parsed: &ParsedNodeFn, raw: bool, index: usize, field: &ParsedField) -> &'static str {
|
||||||
let record = matches!(class, Class::RecordIo(_));
|
let Kinds {
|
||||||
let skips_carrier = matches!(class, Class::RecordIo(shape) if shape.skips_carrier());
|
record_io: record,
|
||||||
let carrier_flip = matches!(class, Class::Flip { carrier: true });
|
routing,
|
||||||
let flip = matches!(class, Class::Flip { .. });
|
flip,
|
||||||
let opaque = matches!(class, Class::Opaque);
|
opaque,
|
||||||
let routing = matches!(class, Class::Routing(_));
|
} = kinds(parsed);
|
||||||
|
let skips_carrier = skips_carrier(parsed);
|
||||||
|
let carrier_flip = flip && flip_carrier(parsed);
|
||||||
let derives = ctx_derives(parsed);
|
let derives = ctx_derives(parsed);
|
||||||
let routing_source = |ty: &Type| matches!(class, Class::Routing(routing) if bare_ident(ty) == Some(&routing.generic));
|
let generic = routing_generic(parsed);
|
||||||
|
let routing_source = |ty: &Type| generic.as_ref().is_some_and(|generic| bare_ident(ty) == Some(generic));
|
||||||
match &field.ty {
|
match &field.ty {
|
||||||
ParsedFieldType::Regular(RegularParsedField { ty, lend, .. }) => {
|
ParsedFieldType::Regular(RegularParsedField { ty, lend, .. }) => {
|
||||||
if record && !skips_carrier && index == 0 {
|
if record && !skips_carrier && index == 0 {
|
||||||
@@ -602,14 +639,18 @@ mod tests {
|
|||||||
fn assert_bindings(attr: TokenStream2, item: TokenStream2) {
|
fn assert_bindings(attr: TokenStream2, item: TokenStream2) {
|
||||||
let mut parsed = parse_node_fn(attr, item).unwrap();
|
let mut parsed = parse_node_fn(attr, item).unwrap();
|
||||||
parsed.replace_impl_trait_in_input();
|
parsed.replace_impl_trait_in_input();
|
||||||
let model = analyze(&parsed).expect("representative resolves to a class");
|
analyze(&parsed).expect("representative resolves to a supported node");
|
||||||
let raw = matches!(dialect(&parsed), Dialect::Poll);
|
let raw = matches!(dialect(&parsed), Dialect::Poll);
|
||||||
let node = build(&parsed);
|
let node = build(&parsed);
|
||||||
let expected_kind = match &model.class {
|
let kinds = kinds(&parsed);
|
||||||
Class::RecordIo(_) => "record-io",
|
let expected_kind = if kinds.record_io {
|
||||||
Class::Flip { .. } => "flip",
|
"record-io"
|
||||||
Class::Routing(_) => "routing",
|
} else if kinds.routing {
|
||||||
Class::Opaque => "opaque",
|
"routing"
|
||||||
|
} else if kinds.flip {
|
||||||
|
"flip"
|
||||||
|
} else {
|
||||||
|
"opaque"
|
||||||
};
|
};
|
||||||
let actual_kind = match node_kind(&node) {
|
let actual_kind = match node_kind(&node) {
|
||||||
NodeKind::RecordIo => "record-io",
|
NodeKind::RecordIo => "record-io",
|
||||||
@@ -622,7 +663,7 @@ mod tests {
|
|||||||
for (index, field) in fields.iter().enumerate() {
|
for (index, field) in fields.iter().enumerate() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ir_label(&node, index, field, raw),
|
ir_label(&node, index, field, raw),
|
||||||
reference_label(&parsed, &model.class, raw, index, field),
|
reference_label(&parsed, raw, index, field),
|
||||||
"field {index} of {}",
|
"field {index} of {}",
|
||||||
parsed.fn_name
|
parsed.fn_name
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -393,10 +393,11 @@ fn validate_primary_input_expose(parsed: &ParsedNodeFn) {
|
|||||||
fn validate_implementations_for_generics(parsed: &ParsedNodeFn) {
|
fn validate_implementations_for_generics(parsed: &ParsedNodeFn) {
|
||||||
let has_skip_impl = parsed.attributes.skip_impl;
|
let has_skip_impl = parsed.attributes.skip_impl;
|
||||||
let routing = crate::codegen::routing_io(parsed);
|
let routing = crate::codegen::routing_io(parsed);
|
||||||
let record_token = crate::codegen::record_shape(parsed).and_then(|shape| match shape.carrier {
|
let node = crate::codegen::ir::build(parsed);
|
||||||
crate::codegen::RecordCarrier::Token(token) => Some(token),
|
let record_token = match (crate::codegen::ir::node_kind(&node), &node.output.shape.element) {
|
||||||
|
(crate::codegen::ir::NodeKind::RecordIo, crate::codegen::ir::Element::Generic(ident)) => Some(ident.clone()),
|
||||||
_ => None,
|
_ => None,
|
||||||
});
|
};
|
||||||
let opaque_record_generic = |ty: &Type| {
|
let opaque_record_generic = |ty: &Type| {
|
||||||
let ident = match ty {
|
let ident = match ty {
|
||||||
Type::Path(path) => path.path.get_ident(),
|
Type::Path(path) => path.path.get_ident(),
|
||||||
|
|||||||
Reference in New Issue
Block a user