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

  1. Ensure the function definition comes from a real `parse()` of non-empty source so its node has a source range
  2. Do not call `definition_lineno()` on synthesized/constructed FunctionDefinition values; track line numbers separately
  3. Check `func_node.try_source_range()` yourself and fall back to 0 or an Option instead of panicking
  4. 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

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


AI-assisted analysis of fish-shell/fish-shell@edb719d76c (2026-09-03). Data as JSON: /api/errors/c06f28769980583a. Report an issue: GitHub.