Files
storybook/src/lsp/inlay_hints.rs
Sienna Meridian Satterwhite 25d59d6107 feat(type-system): implement concept_comparison with pattern matching
Added complete support for the new type system syntax including:

- concept: Base type declarations
- sub_concept: Enum and record sub-type definitions
- concept_comparison: Compile-time pattern matching with conditional guards

Parser changes:
- Added VariantPattern, FieldCondition, and Condition AST nodes
- Implemented "is" keyword for pattern matching (e.g., "CupType is Glass or CupType is Plastic")
- Added Value::Any variant to support universal type matching
- Disambiguated enum-like vs record-like sub_concept syntax

LSP updates:
- Added Value::Any match arms across code_actions, completion, hover, inlay_hints, and semantic_tokens
- Type inference and formatting support for Any values

Example fixes:
- Fixed syntax error in baker-family behaviors (missing closing brace in nested if)
- Removed deprecated core_enums.sb file
2026-02-14 09:28:20 +00:00

208 lines
7.5 KiB
Rust

//! Inlay hints for implicit information
//!
//! Provides inline annotations showing:
//! - Parameter names in action calls
//! - Inferred types for field values
//! - Template/species field sources
use tower_lsp::lsp_types::{
InlayHint,
InlayHintKind,
InlayHintLabel,
Position,
};
use super::document::Document;
use crate::syntax::ast::{
Declaration,
Field,
Value,
};
/// Get inlay hints for a document range
pub fn get_inlay_hints(doc: &Document, start: Position, end: Position) -> Option<Vec<InlayHint>> {
let ast = doc.ast.as_ref()?;
let mut hints = Vec::new();
let mut positions = doc.positions.clone();
// Convert positions to offsets
let start_line = start.line as usize;
let end_line = end.line as usize;
// Process all declarations
for decl in &ast.declarations {
match decl {
| Declaration::Character(character) => {
// Skip if character is outside requested range
if character.span.start_line > end_line || character.span.end_line < start_line {
continue;
}
// Add type hints for character fields
for field in &character.fields {
if field.span.start_line >= start_line && field.span.start_line <= end_line {
add_type_hint(&mut hints, &mut positions, field);
}
}
},
| Declaration::Template(template) => {
// Skip if template is outside requested range
if template.span.start_line > end_line || template.span.end_line < start_line {
continue;
}
// Add type hints for template fields
for field in &template.fields {
if field.span.start_line >= start_line && field.span.start_line <= end_line {
add_type_hint(&mut hints, &mut positions, field);
}
}
},
| Declaration::Species(species) => {
// Skip if species is outside requested range
if species.span.start_line > end_line || species.span.end_line < start_line {
continue;
}
// Add type hints for species fields
for field in &species.fields {
if field.span.start_line >= start_line && field.span.start_line <= end_line {
add_type_hint(&mut hints, &mut positions, field);
}
}
},
| Declaration::Institution(institution) => {
// Skip if institution is outside requested range
if institution.span.start_line > end_line || institution.span.end_line < start_line
{
continue;
}
// Add type hints for institution fields
for field in &institution.fields {
if field.span.start_line >= start_line && field.span.start_line <= end_line {
add_type_hint(&mut hints, &mut positions, field);
}
}
},
| Declaration::Location(location) => {
// Skip if location is outside requested range
if location.span.start_line > end_line || location.span.end_line < start_line {
continue;
}
// Add type hints for location fields
for field in &location.fields {
if field.span.start_line >= start_line && field.span.start_line <= end_line {
add_type_hint(&mut hints, &mut positions, field);
}
}
},
| Declaration::Relationship(relationship) => {
// Skip if relationship is outside requested range
if relationship.span.start_line > end_line ||
relationship.span.end_line < start_line
{
continue;
}
// Add type hints for relationship fields
for field in &relationship.fields {
if field.span.start_line >= start_line && field.span.start_line <= end_line {
add_type_hint(&mut hints, &mut positions, field);
}
}
},
| Declaration::Behavior(behavior) => {
// Skip if behavior is outside requested range
if behavior.span.start_line > end_line || behavior.span.end_line < start_line {
continue;
}
// TODO: Add parameter name hints for action calls in behavior
// trees Would need to traverse BehaviorNode
// tree and match actions to schema
},
| _ => {},
}
}
if hints.is_empty() {
None
} else {
Some(hints)
}
}
/// Add type hint for a field value
fn add_type_hint(
hints: &mut Vec<InlayHint>,
positions: &mut crate::position::PositionTracker,
field: &Field,
) {
let type_str = infer_value_type(&field.value);
// Only add hints for non-obvious types
// Skip if the type is clear from the literal (e.g., "string", 123, true)
let should_hint = match &field.value {
| Value::String(_) | Value::Int(_) | Value::Float(_) | Value::Bool(_) => false,
| Value::Identifier(_) => true, // Show type for identifier references
| Value::List(_) => true, // Show list element type
| Value::Object(_) => false, // Object structure is visible
| Value::Range(_, _) => false, // Range syntax is clear
| Value::Time(_) => false, // Time format is clear
| Value::Duration(_) => false, // Duration format is clear
| Value::ProseBlock(_) => false, // Prose is obvious
| Value::Override(_) => true, // Show what's being overridden
| Value::Any => true, // Show Any type marker
};
if !should_hint {
return;
}
// Position the hint at the end of the field value
let (line, col) = positions.offset_to_position(field.span.end);
hints.push(InlayHint {
position: Position {
line: line as u32,
character: col as u32,
},
label: InlayHintLabel::String(format!(": {}", type_str)),
kind: Some(InlayHintKind::TYPE),
text_edits: None,
tooltip: None,
padding_left: Some(true),
padding_right: None,
data: None,
});
}
/// Infer the type of a value for display
fn infer_value_type(value: &Value) -> String {
match value {
| Value::Identifier(path) => path.join("."),
| Value::String(_) => "String".to_string(),
| Value::Int(_) => "Int".to_string(),
| Value::Float(_) => "Float".to_string(),
| Value::Bool(_) => "Bool".to_string(),
| Value::List(items) => {
if items.is_empty() {
"[]".to_string()
} else {
format!("[{}]", infer_value_type(&items[0]))
}
},
| Value::Object(_) => "Object".to_string(),
| Value::Range(start, end) => {
format!("{}..{}", infer_value_type(start), infer_value_type(end))
},
| Value::Time(_) => "Time".to_string(),
| Value::Duration(_) => "Duration".to_string(),
| Value::ProseBlock(_) => "Prose".to_string(),
| Value::Override(_) => "Override".to_string(),
| Value::Any => "Any".to_string(),
}
}