binder: snapshot

This commit is contained in:
nym21
2025-12-20 21:08:17 +01:00
parent bcb8d5bed6
commit 71f45479b9
4 changed files with 363 additions and 51 deletions
+75 -12
View File
@@ -336,9 +336,12 @@ fn generate_structural_patterns(
// Generate JSDoc typedef
writeln!(output, "/**").unwrap();
if pattern.is_generic {
writeln!(output, " * @template T").unwrap();
}
writeln!(output, " * @typedef {{Object}} {}", pattern.name).unwrap();
for field in &pattern.fields {
let js_type = field_to_js_type(field, metadata);
let js_type = field_to_js_type_generic(field, metadata, pattern.is_generic);
writeln!(
output,
" * @property {{{}}} {}",
@@ -488,17 +491,45 @@ fn generate_tree_path_field(
}
}
/// Convert pattern field to JavaScript/JSDoc type
fn field_to_js_type(field: &PatternField, metadata: &ClientMetadata) -> String {
/// Convert pattern field to JavaScript/JSDoc type, with optional generic support
fn field_to_js_type_generic(
field: &PatternField,
metadata: &ClientMetadata,
is_generic: bool,
) -> String {
field_to_js_type_with_generic_value(field, metadata, is_generic, None)
}
/// Convert pattern field to JavaScript/JSDoc type.
/// - `is_generic`: If true and field.rust_type is "T", use T in the output
/// - `generic_value_type`: For branch fields that reference a generic pattern, this is the concrete type to substitute
fn field_to_js_type_with_generic_value(
field: &PatternField,
metadata: &ClientMetadata,
is_generic: bool,
generic_value_type: Option<&str>,
) -> String {
// For generic patterns, use T instead of concrete value type
let value_type = if is_generic && field.rust_type == "T" {
"T".to_string()
} else {
field.rust_type.clone()
};
if metadata.is_pattern_type(&field.rust_type) {
// Pattern type - use pattern name directly
// Check if this pattern is generic and we have a value type
if metadata.is_pattern_generic(&field.rust_type) {
if let Some(vt) = generic_value_type {
return format!("{}<{}>", field.rust_type, vt);
}
}
field.rust_type.clone()
} else if let Some(accessor) = metadata.find_index_set_pattern(&field.indexes) {
// Leaf with accessor - use rust_type as the generic (e.g., DateIndexAccessor<Height>)
format!("{}<{}>", accessor.name, field.rust_type)
// Leaf with accessor - use value_type as the generic
format!("{}<{}>", accessor.name, value_type)
} else {
// Leaf - use rust_type as the generic (e.g., MetricNode<Height>)
format!("MetricNode<{}>", field.rust_type)
// Leaf - use value_type as the generic
format!("MetricNode<{}>", value_type)
}
}
@@ -533,8 +564,36 @@ fn generate_tree_typedef(
generated: &mut HashSet<String>,
) {
if let TreeNode::Branch(children) = node {
// Build signature
let fields = get_node_fields(children, pattern_lookup);
// Build signature with child field info for generic pattern lookup
let fields_with_child_info: Vec<(PatternField, Option<Vec<PatternField>>)> = children
.iter()
.map(|(child_name, child_node)| {
let (rust_type, json_type, indexes, child_fields) = match child_node {
TreeNode::Leaf(leaf) => (
leaf.value_type().to_string(),
leaf.schema.get("type").and_then(|v| v.as_str()).unwrap_or("object").to_string(),
leaf.indexes().clone(),
None,
),
TreeNode::Branch(grandchildren) => {
let child_fields = get_node_fields(grandchildren, pattern_lookup);
let pattern_name = pattern_lookup
.get(&child_fields)
.cloned()
.unwrap_or_else(|| format!("{}_{}", name, to_pascal_case(child_name)));
(pattern_name.clone(), pattern_name, std::collections::BTreeSet::new(), Some(child_fields))
}
};
(PatternField {
name: child_name.clone(),
rust_type,
json_type,
indexes,
}, child_fields)
})
.collect();
let fields: Vec<PatternField> = fields_with_child_info.iter().map(|(f, _)| f.clone()).collect();
// Skip if this matches a pattern (already generated)
if pattern_lookup.contains_key(&fields)
@@ -551,8 +610,12 @@ fn generate_tree_typedef(
writeln!(output, "/**").unwrap();
writeln!(output, " * @typedef {{Object}} {}", name).unwrap();
for field in &fields {
let js_type = field_to_js_type(field, metadata);
for (field, child_fields) in &fields_with_child_info {
// For generic patterns, extract the value type from child fields
let generic_value_type = child_fields.as_ref().and_then(|cf| {
metadata.get_generic_value_type(&field.rust_type, cf)
});
let js_type = field_to_js_type_with_generic_value(field, metadata, false, generic_value_type.as_deref());
writeln!(
output,
" * @property {{{}}} {}",
+81 -11
View File
@@ -255,7 +255,12 @@ fn generate_structural_patterns(
for pattern in patterns {
let is_parameterizable = pattern.is_parameterizable();
writeln!(output, "class {}:", pattern.name).unwrap();
// For generic patterns, inherit from Generic[T]
if pattern.is_generic {
writeln!(output, "class {}(Generic[T]):", pattern.name).unwrap();
} else {
writeln!(output, "class {}:", pattern.name).unwrap();
}
writeln!(
output,
" \"\"\"Pattern struct for repeated tree structure.\"\"\""
@@ -298,7 +303,7 @@ fn generate_parameterized_python_field(
metadata: &ClientMetadata,
) {
let field_name = to_snake_case(&field.name);
let py_type = field_to_python_type(field, metadata);
let py_type = field_to_python_type_generic(field, metadata, pattern.is_generic);
// For branch fields, pass the accumulated name to nested pattern
if metadata.is_pattern_type(&field.rust_type) {
@@ -388,15 +393,48 @@ fn generate_tree_path_python_field(
/// Convert pattern field to Python type annotation
fn field_to_python_type(field: &PatternField, metadata: &ClientMetadata) -> String {
field_to_python_type_generic(field, metadata, false)
}
/// Convert pattern field to Python type annotation, with optional generic support
fn field_to_python_type_generic(
field: &PatternField,
metadata: &ClientMetadata,
is_generic: bool,
) -> String {
field_to_python_type_with_generic_value(field, metadata, is_generic, None)
}
/// Convert pattern field to Python type annotation.
/// - `is_generic`: If true and field.rust_type is "T", use T in the output
/// - `generic_value_type`: For branch fields that reference a generic pattern, this is the concrete type to substitute
fn field_to_python_type_with_generic_value(
field: &PatternField,
metadata: &ClientMetadata,
is_generic: bool,
generic_value_type: Option<&str>,
) -> String {
// For generic patterns, use T instead of concrete value type
let value_type = if is_generic && field.rust_type == "T" {
"T".to_string()
} else {
field.rust_type.clone()
};
if metadata.is_pattern_type(&field.rust_type) {
// Pattern type - use pattern name directly
// Check if this pattern is generic and we have a value type
if metadata.is_pattern_generic(&field.rust_type) {
if let Some(vt) = generic_value_type {
return format!("{}[{}]", field.rust_type, vt);
}
}
field.rust_type.clone()
} else if let Some(accessor) = metadata.find_index_set_pattern(&field.indexes) {
// Leaf with accessor - use rust_type as the generic (e.g., DateIndexAccessor[Height])
format!("{}[{}]", accessor.name, field.rust_type)
// Leaf with accessor - use value_type as the generic
format!("{}[{}]", accessor.name, value_type)
} else {
// Leaf - use rust_type as the generic (e.g., MetricNode[Height])
format!("MetricNode[{}]", field.rust_type)
// Leaf - use value_type as the generic
format!("MetricNode[{}]", value_type)
}
}
@@ -431,8 +469,36 @@ fn generate_tree_class(
generated: &mut HashSet<String>,
) {
if let TreeNode::Branch(children) = node {
// Build signature
let fields = get_node_fields(children, pattern_lookup);
// Build signature with child field info for generic pattern lookup
let fields_with_child_info: Vec<(PatternField, Option<Vec<PatternField>>)> = children
.iter()
.map(|(child_name, child_node)| {
let (rust_type, json_type, indexes, child_fields) = match child_node {
TreeNode::Leaf(leaf) => (
leaf.value_type().to_string(),
leaf.schema.get("type").and_then(|v| v.as_str()).unwrap_or("object").to_string(),
leaf.indexes().clone(),
None,
),
TreeNode::Branch(grandchildren) => {
let child_fields = get_node_fields(grandchildren, pattern_lookup);
let pattern_name = pattern_lookup
.get(&child_fields)
.cloned()
.unwrap_or_else(|| format!("{}_{}", name, to_pascal_case(child_name)));
(pattern_name.clone(), pattern_name, std::collections::BTreeSet::new(), Some(child_fields))
}
};
(PatternField {
name: child_name.clone(),
rust_type,
json_type,
indexes,
}, child_fields)
})
.collect();
let fields: Vec<PatternField> = fields_with_child_info.iter().map(|(f, _)| f.clone()).collect();
// Skip if this matches a pattern (already generated)
if pattern_lookup.contains_key(&fields)
@@ -455,8 +521,12 @@ fn generate_tree_class(
)
.unwrap();
for (field, (child_name, child_node)) in fields.iter().zip(children.iter()) {
let py_type = field_to_python_type(field, metadata);
for ((field, child_fields_opt), (child_name, child_node)) in fields_with_child_info.iter().zip(children.iter()) {
// For generic patterns, extract the value type from child fields
let generic_value_type = child_fields_opt.as_ref().and_then(|cf| {
metadata.get_generic_value_type(&field.rust_type, cf)
});
let py_type = field_to_python_type_with_generic_value(field, metadata, false, generic_value_type.as_deref());
let field_name_py = to_snake_case(&field.name);
if metadata.is_pattern_type(&field.rust_type) {
+53 -16
View File
@@ -233,20 +233,21 @@ fn generate_pattern_structs(output: &mut String, patterns: &[StructuralPattern],
for pattern in patterns {
let is_parameterizable = pattern.is_parameterizable();
let generic_params = if pattern.is_generic { "<'a, T>" } else { "<'a>" };
writeln!(output, "/// Pattern struct for repeated tree structure.").unwrap();
writeln!(output, "pub struct {}<'a> {{", pattern.name).unwrap();
writeln!(output, "pub struct {}{} {{", pattern.name, generic_params).unwrap();
for field in &pattern.fields {
let field_name = to_snake_case(&field.name);
let type_annotation = field_to_type_annotation(field, metadata);
let type_annotation = field_to_type_annotation_generic(field, metadata, pattern.is_generic);
writeln!(output, " pub {}: {},", field_name, type_annotation).unwrap();
}
writeln!(output, "}}\n").unwrap();
// Generate impl block with constructor
writeln!(output, "impl<'a> {}<'a> {{", pattern.name).unwrap();
writeln!(output, "impl{} {}{} {{", generic_params, pattern.name, generic_params).unwrap();
if is_parameterizable {
writeln!(output, " /// Create a new pattern node with accumulated metric name.").unwrap();
@@ -358,16 +359,45 @@ fn generate_tree_path_rust_field(
}
}
/// Convert a PatternField to the full type annotation
fn field_to_type_annotation(field: &PatternField, metadata: &ClientMetadata) -> String {
/// Convert a PatternField to the full type annotation, with optional generic support
fn field_to_type_annotation_generic(
field: &PatternField,
metadata: &ClientMetadata,
is_generic: bool,
) -> String {
field_to_type_annotation_with_generic(field, metadata, is_generic, None)
}
/// Convert a PatternField to the full type annotation.
/// - `is_generic`: If true and field.rust_type is "T", use T in the output
/// - `generic_value_type`: For branch fields that reference a generic pattern, this is the concrete type to substitute
fn field_to_type_annotation_with_generic(
field: &PatternField,
metadata: &ClientMetadata,
is_generic: bool,
generic_value_type: Option<&str>,
) -> String {
// For generic patterns, use T instead of concrete value type
let value_type = if is_generic && field.rust_type == "T" {
"T".to_string()
} else {
field.rust_type.clone()
};
if metadata.is_pattern_type(&field.rust_type) {
// Check if this pattern is generic and we have a value type
if metadata.is_pattern_generic(&field.rust_type) {
if let Some(vt) = generic_value_type {
return format!("{}<'a, {}>", field.rust_type, vt);
}
}
format!("{}<'a>", field.rust_type)
} else if let Some(accessor) = metadata.find_index_set_pattern(&field.indexes) {
// Leaf with a reusable accessor pattern
format!("{}<'a, {}>", accessor.name, field.rust_type)
format!("{}<'a, {}>", accessor.name, value_type)
} else {
// Leaf with unique index set - use MetricNode directly
format!("MetricNode<'a, {}>", field.rust_type)
format!("MetricNode<'a, {}>", value_type)
}
}
@@ -399,15 +429,16 @@ fn generate_tree_node(
generated: &mut HashSet<String>,
) {
if let TreeNode::Branch(children) = node {
// Build the signature for this node
let mut fields: Vec<PatternField> = children
// Build the signature for this node, also tracking child fields for generic pattern lookup
let mut fields_with_child_info: Vec<(PatternField, Option<Vec<PatternField>>)> = children
.iter()
.map(|(child_name, child_node)| {
let (rust_type, json_type, indexes) = match child_node {
let (rust_type, json_type, indexes, child_fields) = match child_node {
TreeNode::Leaf(leaf) => (
leaf.value_type().to_string(),
leaf.schema.get("type").and_then(|v| v.as_str()).unwrap_or("object").to_string(),
leaf.indexes().clone(),
None,
),
TreeNode::Branch(grandchildren) => {
// Get pattern name for this child
@@ -416,18 +447,20 @@ fn generate_tree_node(
.get(&child_fields)
.cloned()
.unwrap_or_else(|| format!("{}_{}", name, to_pascal_case(child_name)));
(pattern_name.clone(), pattern_name, std::collections::BTreeSet::new())
(pattern_name.clone(), pattern_name, std::collections::BTreeSet::new(), Some(child_fields))
}
};
PatternField {
(PatternField {
name: child_name.clone(),
rust_type,
json_type,
indexes,
}
}, child_fields)
})
.collect();
fields.sort_by(|a, b| a.name.cmp(&b.name));
fields_with_child_info.sort_by(|a, b| a.0.name.cmp(&b.0.name));
let fields: Vec<PatternField> = fields_with_child_info.iter().map(|(f, _)| f.clone()).collect();
// Check if this matches a reusable pattern
if let Some(pattern_name) = pattern_lookup.get(&fields) {
@@ -447,9 +480,13 @@ fn generate_tree_node(
writeln!(output, "/// Catalog tree node.").unwrap();
writeln!(output, "pub struct {}<'a> {{", name).unwrap();
for field in &fields {
for (field, child_fields) in &fields_with_child_info {
let field_name = to_snake_case(&field.name);
let type_annotation = field_to_type_annotation(field, metadata);
// For generic patterns, extract the value type from child fields
let generic_value_type = child_fields.as_ref().and_then(|cf| {
metadata.get_generic_value_type(&field.rust_type, cf)
});
let type_annotation = field_to_type_annotation_with_generic(field, metadata, false, generic_value_type.as_deref());
writeln!(output, " pub {}: {},", field_name, type_annotation).unwrap();
}
+154 -12
View File
@@ -28,6 +28,8 @@ pub struct ClientMetadata {
pub used_indexes: BTreeSet<Index>,
/// Index set patterns - sets of indexes that appear together on metrics
pub index_set_patterns: Vec<IndexSetPattern>,
/// Maps concrete field signatures to pattern names (includes generic pattern mappings)
pub concrete_to_pattern: HashMap<Vec<PatternField>, String>,
}
/// A pattern of indexes that appear together on multiple metrics
@@ -48,6 +50,8 @@ pub struct StructuralPattern {
pub fields: Vec<PatternField>,
/// How each field modifies the accumulated name (field_name -> position)
pub field_positions: HashMap<String, FieldNamePosition>,
/// If true, all leaf fields use a type parameter T instead of concrete types
pub is_generic: bool,
}
impl StructuralPattern {
@@ -112,7 +116,7 @@ impl ClientMetadata {
/// Extract metadata from brk_query::Vecs
pub fn from_vecs(vecs: &Vecs) -> Self {
let catalog = vecs.catalog().clone();
let structural_patterns = detect_structural_patterns(&catalog);
let (structural_patterns, concrete_to_pattern) = detect_structural_patterns(&catalog);
let (used_indexes, index_set_patterns) = detect_index_patterns(&catalog);
ClientMetadata {
@@ -120,6 +124,7 @@ impl ClientMetadata {
structural_patterns,
used_indexes,
index_set_patterns,
concrete_to_pattern,
}
}
@@ -135,19 +140,47 @@ impl ClientMetadata {
self.structural_patterns.iter().any(|p| p.name == type_name)
}
/// Build a lookup map from field signatures to pattern names
pub fn pattern_lookup(&self) -> HashMap<Vec<PatternField>, String> {
self.structural_patterns
/// Find a pattern by name
pub fn find_pattern(&self, name: &str) -> Option<&StructuralPattern> {
self.structural_patterns.iter().find(|p| p.name == name)
}
/// Check if a pattern is generic
pub fn is_pattern_generic(&self, name: &str) -> bool {
self.find_pattern(name).map(|p| p.is_generic).unwrap_or(false)
}
/// Extract the value type from concrete fields for a generic pattern.
/// Returns the first leaf field's rust_type if this pattern is generic.
pub fn get_generic_value_type(&self, pattern_name: &str, fields: &[PatternField]) -> Option<String> {
if !self.is_pattern_generic(pattern_name) {
return None;
}
// Find first leaf field (has indexes)
fields
.iter()
.map(|p| (p.fields.clone(), p.name.clone()))
.collect()
.find(|f| !f.indexes.is_empty())
.map(|f| f.rust_type.clone())
}
/// Build a lookup map from field signatures to pattern names.
/// Includes both generic pattern signatures and concrete signatures.
pub fn pattern_lookup(&self) -> HashMap<Vec<PatternField>, String> {
// Start with concrete-to-pattern mappings (includes generic pattern concrete signatures)
let mut lookup = self.concrete_to_pattern.clone();
// Also add the normalized generic signatures
for p in &self.structural_patterns {
lookup.insert(p.fields.clone(), p.name.clone());
}
lookup
}
}
/// Detect structural patterns in the tree using a bottom-up approach.
/// For every branch node, create a signature from its children (sorted field names + types).
/// Patterns that appear 2+ times are deduplicated.
fn detect_structural_patterns(tree: &TreeNode) -> Vec<StructuralPattern> {
/// Returns (patterns, concrete_to_pattern_mapping).
fn detect_structural_patterns(tree: &TreeNode) -> (Vec<StructuralPattern>, HashMap<Vec<PatternField>, String>) {
// Map from sorted fields signature to pattern name
let mut signature_to_pattern: HashMap<Vec<PatternField>, String> = HashMap::new();
// Count how many times each signature appears
@@ -156,19 +189,42 @@ fn detect_structural_patterns(tree: &TreeNode) -> Vec<StructuralPattern> {
// Process tree bottom-up to resolve all branch types
resolve_branch_patterns(tree, &mut signature_to_pattern, &mut signature_counts);
// Build initial list of patterns (only those appearing 2+ times)
// First, identify generic patterns by grouping ALL signatures by their normalized form.
// Even if each concrete signature appears only once, if 2+ different value types
// normalize to the same pattern, we create a generic pattern.
let (generic_patterns, generic_mappings) = detect_generic_patterns(&signature_to_pattern);
// Build non-generic patterns: signatures appearing 2+ times that weren't merged into generics
let mut patterns: Vec<StructuralPattern> = signature_to_pattern
.iter()
.filter(|(sig, _)| signature_counts.get(*sig).copied().unwrap_or(0) >= 2)
.filter(|(sig, _)| {
signature_counts.get(*sig).copied().unwrap_or(0) >= 2
&& !generic_mappings.contains_key(*sig)
})
.map(|(fields, name)| StructuralPattern {
name: name.clone(),
fields: fields.clone(),
field_positions: HashMap::new(),
is_generic: false,
})
.collect();
// Build lookup for second pass
let pattern_lookup: HashMap<Vec<PatternField>, String> = signature_to_pattern;
// Add the generic patterns
patterns.extend(generic_patterns);
// Build lookup for second pass - include all concrete signatures
let mut pattern_lookup: HashMap<Vec<PatternField>, String> = HashMap::new();
// Add non-generic patterns that appear 2+ times
for (sig, name) in &signature_to_pattern {
if signature_counts.get(sig).copied().unwrap_or(0) >= 2 {
pattern_lookup.insert(sig.clone(), name.clone());
}
}
// Add generic mappings (overwrite if there's overlap)
pattern_lookup.extend(generic_mappings.clone());
// Build the concrete_to_pattern map to return
let concrete_to_pattern = pattern_lookup.clone();
// Second pass: analyze field positions by traversing tree instances
analyze_pattern_field_positions(tree, &mut patterns, &pattern_lookup);
@@ -176,7 +232,93 @@ fn detect_structural_patterns(tree: &TreeNode) -> Vec<StructuralPattern> {
// Sort by number of fields descending (larger patterns first)
patterns.sort_by(|a, b| b.fields.len().cmp(&a.fields.len()));
patterns
(patterns, concrete_to_pattern)
}
/// Detect generic patterns by grouping all signatures by their normalized form.
/// Returns (generic_patterns, concrete_signature -> generic_pattern_name mapping).
fn detect_generic_patterns(
signature_to_pattern: &HashMap<Vec<PatternField>, String>,
) -> (Vec<StructuralPattern>, HashMap<Vec<PatternField>, String>) {
// Group signatures by their normalized (generic) form
let mut normalized_groups: HashMap<Vec<PatternField>, Vec<(Vec<PatternField>, String)>> = HashMap::new();
for (fields, name) in signature_to_pattern {
if let Some(normalized) = normalize_fields_for_generic(fields) {
normalized_groups
.entry(normalized)
.or_default()
.push((fields.clone(), name.clone()));
}
}
let mut patterns = Vec::new();
let mut mappings: HashMap<Vec<PatternField>, String> = HashMap::new();
// Create generic patterns for groups with 2+ different concrete signatures
for (normalized_fields, group) in normalized_groups {
if group.len() >= 2 {
// Use the first pattern's name as the generic pattern name
let generic_name = group[0].1.clone();
// Map all concrete signatures to this generic pattern
for (concrete_fields, _) in &group {
mappings.insert(concrete_fields.clone(), generic_name.clone());
}
patterns.push(StructuralPattern {
name: generic_name,
fields: normalized_fields,
field_positions: HashMap::new(),
is_generic: true,
});
}
}
(patterns, mappings)
}
/// Normalize fields by replacing concrete value types with "T" for generic matching.
/// Returns None if the pattern is not suitable for generics (e.g., mixed value types).
fn normalize_fields_for_generic(fields: &[PatternField]) -> Option<Vec<PatternField>> {
// Get all leaf field value types
let leaf_types: Vec<&str> = fields
.iter()
.filter(|f| !f.indexes.is_empty()) // Only leaves have indexes
.map(|f| f.rust_type.as_str())
.collect();
// Need at least one leaf to be generic
if leaf_types.is_empty() {
return None;
}
// All leaves must have the same value type
let first_type = leaf_types[0];
if !leaf_types.iter().all(|t| *t == first_type) {
return None;
}
// Create normalized fields with "T" as the value type
let normalized: Vec<PatternField> = fields
.iter()
.map(|f| {
if f.indexes.is_empty() {
// Branch field - keep as is
f.clone()
} else {
// Leaf field - replace value type with T
PatternField {
name: f.name.clone(),
rust_type: "T".to_string(),
json_type: "T".to_string(),
indexes: f.indexes.clone(),
}
}
})
.collect();
Some(normalized)
}
/// Analyze field positions for all patterns by traversing tree instances.