fish-shell/fish-shell · error
Function has no source range
Error message
Function has no source range
What it means
`FunctionDefinition::definition_lineno()` returns the 1-based line of a function's definition by consulting the source range of the function's parse node. If `func_node.try_source_range()` returns None — the AST node has no attached source span — the code panics with "Function has no source range". This is an internal invariant violation, meaning a function definition node reached line-number computation without valid source text (e.g. a synthesized or detached node).
Source
Thrown at src/function.rs:395
self.definition_file.as_ref().map(|f| f.as_utfstr())
}
/// Return a reference to the vars that this function has inherited from its definition scope.
pub fn inherit_vars(&self) -> &[(WString, Vec<WString>)] {
&self.inherit_vars
}
/// If this function is a copy, return a reference to the original definition file, or None if it was defined interactively or copied.
pub fn copy_definition_file(&self) -> Option<&wstr> {
self.copy_definition_file.as_ref().map(|f| f.as_utfstr())
}
/// Return the 1-based line number of the function's definition.
pub fn definition_lineno(&self) -> i32 {
// Return one plus the number of newlines at offsets less than the start of our function's
// statement (which includes the header).
let Some(source_range) = self.func_node.try_source_range() else {
panic!("Function has no source range");
};
let func_start = source_range.start as usize;
let source = &self.func_node.parsed_source().src;
assert!(
func_start <= source.char_count(),
"function start out of bounds"
);
1 + source
.slice_to(func_start)
.chars()
.filter(|&c| c == '\n')
.count() as i32
}
/// If this function is a copy, return the original 1-based line number. Otherwise, return 0.
pub fn copy_definition_lineno(&self) -> u32 {
self.copy_definition_lineno.map_or(0, |val| val.get())
}View on GitHub (pinned to edb719d76c)
Solutions
- Ensure the function definition comes from a real `parse()` of non-empty source so its node has a source range
- Do not call `definition_lineno()` on synthesized/constructed FunctionDefinition values; track line numbers separately
- Check `func_node.try_source_range()` yourself and fall back to 0 or an Option instead of panicking
- Update/re-parse the source after any tree mutation before asking for line numbers
Example fix
// before let lineno = func.definition_lineno(); // after let lineno = func.func_node.try_source_range().map(|r| (r.start as usize) as i32 + 1).unwrap_or(0);
Defensive patterns
Strategy: type-guard
Validate before calling
if func.func_node.try_source_range().is_none() { /* skip or fallback */ } Type guard
fn has_source_range(f: &FunctionDefinition) -> bool { f.func_node.try_source_range().is_some() } Try / catch
std::panic::catch_unwind(|| func.definition_lineno()).unwrap_or(0)
Prevention
- Only query line numbers for nodes parsed from real source text
- Never hand-construct parse nodes in production code
- Re-parse after tree mutations before reading source ranges
When it happens
Trigger: Calling `definition_lineno()` on a FunctionDefinition whose `func_node` was not produced from a real parse of source text, or whose node lost its source range (synthesized node, node from a reused/trimmed parse tree).
Common situations: Programmatically constructing function definitions in tests or tooling; parsing pathological/edge-case input where source ranges are elided; using a FunctionDefinition obtained from a modified parse tree after edits.
Related errors
- Unexpected keyword in statement decoration
- Internal parse error from {$func} - this indicates a bug in
- Expected switch statement
- Not a char type
- Is not an argument
AI-assisted analysis of fish-shell/fish-shell@edb719d76c (2026-09-03).
Data as JSON: /api/errors/c06f28769980583a.
Report an issue: GitHub.