Skip to content

fix(checker): Improve precision on span of missing return type error to function header#1882

Open
luffy-orf wants to merge 3 commits into
Quantinuum:mainfrom
luffy-orf:fix/1868-missing-return-type-error-span
Open

fix(checker): Improve precision on span of missing return type error to function header#1882
luffy-orf wants to merge 3 commits into
Quantinuum:mainfrom
luffy-orf:fix/1868-missing-return-type-error-span

Conversation

@luffy-orf

Copy link
Copy Markdown
Contributor

Summary

  • Fixes [Feature]: Improve error message for missing return type #1868 by highlighting only the function signature (up to the trailing :) for MissingReturnAnnotationError, instead of the entire function definition
  • Adds function_header_span() in span.py to compute the correct diagnostic span
  • Updates golden error snapshots for missing return type cases

Test plan

  • uv run pytest tests/error/test_misc_errors.py -k "return_not_annotated" -v
  • uv run pytest tests/error/test_misc_errors.py -v
  • uv run ruff check guppylang-internals/src/guppylang_internals/span.py guppylang-internals/src/guppylang_internals/checker/func_checker.py

@luffy-orf luffy-orf requested a review from a team as a code owner June 17, 2026 17:11
@maximilianruesch maximilianruesch changed the title fix(checker): narrow missing return type error span to function header fix(checker): Improve precision on span of missing return type error to function header Jun 18, 2026

@maximilianruesch maximilianruesch left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, thank you for the PR! The error message is definitely improved!

I am a bit careful about the slightly hacky approach (with the manual parsing), but I see that the AST module does not expose enough information to handle things in another way really. Nonetheless, I have some suggestions to improve the code as well as some suggestions!

Comment on lines +135 to +138
if source is None or file is None or line_offset is None:
if func_def.body:
return Span(start, to_span(func_def.body[0]).start)
return Span(start, to_span(func_def).end)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is a possibility for the user, you may want to add test cases covering this (or assert that they are present already), and if it is not, assert that these cases are not present and move on?

Comment on lines +135 to +138
if source is None or file is None or line_offset is None:
if func_def.body:
return Span(start, to_span(func_def.body[0]).start)
return Span(start, to_span(func_def).end)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if source is None or file is None or line_offset is None:
if func_def.body:
return Span(start, to_span(func_def.body[0]).start)
return Span(start, to_span(func_def).end)
if source is None or file is None or line_offset is None:
if func_def.body:
return Span(start, to_span(func_def.body[0]).start)
return to_span(func_def)

The work is done anyway, and if I understand correctly these should be equivalent. Imo the intention of "giving up" is more understandable this way.



def function_header_span(func_def: ast.FunctionDef) -> Span:
"""Returns a span covering only the function header up to and including `:`."""

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also describe cases where this is not possible, i.e. what the span will cover when one cannot identify the header span. So perhaps something like (with suitable line breaks for max line length):

Suggested change
"""Returns a span covering only the function header up to and including `:`."""
"""Returns a span covering only the function header up to and including `:` on a best-effort basis, falling back to the full function definition."""

However, if you can prove that your below cases can all make the span exact (via a short argument), you do not need to add this.

Comment on lines +145 to +146
for col in range(col_begin, len(lines[i])):
char = lines[i][col]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
for col in range(col_begin, len(lines[i])):
char = lines[i][col]
for char in lines[i][col_begin:]:

A bit of Python splicing magic. Also handles out of bounds equally as gracefullly. Probably improves performance a touch too, but I am not worried about that here (we are already exclusively in a failure case).

You can do a very similar thing to the outer loop.

file = get_file(func_def)
line_offset = get_line_offset(func_def)
if source is None or file is None or line_offset is None:
if func_def.body:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you be more explicit here, i.e.

Suggested change
if func_def.body:
if len(func_def.body) > 0:

@luffy-orf

luffy-orf commented Jun 18, 2026

Copy link
Copy Markdown
Contributor Author

@maximilianruesch Thanks for the review! Addressed all suggestions:

  • Removed the fallback branch and asserted that source metadata is always present (all check_signature call paths go through annotate_location).
  • Simplified the loops using slicing/enumerate.
  • Added tests/test_function_header_span.py covering single-line, annotated, multiline, and spaced-colon signatures.

@acl-cqc

acl-cqc commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

As an alternative approach #1846 tackles a similar problem using subcomponents of the AST node. In that PR I specifically wanted to include the decorators, which you might not want here, but exclude the function body (the main problem with falling back to the definition!). Does that work here?

@maximilianruesch

Copy link
Copy Markdown
Collaborator

@acl-cqc I cannot immediately see how that changeset is relevant here. Specifically you improve error messages at call sites, whereas here we improve error messages at function definition sites. I also could not find measures taken in that PR that improve on function definition sites. Could you elaborate on this please?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Improve error message for missing return type

3 participants