-
Notifications
You must be signed in to change notification settings - Fork 3
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
Changes from 1 commit
04282ac
afe8801
d8b5c78
a7f6aa1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 "" |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
|
@@ -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. | ||
|
@@ -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)) | ||
sig = self._get_signature(globals) | ||
ty = sig or FunctionType([], NoneType()) | ||
return CustomFunctionDef( | ||
|
@@ -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)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What does this do? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it uses the default message? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The 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) | ||
|
@@ -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 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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, | ||
|
@@ -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 | ||
|
@@ -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 | ||
) | ||
|
@@ -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")) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no coverage, ok? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added a test 👍 |
||
return parse_function_with_docstring(func_ast) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no coverage, ok?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a test 👍