feat: add benchmarks and enhance LSP completions/hover

Benchmarks:
- Add fib, list_ops, primes benchmarks comparing Lux vs Node.js vs Rust
- Lux matches Rust performance and is 8-30x faster than Node.js
- Add docs/benchmarks.md documenting results

LSP improvements:
- Context-aware completions (module access vs general)
- Add List, String, Option, Result, Console, Math method completions
- Add type and builtin completions
- Hover now shows type signatures and documentation for known symbols
- Hover returns formatted markdown with code blocks

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-14 15:23:35 -05:00
parent 1ca31fe985
commit 2960dd6538
12 changed files with 647 additions and 72 deletions

View File

@@ -212,88 +212,289 @@ impl LspServer {
fn handle_hover(&self, params: HoverParams) -> Option<Hover> {
let uri = params.text_document_position_params.text_document.uri;
let _position = params.text_document_position_params.position;
let position = params.text_document_position_params.position;
let _source = self.documents.get(&uri)?;
let source = self.documents.get(&uri)?;
// For now, return basic hover info
// A full implementation would find the symbol at the position
// and return its type and documentation
Some(Hover {
contents: HoverContents::Markup(MarkupContent {
kind: MarkupKind::Markdown,
value: "Lux language element".to_string(),
}),
range: None,
})
// Extract the word at the cursor position
let word = self.get_word_at_position(source, position)?;
// Look up documentation for known symbols
let info = self.get_symbol_info(&word);
if let Some((signature, doc)) = info {
Some(Hover {
contents: HoverContents::Markup(MarkupContent {
kind: MarkupKind::Markdown,
value: format!("```lux\n{}\n```\n\n{}", signature, doc),
}),
range: None,
})
} else {
// Return generic info for unknown symbols
None
}
}
fn get_word_at_position(&self, source: &str, position: Position) -> Option<String> {
let lines: Vec<&str> = source.lines().collect();
let line = lines.get(position.line as usize)?;
let col = position.character as usize;
// Find word boundaries
let start = line[..col.min(line.len())]
.rfind(|c: char| !c.is_alphanumeric() && c != '_')
.map(|i| i + 1)
.unwrap_or(0);
let end = line[col.min(line.len())..]
.find(|c: char| !c.is_alphanumeric() && c != '_')
.map(|i| col + i)
.unwrap_or(line.len());
if start < end {
Some(line[start..end].to_string())
} else {
None
}
}
fn get_symbol_info(&self, word: &str) -> Option<(&'static str, &'static str)> {
match word {
// Keywords
"fn" => Some(("fn name(params): ReturnType = body", "Declare a function")),
"let" => Some(("let name = value", "Bind a value to a name")),
"if" => Some(("if condition then expr else expr", "Conditional expression")),
"match" => Some(("match value { pattern => expr, ... }", "Pattern matching")),
"type" => Some(("type Name = TypeExpr", "Define a type alias or ADT")),
"effect" => Some(("effect Name { fn op(): Type }", "Define an effect")),
"handler" => Some(("handler { op() => impl }", "Handle effects")),
"trait" => Some(("trait Name { fn method(): Type }", "Define a trait")),
"impl" => Some(("impl Trait for Type { ... }", "Implement a trait")),
// List functions
"map" => Some(("List.map(list: List<A>, f: A -> B): List<B>", "Transform each element in a list")),
"filter" => Some(("List.filter(list: List<A>, p: A -> Bool): List<A>", "Keep elements matching predicate")),
"fold" => Some(("List.fold(list: List<A>, init: B, f: (B, A) -> B): B", "Reduce list to single value")),
"reverse" => Some(("List.reverse(list: List<A>): List<A>", "Reverse a list")),
"concat" => Some(("List.concat(a: List<A>, b: List<A>): List<A>", "Concatenate two lists")),
"range" => Some(("List.range(start: Int, end: Int): List<Int>", "Create a list from start to end-1")),
"length" => Some(("List.length(list: List<A>): Int", "Get the length of a list")),
"head" => Some(("List.head(list: List<A>): Option<A>", "Get the first element")),
"tail" => Some(("List.tail(list: List<A>): List<A>", "Get all elements except the first")),
"isEmpty" => Some(("List.isEmpty(list: List<A>): Bool", "Check if list is empty")),
// Option/Result
"Some" => Some(("Some(value: A): Option<A>", "Wrap a value in Some")),
"None" => Some(("None: Option<A>", "The empty Option")),
"Ok" => Some(("Ok(value: A): Result<A, E>", "Successful result")),
"Err" => Some(("Err(error: E): Result<A, E>", "Error result")),
"isSome" => Some(("Option.isSome(opt: Option<A>): Bool", "Check if Option has a value")),
"isNone" => Some(("Option.isNone(opt: Option<A>): Bool", "Check if Option is empty")),
"getOrElse" => Some(("Option.getOrElse(opt: Option<A>, default: A): A", "Get value or default")),
// Console
"print" => Some(("Console.print(msg: String): Unit", "Print a message to the console")),
"readLine" => Some(("Console.readLine(): String", "Read a line from input")),
"readInt" => Some(("Console.readInt(): Int", "Read an integer from input")),
// Types
"Int" => Some(("type Int", "64-bit signed integer")),
"Float" => Some(("type Float", "64-bit floating point number")),
"Bool" => Some(("type Bool", "Boolean (true or false)")),
"String" => Some(("type String", "UTF-8 string")),
"Unit" => Some(("type Unit", "Unit type (no value)")),
"List" => Some(("type List<A>", "Generic list/array type")),
"Option" => Some(("type Option<A> = Some(A) | None", "Optional value (Some or None)")),
"Result" => Some(("type Result<A, E> = Ok(A) | Err(E)", "Result of fallible operation")),
// Built-in functions
"toString" => Some(("toString(value: A): String", "Convert any value to a string")),
_ => None,
}
}
fn handle_completion(&self, params: CompletionParams) -> Option<CompletionResponse> {
let _uri = params.text_document_position.text_document.uri;
let uri = params.text_document_position.text_document.uri;
let position = params.text_document_position.position;
// Return basic completions
// A full implementation would analyze context and provide
// relevant completions based on scope
let items = vec![
CompletionItem {
label: "fn".to_string(),
kind: Some(CompletionItemKind::KEYWORD),
detail: Some("Function declaration".to_string()),
..Default::default()
},
CompletionItem {
label: "let".to_string(),
kind: Some(CompletionItemKind::KEYWORD),
detail: Some("Variable binding".to_string()),
..Default::default()
},
CompletionItem {
label: "if".to_string(),
kind: Some(CompletionItemKind::KEYWORD),
detail: Some("Conditional expression".to_string()),
..Default::default()
},
CompletionItem {
label: "match".to_string(),
kind: Some(CompletionItemKind::KEYWORD),
detail: Some("Pattern matching".to_string()),
..Default::default()
},
CompletionItem {
label: "type".to_string(),
kind: Some(CompletionItemKind::KEYWORD),
detail: Some("Type declaration".to_string()),
..Default::default()
},
CompletionItem {
label: "effect".to_string(),
kind: Some(CompletionItemKind::KEYWORD),
detail: Some("Effect declaration".to_string()),
..Default::default()
},
CompletionItem {
label: "handler".to_string(),
kind: Some(CompletionItemKind::KEYWORD),
detail: Some("Effect handler".to_string()),
..Default::default()
},
CompletionItem {
label: "trait".to_string(),
kind: Some(CompletionItemKind::KEYWORD),
detail: Some("Trait declaration".to_string()),
..Default::default()
},
CompletionItem {
label: "impl".to_string(),
kind: Some(CompletionItemKind::KEYWORD),
detail: Some("Trait implementation".to_string()),
..Default::default()
},
];
// Check context to provide relevant completions
let source = self.documents.get(&uri)?;
let trigger_context = self.get_completion_context(source, position);
let mut items = Vec::new();
// If triggered after a dot, provide module/method completions
if trigger_context == CompletionContext::ModuleAccess {
// Add List module functions
items.extend(self.get_list_completions());
// Add String module functions
items.extend(self.get_string_completions());
// Add Option/Result completions
items.extend(self.get_option_result_completions());
// Add Console functions
items.extend(self.get_console_completions());
// Add Math functions
items.extend(self.get_math_completions());
} else {
// General completions (keywords + common functions)
items.extend(self.get_keyword_completions());
items.extend(self.get_builtin_completions());
items.extend(self.get_type_completions());
}
Some(CompletionResponse::Array(items))
}
fn get_completion_context(&self, source: &str, position: Position) -> CompletionContext {
// Find the character before the cursor
let offset = self.position_to_offset(source, position);
if offset > 0 {
let prev_char = source.chars().nth(offset - 1);
if prev_char == Some('.') {
return CompletionContext::ModuleAccess;
}
}
CompletionContext::General
}
fn position_to_offset(&self, source: &str, position: Position) -> usize {
let mut offset = 0;
let mut line = 0u32;
for (i, c) in source.char_indices() {
if line == position.line {
let col = i - offset;
return offset + (position.character as usize).min(col + 1);
}
if c == '\n' {
line += 1;
offset = i + 1;
}
}
source.len()
}
fn get_keyword_completions(&self) -> Vec<CompletionItem> {
vec![
completion_item("fn", CompletionItemKind::KEYWORD, "Function declaration"),
completion_item("let", CompletionItemKind::KEYWORD, "Variable binding"),
completion_item("if", CompletionItemKind::KEYWORD, "Conditional expression"),
completion_item("then", CompletionItemKind::KEYWORD, "Then branch"),
completion_item("else", CompletionItemKind::KEYWORD, "Else branch"),
completion_item("match", CompletionItemKind::KEYWORD, "Pattern matching"),
completion_item("type", CompletionItemKind::KEYWORD, "Type declaration"),
completion_item("effect", CompletionItemKind::KEYWORD, "Effect declaration"),
completion_item("handler", CompletionItemKind::KEYWORD, "Effect handler"),
completion_item("trait", CompletionItemKind::KEYWORD, "Trait declaration"),
completion_item("impl", CompletionItemKind::KEYWORD, "Trait implementation"),
completion_item("import", CompletionItemKind::KEYWORD, "Import declaration"),
completion_item("return", CompletionItemKind::KEYWORD, "Return from function"),
completion_item("run", CompletionItemKind::KEYWORD, "Run effectful computation"),
completion_item("with", CompletionItemKind::KEYWORD, "With handler"),
completion_item("true", CompletionItemKind::KEYWORD, "Boolean true"),
completion_item("false", CompletionItemKind::KEYWORD, "Boolean false"),
]
}
fn get_builtin_completions(&self) -> Vec<CompletionItem> {
vec![
completion_item("List", CompletionItemKind::MODULE, "List module"),
completion_item("String", CompletionItemKind::MODULE, "String module"),
completion_item("Console", CompletionItemKind::MODULE, "Console I/O"),
completion_item("Math", CompletionItemKind::MODULE, "Math functions"),
completion_item("Option", CompletionItemKind::MODULE, "Option type"),
completion_item("Result", CompletionItemKind::MODULE, "Result type"),
completion_item("Some", CompletionItemKind::CONSTRUCTOR, "Option.Some constructor"),
completion_item("None", CompletionItemKind::CONSTRUCTOR, "Option.None constructor"),
completion_item("Ok", CompletionItemKind::CONSTRUCTOR, "Result.Ok constructor"),
completion_item("Err", CompletionItemKind::CONSTRUCTOR, "Result.Err constructor"),
completion_item("toString", CompletionItemKind::FUNCTION, "Convert value to string"),
]
}
fn get_type_completions(&self) -> Vec<CompletionItem> {
vec![
completion_item("Int", CompletionItemKind::TYPE_PARAMETER, "Integer type"),
completion_item("Float", CompletionItemKind::TYPE_PARAMETER, "Floating point type"),
completion_item("Bool", CompletionItemKind::TYPE_PARAMETER, "Boolean type"),
completion_item("String", CompletionItemKind::TYPE_PARAMETER, "String type"),
completion_item("Unit", CompletionItemKind::TYPE_PARAMETER, "Unit type"),
completion_item("List", CompletionItemKind::TYPE_PARAMETER, "List type"),
completion_item("Option", CompletionItemKind::TYPE_PARAMETER, "Option type"),
completion_item("Result", CompletionItemKind::TYPE_PARAMETER, "Result type"),
]
}
fn get_list_completions(&self) -> Vec<CompletionItem> {
vec![
completion_item_with_doc("length", CompletionItemKind::METHOD, "List.length(list)", "Get the length of a list"),
completion_item_with_doc("head", CompletionItemKind::METHOD, "List.head(list)", "Get first element (returns Option)"),
completion_item_with_doc("tail", CompletionItemKind::METHOD, "List.tail(list)", "Get all but first element"),
completion_item_with_doc("map", CompletionItemKind::METHOD, "List.map(list, fn)", "Transform each element"),
completion_item_with_doc("filter", CompletionItemKind::METHOD, "List.filter(list, predicate)", "Keep elements matching predicate"),
completion_item_with_doc("fold", CompletionItemKind::METHOD, "List.fold(list, init, fn)", "Reduce list to single value"),
completion_item_with_doc("reverse", CompletionItemKind::METHOD, "List.reverse(list)", "Reverse list order"),
completion_item_with_doc("concat", CompletionItemKind::METHOD, "List.concat(a, b)", "Concatenate two lists"),
completion_item_with_doc("range", CompletionItemKind::METHOD, "List.range(start, end)", "Create list from range"),
completion_item_with_doc("get", CompletionItemKind::METHOD, "List.get(list, index)", "Get element at index (returns Option)"),
completion_item_with_doc("find", CompletionItemKind::METHOD, "List.find(list, predicate)", "Find first matching element"),
completion_item_with_doc("isEmpty", CompletionItemKind::METHOD, "List.isEmpty(list)", "Check if list is empty"),
completion_item_with_doc("take", CompletionItemKind::METHOD, "List.take(list, n)", "Take first n elements"),
completion_item_with_doc("drop", CompletionItemKind::METHOD, "List.drop(list, n)", "Drop first n elements"),
completion_item_with_doc("any", CompletionItemKind::METHOD, "List.any(list, predicate)", "Check if any element matches"),
completion_item_with_doc("all", CompletionItemKind::METHOD, "List.all(list, predicate)", "Check if all elements match"),
]
}
fn get_string_completions(&self) -> Vec<CompletionItem> {
vec![
completion_item_with_doc("length", CompletionItemKind::METHOD, "String.length(s)", "Get string length"),
completion_item_with_doc("split", CompletionItemKind::METHOD, "String.split(s, delimiter)", "Split string by delimiter"),
completion_item_with_doc("join", CompletionItemKind::METHOD, "String.join(list, delimiter)", "Join strings with delimiter"),
completion_item_with_doc("trim", CompletionItemKind::METHOD, "String.trim(s)", "Remove leading/trailing whitespace"),
completion_item_with_doc("contains", CompletionItemKind::METHOD, "String.contains(s, substr)", "Check if contains substring"),
completion_item_with_doc("replace", CompletionItemKind::METHOD, "String.replace(s, from, to)", "Replace occurrences"),
completion_item_with_doc("chars", CompletionItemKind::METHOD, "String.chars(s)", "Get list of characters"),
completion_item_with_doc("lines", CompletionItemKind::METHOD, "String.lines(s)", "Split into lines"),
completion_item_with_doc("fromChar", CompletionItemKind::METHOD, "String.fromChar(c)", "Convert char to string"),
]
}
fn get_option_result_completions(&self) -> Vec<CompletionItem> {
vec![
completion_item_with_doc("isSome", CompletionItemKind::METHOD, "Option.isSome(opt)", "Check if Option has value"),
completion_item_with_doc("isNone", CompletionItemKind::METHOD, "Option.isNone(opt)", "Check if Option is empty"),
completion_item_with_doc("getOrElse", CompletionItemKind::METHOD, "Option.getOrElse(opt, default)", "Get value or default"),
completion_item_with_doc("map", CompletionItemKind::METHOD, "Option.map(opt, fn)", "Transform value if present"),
completion_item_with_doc("flatMap", CompletionItemKind::METHOD, "Option.flatMap(opt, fn)", "Chain Option operations"),
completion_item_with_doc("isOk", CompletionItemKind::METHOD, "Result.isOk(result)", "Check if Result is Ok"),
completion_item_with_doc("isErr", CompletionItemKind::METHOD, "Result.isErr(result)", "Check if Result is Err"),
]
}
fn get_console_completions(&self) -> Vec<CompletionItem> {
vec![
completion_item_with_doc("print", CompletionItemKind::METHOD, "Console.print(msg)", "Print to console"),
completion_item_with_doc("readLine", CompletionItemKind::METHOD, "Console.readLine()", "Read line from input"),
completion_item_with_doc("readInt", CompletionItemKind::METHOD, "Console.readInt()", "Read integer from input"),
]
}
fn get_math_completions(&self) -> Vec<CompletionItem> {
vec![
completion_item_with_doc("abs", CompletionItemKind::METHOD, "Math.abs(n)", "Absolute value"),
completion_item_with_doc("min", CompletionItemKind::METHOD, "Math.min(a, b)", "Minimum of two values"),
completion_item_with_doc("max", CompletionItemKind::METHOD, "Math.max(a, b)", "Maximum of two values"),
completion_item_with_doc("sqrt", CompletionItemKind::METHOD, "Math.sqrt(n)", "Square root"),
completion_item_with_doc("pow", CompletionItemKind::METHOD, "Math.pow(base, exp)", "Power function"),
completion_item_with_doc("floor", CompletionItemKind::METHOD, "Math.floor(n)", "Round down"),
completion_item_with_doc("ceil", CompletionItemKind::METHOD, "Math.ceil(n)", "Round up"),
completion_item_with_doc("round", CompletionItemKind::METHOD, "Math.round(n)", "Round to nearest"),
]
}
fn handle_goto_definition(
&self,
_params: GotoDefinitionParams,
@@ -350,3 +551,41 @@ where
}
}
}
/// Context for completion suggestions
#[derive(PartialEq)]
enum CompletionContext {
/// After a dot (e.g., "List.")
ModuleAccess,
/// General context (keywords, types, etc.)
General,
}
/// Create a simple completion item
fn completion_item(label: &str, kind: CompletionItemKind, detail: &str) -> CompletionItem {
CompletionItem {
label: label.to_string(),
kind: Some(kind),
detail: Some(detail.to_string()),
..Default::default()
}
}
/// Create a completion item with documentation
fn completion_item_with_doc(
label: &str,
kind: CompletionItemKind,
signature: &str,
doc: &str,
) -> CompletionItem {
CompletionItem {
label: label.to_string(),
kind: Some(kind),
detail: Some(signature.to_string()),
documentation: Some(lsp_types::Documentation::MarkupContent(MarkupContent {
kind: MarkupKind::Markdown,
value: doc.to_string(),
})),
..Default::default()
}
}