ruchy 4.1.2

A systems scripting language that transpiles to idiomatic Rust with extreme quality engineering
Documentation
//! Semantic analysis for LSP
use crate::frontend::ast::{Expr, ExprKind};
use crate::frontend::parser::Parser;
use std::collections::HashMap;
use tower_lsp::jsonrpc::Result;
use tower_lsp::lsp_types::{
    CompletionItem, CompletionItemKind, Diagnostic, DiagnosticSeverity, Documentation, Hover,
    HoverContents, Location, MarkedString, NumberOrString, Position, Range,
};
pub struct SemanticAnalyzer {
    symbol_table: SymbolTable,
}
#[derive(Debug, Default)]
struct SymbolTable {
    symbols: HashMap<String, Symbol>,
}
#[derive(Debug, Clone)]
struct Symbol {
    name: String,
    kind: String,
    documentation: Option<String>,
}
impl SemanticAnalyzer {
    /// Create a new semantic analyzer
    ///
    /// # Examples
    ///
    /// ```
    /// use ruchy::lsp::SemanticAnalyzer;
    ///
    /// let analyzer = SemanticAnalyzer::new();
    /// // Analyzer starts with empty symbol table
    /// ```
    pub fn new() -> Self {
        Self {
            symbol_table: SymbolTable::default(),
        }
    }
    /// Get completions for a position in the document
    ///
    /// # Errors
    ///
    /// This function currently does not return errors but returns Result for future compatibility
    pub fn get_completions(
        &self,
        _document: &str,
        _position: Position,
    ) -> Result<Vec<CompletionItem>> {
        // Basic keyword completions
        let keywords = vec![
            "fun", "let", "if", "else", "match", "struct", "trait", "impl", "actor", "import",
            "for", "while", "break", "continue", "true", "false",
        ];
        let mut completions = Vec::new();
        // Add keyword completions
        for keyword in keywords {
            completions.push(CompletionItem {
                label: keyword.to_string(),
                kind: Some(CompletionItemKind::KEYWORD),
                detail: Some(format!("Ruchy keyword: {keyword}")),
                ..Default::default()
            });
        }
        // Add built-in types
        let types = vec!["i32", "i64", "f32", "f64", "String", "bool", "()"];
        for type_name in types {
            completions.push(CompletionItem {
                label: type_name.to_string(),
                kind: Some(CompletionItemKind::TYPE_PARAMETER),
                detail: Some(format!("Built-in type: {type_name}")),
                ..Default::default()
            });
        }
        // Add symbols from symbol table
        for symbol in self.symbol_table.symbols.values() {
            completions.push(CompletionItem {
                label: symbol.name.clone(),
                kind: Some(CompletionItemKind::VARIABLE),
                detail: Some(symbol.kind.clone()),
                documentation: symbol
                    .documentation
                    .as_ref()
                    .map(|doc| Documentation::String(doc.clone())),
                ..Default::default()
            });
        }
        Ok(completions)
    }
    /// Get hover information for a position
    ///
    /// # Errors
    ///
    /// This function currently does not return errors but returns Result for future compatibility
    pub fn get_hover_info(&self, document: &str, _position: Position) -> Result<Option<Hover>> {
        // Parse the document to get context
        let mut parser = Parser::new(document);
        if parser.parse().is_err() {
            return Ok(None);
        }
        // For now, return basic info about Ruchy features
        let hover_text = "Ruchy Language\n\nA systems scripting language with:\n- Strong type inference\n- Actor-based concurrency\n- DataFrame-first collections\n- Rust interoperability";
        Ok(Some(Hover {
            contents: HoverContents::Scalar(MarkedString::String(hover_text.to_string())),
            range: None,
        }))
    }
    /// Get definition location for a symbol
    ///
    /// # Errors
    ///
    /// This function currently does not return errors but returns Result for future compatibility
    pub fn get_definition(&self, _document: &str, _position: Position) -> Result<Option<Location>> {
        // For now, return None - would need more sophisticated symbol tracking
        Ok(None)
    }
    /// Get diagnostics for a document
    ///
    /// # Errors
    ///
    /// This function currently does not return errors but returns Result for future compatibility
    pub fn get_diagnostics(&mut self, document: &str) -> Result<Vec<Diagnostic>> {
        let mut diagnostics = Vec::new();
        // Try to parse the document
        let mut parser = Parser::new(document);
        match parser.parse() {
            Ok(ast) => {
                // Update symbol table
                self.update_symbol_table(&ast, document);
                // Add any semantic warnings
                // For now, just check for unused variables (placeholder)
            }
            Err(parse_error) => {
                // Add parse error as diagnostic
                diagnostics.push(Diagnostic {
                    range: Range {
                        start: Position {
                            line: 0,
                            character: 0,
                        },
                        end: Position {
                            line: 0,
                            character: 10,
                        },
                    },
                    severity: Some(DiagnosticSeverity::ERROR),
                    code: Some(NumberOrString::String("parse_error".to_string())),
                    message: format!("Parse error: {parse_error}"),
                    source: Some("ruchy".to_string()),
                    ..Default::default()
                });
            }
        }
        Ok(diagnostics)
    }
    fn update_symbol_table(&mut self, ast: &Expr, document: &str) {
        // Walk the AST and extract symbols
        self.extract_symbols(ast, document);
    }
    fn extract_symbols(&mut self, expr: &Expr, _document: &str) {
        match &expr.kind {
            ExprKind::Function { name, params, .. } => {
                // Add function to symbol table
                let symbol = Symbol {
                    name: name.clone(),
                    kind: "function".to_string(),
                    documentation: None,
                };
                self.symbol_table.symbols.insert(name.clone(), symbol);
                // Add parameters
                for param in params {
                    let param_symbol = Symbol {
                        name: param.name(),
                        kind: "parameter".to_string(),
                        documentation: None,
                    };
                    self.symbol_table.symbols.insert(param.name(), param_symbol);
                }
            }
            ExprKind::Let { name, .. } => {
                let symbol = Symbol {
                    name: name.clone(),
                    kind: "variable".to_string(),
                    documentation: None,
                };
                self.symbol_table.symbols.insert(name.clone(), symbol);
            }
            ExprKind::Struct { name, .. } => {
                let symbol = Symbol {
                    name: name.clone(),
                    kind: "struct".to_string(),
                    documentation: None,
                };
                self.symbol_table.symbols.insert(name.clone(), symbol);
            }
            // Recursively process other expressions
            _ => {
                // Would recursively walk all child expressions
                // For now, just a placeholder
            }
        }
    }
}
impl Default for SemanticAnalyzer {
    fn default() -> Self {
        Self::new()
    }
}
#[cfg(test)]
mod property_tests_analyzer {
    use proptest::prelude::*;
    proptest! {
        /// Property: Function never panics on any input
        #[test]
        fn test_new_never_panics(input: String) {
            // Limit input size to avoid timeout
            let _input = if input.len() > 100 { &input[..100] } else { &input[..] };
            // Function should not panic on any input
            let _ = std::panic::catch_unwind(|| {
                // Call function with various inputs
                // This is a template - adjust based on actual function signature
            });
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_semantic_analyzer_new() {
        let analyzer = SemanticAnalyzer::new();
        assert!(analyzer.symbol_table.symbols.is_empty());
    }

    #[test]
    fn test_semantic_analyzer_default() {
        let analyzer = SemanticAnalyzer::default();
        assert!(analyzer.symbol_table.symbols.is_empty());
    }

    #[test]
    fn test_get_completions_includes_keywords() {
        let analyzer = SemanticAnalyzer::new();
        let completions = analyzer
            .get_completions(
                "",
                Position {
                    line: 0,
                    character: 0,
                },
            )
            .expect("should succeed");
        let labels: Vec<_> = completions.iter().map(|c| c.label.as_str()).collect();
        assert!(labels.contains(&"fun"));
        assert!(labels.contains(&"let"));
        assert!(labels.contains(&"if"));
    }

    #[test]
    fn test_get_completions_includes_types() {
        let analyzer = SemanticAnalyzer::new();
        let completions = analyzer
            .get_completions(
                "",
                Position {
                    line: 0,
                    character: 0,
                },
            )
            .expect("should succeed");
        let labels: Vec<_> = completions.iter().map(|c| c.label.as_str()).collect();
        assert!(labels.contains(&"i32"));
        assert!(labels.contains(&"String"));
        assert!(labels.contains(&"bool"));
    }

    #[test]
    fn test_get_hover_info_returns_some() {
        let analyzer = SemanticAnalyzer::new();
        let hover = analyzer
            .get_hover_info(
                "let x = 42",
                Position {
                    line: 0,
                    character: 0,
                },
            )
            .expect("should succeed");
        assert!(hover.is_some());
    }

    #[test]
    fn test_get_definition_returns_none() {
        let analyzer = SemanticAnalyzer::new();
        let location = analyzer
            .get_definition(
                "let x = 42",
                Position {
                    line: 0,
                    character: 0,
                },
            )
            .expect("should succeed");
        assert!(location.is_none());
    }

    #[test]
    fn test_get_diagnostics_valid_code() {
        let mut analyzer = SemanticAnalyzer::new();
        let diagnostics = analyzer
            .get_diagnostics("let x = 42")
            .expect("should succeed");
        // Valid code should not produce diagnostics
        assert!(diagnostics.is_empty());
    }

    #[test]
    fn test_symbol_table_default() {
        let table = SymbolTable::default();
        assert!(table.symbols.is_empty());
    }

    #[test]
    fn test_symbol_clone() {
        let symbol = Symbol {
            name: "test".to_string(),
            kind: "variable".to_string(),
            documentation: Some("doc".to_string()),
        };
        let cloned = symbol.clone();
        assert_eq!(cloned.name, "test");
        assert_eq!(cloned.kind, "variable");
        assert_eq!(cloned.documentation, Some("doc".to_string()));
    }

    #[test]
    fn test_get_diagnostics_invalid_code() {
        let mut analyzer = SemanticAnalyzer::new();
        let diagnostics = analyzer
            .get_diagnostics("fun { incomplete")
            .expect("should succeed");
        // Invalid code should produce parse error diagnostic
        assert!(!diagnostics.is_empty());
    }
}