Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Update top-level definitions to use new diagnostics #604

Merged
merged 4 commits into from
Nov 11, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
45 changes: 45 additions & 0 deletions guppylang/checker/errors/generic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from dataclasses import dataclass
from typing import ClassVar

from guppylang.diagnostic import Error


@dataclass(frozen=True)
class UnsupportedError(Error):
title: ClassVar[str] = "Unsupported"
span_label: ClassVar[str] = "{things} {is_are} not supported{extra}"
things: str
singular: bool = False
unsupported_in: str = ""

@property
def is_are(self) -> str:
return "is" if self.singular else "are"

@property
def extra(self) -> str:
return f" in {self.unsupported_in}" if self.unsupported_in else ""


@dataclass(frozen=True)
class UnexpectedError(Error):
title: ClassVar[str] = "Unexpected {things}"
span_label: ClassVar[str] = "Unexpected {things}{extra}"
things: str
unexpected_in: str = ""

@property
def extra(self) -> str:
return f" in {self.unexpected_in}" if self.unexpected_in else ""


@dataclass(frozen=True)
class ExpectedError(Error):
title: ClassVar[str] = "Expected {things}"
span_label: ClassVar[str] = "Expected {things}{extra}"
things: str
got: str = ""

@property
def extra(self) -> str:
return f", got {self.got}" if self.got else ""
10 changes: 10 additions & 0 deletions guppylang/definition/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from hugr.build.dfg import DefinitionBuilder, OpVar

from guppylang.diagnostic import Fatal
from guppylang.span import SourceMap

if TYPE_CHECKING:
Expand Down Expand Up @@ -157,3 +158,12 @@ def compile_inner(self, globals: "CompiledGlobals") -> None:
Opposed to `CompilableDef.compile()`, we have access to all other compiled
definitions here, which allows things like mutual recursion.
"""


@dataclass(frozen=True)
class UnknownSourceError(Fatal):
title: ClassVar[str] = "Cannot find source"
message: ClassVar[str] = (
"Unable to look up the source code for Python object `{obj}`"
)
obj: object
49 changes: 39 additions & 10 deletions guppylang/definition/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,20 @@
from abc import ABC, abstractmethod
from collections.abc import Callable, Sequence
from dataclasses import dataclass, field
from typing import ClassVar

from hugr import Wire, ops
from hugr import tys as ht
from hugr.build.dfg import DfBase

from guppylang.ast_util import AstNode, get_type, with_loc, with_type
from guppylang.ast_util import AstNode, get_type, has_empty_body, with_loc, with_type
from guppylang.checker.core import Context, Globals
from guppylang.checker.expr_checker import check_call, synthesize_call
from guppylang.checker.func_checker import check_signature
from guppylang.compiler.core import CompiledGlobals, DFContainer
from guppylang.definition.common import ParsableDef
from guppylang.definition.value import CallReturnWires, CompiledCallableDef
from guppylang.diagnostic import Error, Help
from guppylang.error import GuppyError, InternalGuppyError
from guppylang.nodes import GlobalCall
from guppylang.span import SourceMap
Expand All @@ -28,6 +30,36 @@
)


@dataclass(frozen=True)
class BodyNotEmptyError(Error):
title: ClassVar[str] = "Unexpected function body"
span_label: ClassVar[str] = "Body of custom function `{name}` must be empty"
name: str


@dataclass(frozen=True)
class NoSignatureError(Error):
title: ClassVar[str] = "Type signature missing"
span_label: ClassVar[str] = "Custom function `{name}` requires a type signature"
name: str

@dataclass(frozen=True)
class Suggestion(Help):
message: ClassVar[str] = (
"Annotate the type signature of `{name}` or disallow the use of `{name}` "
"as a higher-order value: `@guppy.custom(..., higher_order_value=False)`"
)


@dataclass(frozen=True)
class NotHigherOrderError(Error):
title: ClassVar[str] = "Not higher-order"
span_label: ClassVar[str] = (
"Function `{name}` may not be used as a higher-order value"
)
name: str


@dataclass(frozen=True)
class RawCustomFunctionDef(ParsableDef):
"""A raw custom function definition provided by the user.
Expand Down Expand Up @@ -69,6 +101,8 @@ def parse(self, globals: "Globals", sources: SourceMap) -> "CustomFunctionDef":
code. The only information we need to access is that it's a function type and
that there are no unsolved existential vars.
"""
if not has_empty_body(self.defined_at):
raise GuppyError(BodyNotEmptyError(self.defined_at.body[0], self.name))
Copy link
Contributor

Choose a reason for hiding this comment

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

no coverage, ok?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Added a test 👍

sig = self._get_signature(globals)
ty = sig or FunctionType([], NoneType())
return CustomFunctionDef(
Expand Down Expand Up @@ -122,11 +156,9 @@ def _get_signature(self, globals: Globals) -> FunctionType | None:
)

if requires_type_annotation and not has_type_annotation:
raise GuppyError(
f"Type signature for function `{self.name}` is required. "
"Alternatively, try passing `higher_order_value=False` on definition.",
self.defined_at,
)
err = NoSignatureError(self.defined_at, self.name)
err.add_sub_diagnostic(NoSignatureError.Suggestion(None))
Copy link
Contributor

Choose a reason for hiding this comment

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

What does this do?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think it uses the default message?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The None means that this sub diagnostic does not have span, it's just rendered as text below the main error.

I updated to code so that this subdiagnostic is added by default

raise GuppyError(err)

if requires_type_annotation:
return check_signature(self.defined_at, globals)
Expand Down Expand Up @@ -196,10 +228,7 @@ def load_with_args(
"""
# TODO: This should be raised during checking, not compilation!
if not self.higher_order_value:
raise GuppyError(
"This function does not support usage in a higher-order context",
node,
)
raise GuppyError(NotHigherOrderError(node, self.name))
assert len(self.ty.params) == len(type_args)

