-
Notifications
You must be signed in to change notification settings - Fork 49
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor[next] error handling (#1275)
Replace exceptions and error handling with a consistent, improved model.
- Loading branch information
Showing
40 changed files
with
1,044 additions
and
809 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
# GT4Py - GridTools Framework | ||
# | ||
# Copyright (c) 2014-2023, ETH Zurich | ||
# All rights reserved. | ||
# | ||
# This file is part of the GT4Py project and the GridTools framework. | ||
# GT4Py is free software: you can redistribute it and/or modify it under | ||
# the terms of the GNU General Public License as published by the | ||
# Free Software Foundation, either version 3 of the License, or any later | ||
# version. See the LICENSE.txt file at the top-level directory of this | ||
# distribution for a copy of the license or check <https://www.gnu.org/licenses/>. | ||
# | ||
# SPDX-License-Identifier: GPL-3.0-or-later | ||
|
||
"""Contains the exception classes and other utilities for error handling.""" | ||
|
||
from . import ( # noqa: module needs to be loaded for pretty printing of uncaught exceptions. | ||
excepthook, | ||
) | ||
from .excepthook import set_verbose_exceptions | ||
from .exceptions import ( | ||
DSLError, | ||
InvalidParameterAnnotationError, | ||
MissingAttributeError, | ||
MissingParameterAnnotationError, | ||
UndefinedSymbolError, | ||
UnsupportedPythonFeatureError, | ||
) | ||
|
||
|
||
__all__ = [ | ||
"DSLError", | ||
"InvalidParameterAnnotationError", | ||
"MissingAttributeError", | ||
"MissingParameterAnnotationError", | ||
"UndefinedSymbolError", | ||
"UnsupportedPythonFeatureError", | ||
"set_verbose_exceptions", | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
# GT4Py - GridTools Framework | ||
# | ||
# Copyright (c) 2014-2023, ETH Zurich | ||
# All rights reserved. | ||
# | ||
# This file is part of the GT4Py project and the GridTools framework. | ||
# GT4Py is free software: you can redistribute it and/or modify it under | ||
# the terms of the GNU General Public License as published by the | ||
# Free Software Foundation, either version 3 of the License, or any later | ||
# version. See the LICENSE.txt file at the top-level directory of this | ||
# distribution for a copy of the license or check <https://www.gnu.org/licenses/>. | ||
# | ||
# SPDX-License-Identifier: GPL-3.0-or-later | ||
|
||
""" | ||
Loading this module registers an excepthook that formats :class:`DSLError`. | ||
The excepthook is necessary because the default hook prints :class:`DSLError`s | ||
in an inconvenient way. The previously set excepthook is used to print all | ||
other errors. | ||
""" | ||
|
||
import os | ||
import sys | ||
import warnings | ||
from typing import Callable | ||
|
||
from . import exceptions, formatting | ||
|
||
|
||
def _get_verbose_exceptions_envvar() -> bool: | ||
"""Detect if the user enabled verbose exceptions in the environment variables.""" | ||
env_var_name = "GT4PY_VERBOSE_EXCEPTIONS" | ||
if env_var_name in os.environ: | ||
false_values = ["0", "false", "off"] | ||
true_values = ["1", "true", "on"] | ||
value = os.environ[env_var_name].lower() | ||
if value in false_values: | ||
return False | ||
elif value in true_values: | ||
return True | ||
else: | ||
values = ", ".join([*false_values, *true_values]) | ||
msg = f"the 'GT4PY_VERBOSE_EXCEPTIONS' environment variable must be one of {values} (case insensitive)" | ||
warnings.warn(msg) | ||
return False | ||
|
||
|
||
_verbose_exceptions: bool = _get_verbose_exceptions_envvar() | ||
|
||
|
||
def set_verbose_exceptions(enabled: bool = False) -> None: | ||
"""Programmatically set whether to use verbose printing for uncaught errors.""" | ||
global _verbose_exceptions | ||
_verbose_exceptions = enabled | ||
|
||
|
||
def _format_uncaught_error(err: exceptions.DSLError, verbose_exceptions: bool) -> list[str]: | ||
if verbose_exceptions: | ||
return formatting.format_compilation_error( | ||
type(err), | ||
err.message, | ||
err.location, | ||
err.__traceback__, | ||
err.__cause__, | ||
) | ||
else: | ||
return formatting.format_compilation_error(type(err), err.message, err.location) | ||
|
||
|
||
def compilation_error_hook(fallback: Callable, type_: type, value: BaseException, tb) -> None: | ||
""" | ||
Format `CompilationError`s in a neat way. | ||
All other Python exceptions are formatted by the `fallback` hook. When | ||
verbose exceptions are enabled, the stack trace and cause of the error is | ||
also printed. | ||
""" | ||
if isinstance(value, exceptions.DSLError): | ||
exc_strs = _format_uncaught_error(value, _verbose_exceptions) | ||
print("".join(exc_strs), file=sys.stderr) | ||
else: | ||
fallback(type_, value, tb) | ||
|
||
|
||
_fallback = sys.excepthook | ||
sys.excepthook = lambda ty, val, tb: compilation_error_hook(_fallback, ty, val, tb) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
# GT4Py - GridTools Framework | ||
# | ||
# Copyright (c) 2014-2023, ETH Zurich | ||
# All rights reserved. | ||
# | ||
# This file is part of the GT4Py project and the GridTools framework. | ||
# GT4Py is free software: you can redistribute it and/or modify it under | ||
# the terms of the GNU General Public License as published by the | ||
# Free Software Foundation, either version 3 of the License, or any later | ||
# version. See the LICENSE.txt file at the top-level directory of this | ||
# distribution for a copy of the license or check <https://www.gnu.org/licenses/>. | ||
# | ||
# SPDX-License-Identifier: GPL-3.0-or-later | ||
|
||
""" | ||
The list of exception classes used in the library. | ||
Exception classes that represent errors within an IR go here as a subclass of | ||
:class:`DSLError`. Exception classes that represent other errors, like | ||
the builtin ValueError, go here as well, although you should use Python's | ||
builtin error classes if you can. Exception classes that are specific to a | ||
certain submodule and have no use for the entire application may be better off | ||
in that submodule as opposed to being in this file. | ||
""" | ||
|
||
from __future__ import annotations | ||
|
||
import textwrap | ||
from typing import Any, Optional | ||
|
||
from gt4py.eve import SourceLocation | ||
|
||
from . import formatting | ||
|
||
|
||
class DSLError(Exception): | ||
location: Optional[SourceLocation] | ||
|
||
def __init__(self, location: Optional[SourceLocation], message: str) -> None: | ||
self.location = location | ||
super().__init__(message) | ||
|
||
@property | ||
def message(self) -> str: | ||
return self.args[0] | ||
|
||
def with_location(self, location: Optional[SourceLocation]) -> DSLError: | ||
self.location = location | ||
return self | ||
|
||
def __str__(self) -> str: | ||
if self.location: | ||
loc_str = formatting.format_location(self.location, show_caret=True) | ||
return f"{self.message}\n{textwrap.indent(loc_str, ' ')}" | ||
return self.message | ||
|
||
|
||
class UnsupportedPythonFeatureError(DSLError): | ||
feature: str | ||
|
||
def __init__(self, location: Optional[SourceLocation], feature: str) -> None: | ||
super().__init__(location, f"unsupported Python syntax: '{feature}'") | ||
self.feature = feature | ||
|
||
|
||
class UndefinedSymbolError(DSLError): | ||
sym_name: str | ||
|
||
def __init__(self, location: Optional[SourceLocation], name: str) -> None: | ||
super().__init__(location, f"name '{name}' is not defined") | ||
self.sym_name = name | ||
|
||
|
||
class MissingAttributeError(DSLError): | ||
attr_name: str | ||
|
||
def __init__(self, location: Optional[SourceLocation], attr_name: str) -> None: | ||
super().__init__(location, f"object does not have attribute '{attr_name}'") | ||
self.attr_name = attr_name | ||
|
||
|
||
class TypeError_(DSLError): | ||
def __init__(self, location: Optional[SourceLocation], message: str) -> None: | ||
super().__init__(location, message) | ||
|
||
|
||
class MissingParameterAnnotationError(TypeError_): | ||
param_name: str | ||
|
||
def __init__(self, location: Optional[SourceLocation], param_name: str) -> None: | ||
super().__init__(location, f"parameter '{param_name}' is missing type annotations") | ||
self.param_name = param_name | ||
|
||
|
||
class InvalidParameterAnnotationError(TypeError_): | ||
param_name: str | ||
annotated_type: Any | ||
|
||
def __init__(self, location: Optional[SourceLocation], param_name: str, type_: Any) -> None: | ||
super().__init__( | ||
location, f"parameter '{param_name}' has invalid type annotation '{type_}'" | ||
) | ||
self.param_name = param_name | ||
self.annotated_type = type_ |
Oops, something went wrong.