# We create a `FunctionDef` that takes some inputs, compiles a call to the
Expand Down
13 changes: 10 additions & 3 deletions guppylang/definition/declaration.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import ast
from dataclasses import dataclass, field
from typing import ClassVar

from hugr import Node, Wire
from hugr import tys as ht
Expand All @@ -14,13 +15,21 @@
from guppylang.definition.common import CompilableDef, ParsableDef
from guppylang.definition.function import PyFunc, parse_py_func
from guppylang.definition.value import CallableDef, CallReturnWires, CompiledCallableDef
from guppylang.diagnostic import Error
from guppylang.error import GuppyError
from guppylang.nodes import GlobalCall
from guppylang.span import SourceMap
from guppylang.tys.subst import Inst, Subst
from guppylang.tys.ty import Type, type_to_row


@dataclass(frozen=True)
class BodyNotEmptyError(Error):
title: ClassVar[str] = "Unexpected function body"
span_label: ClassVar[str] = "Body of declared function `{name}` must be empty"
name: str


@dataclass(frozen=True)
class RawFunctionDecl(ParsableDef):
"""A raw function declaration provided by the user.
Expand All @@ -38,9 +47,7 @@ def parse(self, globals: Globals, sources: SourceMap) -> "CheckedFunctionDecl":
func_ast, docstring = parse_py_func(self.python_func, sources)
ty = check_signature(func_ast, globals.with_python_scope(self.python_scope))
if not has_empty_body(func_ast):
raise GuppyError(
"Body of function declaration must be empty", func_ast.body[0]
)
raise GuppyError(BodyNotEmptyError(func_ast.body[0], self.name))
return CheckedFunctionDecl(
self.id,
self.name,
Expand Down
16 changes: 10 additions & 6 deletions guppylang/definition/function.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from guppylang.ast_util import AstNode, annotate_location, with_loc
from guppylang.checker.cfg_checker import CheckedCFG
from guppylang.checker.core import Context, Globals, Place, PyScope
from guppylang.checker.errors.generic import ExpectedError, UnsupportedError
from guppylang.checker.expr_checker import check_call, synthesize_call
from guppylang.checker.func_checker import (
check_global_func_def,
Expand All @@ -22,7 +23,12 @@
)
from guppylang.compiler.core import CompiledGlobals, DFContainer
from guppylang.compiler.func_compiler import compile_global_func_def
from guppylang.definition.common import CheckableDef, CompilableDef, ParsableDef
from guppylang.definition.common import (
CheckableDef,
CompilableDef,
ParsableDef,
UnknownSourceError,
)
from guppylang.definition.value import CallableDef, CallReturnWires, CompiledCallableDef
from guppylang.error import GuppyError
from guppylang.ipython_inspect import find_ipython_def, is_running_ipython
Expand Down Expand Up @@ -60,9 +66,7 @@ def parse(self, globals: Globals, sources: SourceMap) -> "ParsedFunctionDef":
func_ast, docstring = parse_py_func(self.python_func, sources)
ty = check_signature(func_ast, globals.with_python_scope(self.python_scope))
if ty.parametrized:
raise GuppyError(
"Generic function definitions are not supported yet", func_ast
)
raise GuppyError(UnsupportedError(func_ast, "Generic function definitions"))
return ParsedFunctionDef(
self.id, self.name, func_ast, ty, self.python_scope, docstring
)
Expand Down Expand Up @@ -251,9 +255,9 @@ def parse_py_func(f: PyFunc, sources: SourceMap) -> tuple[ast.FunctionDef, str |
else:
file = inspect.getsourcefile(f)
if file is None:
raise GuppyError("Couldn't determine source file for function")
raise GuppyError(UnknownSourceError(None, f))
doug-q marked this conversation as resolved.
Show resolved Hide resolved
sources.add_file(file)
annotate_location(func_ast, source, file, line_offset)
if not isinstance(func_ast, ast.FunctionDef):
raise GuppyError("Expected a function definition", func_ast)
raise GuppyError(ExpectedError(func_ast, "a function definition"))
Copy link
Contributor

Choose a reason for hiding this comment

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

no coverage, ok?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Added a test 👍

return parse_function_with_docstring(func_ast)
Loading
Loading