* [PATCH v2 0/8] qapi: static typing conversion, pt4
@ 2021-03-30 17:18 John Snow
2021-03-30 17:18 ` [PATCH v2 1/8] qapi/error: Repurpose QAPIError as a generic exception base class John Snow
` (7 more replies)
0 siblings, 8 replies; 22+ messages in thread
From: John Snow @ 2021-03-30 17:18 UTC (permalink / raw)
To: qemu-devel, Markus Armbruster
Cc: Peter Maydell, Eduardo Habkost, Michael Roth, Michael Roth,
Cleber Rosa, John Snow
Hi, this series adds static type hints to the QAPI module.
This is part four, and focuses on error.py.
Part 4: https://gitlab.com/jsnow/qemu/-/tree/python-qapi-cleanup-pt4
Requirements:
- Python 3.6+
- mypy >= 0.770
- pylint >= 2.6.0 (2.7.0+ when using Python 3.9+)
Every commit should pass with:
- isort -c qapi/
- flake8 qapi/
- pylint --rcfile=qapi/pylintrc qapi/
- mypy --config-file=qapi/mypy.ini qapi/
John Snow (8):
qapi/error: Repurpose QAPIError as a generic exception base class
qapi/error: Use Python3-style super()
qapi/error: Make QAPISourceError 'col' parameter optional
qapi/error: Change assertion
qapi/error.py: move QAPIParseError to parser.py
qapi/error.py: enable pylint checks
qapi/error: Add type hints
qapi/error.py: enable mypy checks
docs/sphinx/qapidoc.py | 3 ++-
scripts/qapi/error.py | 37 +++++++++++++++++++------------------
scripts/qapi/mypy.ini | 5 -----
scripts/qapi/parser.py | 14 +++++++++++++-
scripts/qapi/pylintrc | 3 +--
scripts/qapi/schema.py | 4 ++--
6 files changed, 37 insertions(+), 29 deletions(-)
--
2.30.2
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH v2 1/8] qapi/error: Repurpose QAPIError as a generic exception base class
2021-03-30 17:18 [PATCH v2 0/8] qapi: static typing conversion, pt4 John Snow
@ 2021-03-30 17:18 ` John Snow
2021-04-15 6:44 ` Markus Armbruster
2021-03-30 17:18 ` [PATCH v2 2/8] qapi/error: Use Python3-style super() John Snow
` (6 subsequent siblings)
7 siblings, 1 reply; 22+ messages in thread
From: John Snow @ 2021-03-30 17:18 UTC (permalink / raw)
To: qemu-devel, Markus Armbruster
Cc: Peter Maydell, Eduardo Habkost, Michael Roth, Michael Roth,
Cleber Rosa, John Snow
Rename QAPIError to QAPISourceError, and then create a new QAPIError
class that serves as the basis for all of our other custom exceptions.
Add docstrings to explain the intended function of each error class.
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx/qapidoc.py | 3 ++-
scripts/qapi/error.py | 11 +++++++++--
scripts/qapi/schema.py | 5 +++--
3 files changed, 14 insertions(+), 5 deletions(-)
diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
index b7b86b5dff..458b1f477e 100644
--- a/docs/sphinx/qapidoc.py
+++ b/docs/sphinx/qapidoc.py
@@ -34,7 +34,8 @@
from sphinx.util.nodes import nested_parse_with_titles
import sphinx
from qapi.gen import QAPISchemaVisitor
-from qapi.schema import QAPIError, QAPISemError, QAPISchema
+from qapi.error import QAPIError, QAPISemError
+from qapi.schema import QAPISchema
# Sphinx up to 1.6 uses AutodocReporter; 1.7 and later
diff --git a/scripts/qapi/error.py b/scripts/qapi/error.py
index ae60d9e2fe..126dda7c9b 100644
--- a/scripts/qapi/error.py
+++ b/scripts/qapi/error.py
@@ -13,6 +13,11 @@
class QAPIError(Exception):
+ """Base class for all exceptions from the QAPI package."""
+
+
+class QAPISourceError(QAPIError):
+ """Error class for all exceptions identifying a source location."""
def __init__(self, info, col, msg):
Exception.__init__(self)
self.info = info
@@ -27,7 +32,8 @@ def __str__(self):
return loc + ': ' + self.msg
-class QAPIParseError(QAPIError):
+class QAPIParseError(QAPISourceError):
+ """Error class for all QAPI schema parsing errors."""
def __init__(self, parser, msg):
col = 1
for ch in parser.src[parser.line_pos:parser.pos]:
@@ -38,6 +44,7 @@ def __init__(self, parser, msg):
super().__init__(parser.info, col, msg)
-class QAPISemError(QAPIError):
+class QAPISemError(QAPISourceError):
+ """Error class for semantic QAPI errors."""
def __init__(self, info, msg):
super().__init__(info, None, msg)
diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
index 01cdd753cd..1849c3e45f 100644
--- a/scripts/qapi/schema.py
+++ b/scripts/qapi/schema.py
@@ -20,7 +20,7 @@
from typing import Optional
from .common import POINTER_SUFFIX, c_name
-from .error import QAPIError, QAPISemError
+from .error import QAPISemError, QAPISourceError
from .expr import check_exprs
from .parser import QAPISchemaParser
@@ -878,7 +878,8 @@ def _def_entity(self, ent):
other_ent = self._entity_dict.get(ent.name)
if other_ent:
if other_ent.info:
- where = QAPIError(other_ent.info, None, "previous definition")
+ where = QAPISourceError(other_ent.info, None,
+ "previous definition")
raise QAPISemError(
ent.info,
"'%s' is already defined\n%s" % (ent.name, where))
--
2.30.2
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v2 2/8] qapi/error: Use Python3-style super()
2021-03-30 17:18 [PATCH v2 0/8] qapi: static typing conversion, pt4 John Snow
2021-03-30 17:18 ` [PATCH v2 1/8] qapi/error: Repurpose QAPIError as a generic exception base class John Snow
@ 2021-03-30 17:18 ` John Snow
2021-04-15 6:47 ` Markus Armbruster
2021-03-30 17:18 ` [PATCH v2 3/8] qapi/error: Make QAPISourceError 'col' parameter optional John Snow
` (5 subsequent siblings)
7 siblings, 1 reply; 22+ messages in thread
From: John Snow @ 2021-03-30 17:18 UTC (permalink / raw)
To: qemu-devel, Markus Armbruster
Cc: Peter Maydell, Eduardo Habkost, Michael Roth, Michael Roth,
Cleber Rosa, John Snow
Signed-off-by: John Snow <jsnow@redhat.com>
---
scripts/qapi/error.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/scripts/qapi/error.py b/scripts/qapi/error.py
index 126dda7c9b..38bd7c4dd6 100644
--- a/scripts/qapi/error.py
+++ b/scripts/qapi/error.py
@@ -19,7 +19,7 @@ class QAPIError(Exception):
class QAPISourceError(QAPIError):
"""Error class for all exceptions identifying a source location."""
def __init__(self, info, col, msg):
- Exception.__init__(self)
+ super().__init__()
self.info = info
self.col = col
self.msg = msg
--
2.30.2
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v2 3/8] qapi/error: Make QAPISourceError 'col' parameter optional
2021-03-30 17:18 [PATCH v2 0/8] qapi: static typing conversion, pt4 John Snow
2021-03-30 17:18 ` [PATCH v2 1/8] qapi/error: Repurpose QAPIError as a generic exception base class John Snow
2021-03-30 17:18 ` [PATCH v2 2/8] qapi/error: Use Python3-style super() John Snow
@ 2021-03-30 17:18 ` John Snow
2021-03-30 17:18 ` [PATCH v2 4/8] qapi/error: Change assertion John Snow
` (4 subsequent siblings)
7 siblings, 0 replies; 22+ messages in thread
From: John Snow @ 2021-03-30 17:18 UTC (permalink / raw)
To: qemu-devel, Markus Armbruster
Cc: Peter Maydell, Eduardo Habkost, Michael Roth, Michael Roth,
Cleber Rosa, John Snow
It's already treated as optional, with some callers and subclasses
passing 'None'. Make it officially optional, which requires moving the
position of the argument to come after all required parameters.
QAPISemError becomes functionally identical to QAPISourceError. Keep the
name to preserve its semantic meaning and avoid code churn, but remove
the now-useless __init__ wrapper.
Signed-off-by: John Snow <jsnow@redhat.com>
---
scripts/qapi/error.py | 8 +++-----
scripts/qapi/schema.py | 3 +--
2 files changed, 4 insertions(+), 7 deletions(-)
diff --git a/scripts/qapi/error.py b/scripts/qapi/error.py
index 38bd7c4dd6..d179a3bd0c 100644
--- a/scripts/qapi/error.py
+++ b/scripts/qapi/error.py
@@ -18,11 +18,11 @@ class QAPIError(Exception):
class QAPISourceError(QAPIError):
"""Error class for all exceptions identifying a source location."""
- def __init__(self, info, col, msg):
+ def __init__(self, info, msg, col=None):
super().__init__()
self.info = info
- self.col = col
self.msg = msg
+ self.col = col
def __str__(self):
loc = str(self.info)
@@ -41,10 +41,8 @@ def __init__(self, parser, msg):
col = (col + 7) % 8 + 1
else:
col += 1
- super().__init__(parser.info, col, msg)
+ super().__init__(parser.info, msg, col)
class QAPISemError(QAPISourceError):
"""Error class for semantic QAPI errors."""
- def __init__(self, info, msg):
- super().__init__(info, None, msg)
diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
index 1849c3e45f..dcf8a86139 100644
--- a/scripts/qapi/schema.py
+++ b/scripts/qapi/schema.py
@@ -878,8 +878,7 @@ def _def_entity(self, ent):
other_ent = self._entity_dict.get(ent.name)
if other_ent:
if other_ent.info:
- where = QAPISourceError(other_ent.info, None,
- "previous definition")
+ where = QAPISourceError(other_ent.info, "previous definition")
raise QAPISemError(
ent.info,
"'%s' is already defined\n%s" % (ent.name, where))
--
2.30.2
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v2 4/8] qapi/error: Change assertion
2021-03-30 17:18 [PATCH v2 0/8] qapi: static typing conversion, pt4 John Snow
` (2 preceding siblings ...)
2021-03-30 17:18 ` [PATCH v2 3/8] qapi/error: Make QAPISourceError 'col' parameter optional John Snow
@ 2021-03-30 17:18 ` John Snow
2021-04-08 15:31 ` John Snow
2021-03-30 17:18 ` [PATCH v2 5/8] qapi/error.py: move QAPIParseError to parser.py John Snow
` (3 subsequent siblings)
7 siblings, 1 reply; 22+ messages in thread
From: John Snow @ 2021-03-30 17:18 UTC (permalink / raw)
To: qemu-devel, Markus Armbruster
Cc: Peter Maydell, Eduardo Habkost, Michael Roth, Michael Roth,
Cleber Rosa, John Snow
Eventually, we'll be able to prove that 'info.line' must be an int and
is never None at static analysis time, and this assert can go
away. Until then, it's a type error to assume that self.info is not
None.
Signed-off-by: John Snow <jsnow@redhat.com>
---
scripts/qapi/error.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/scripts/qapi/error.py b/scripts/qapi/error.py
index d179a3bd0c..d0bc7af6e7 100644
--- a/scripts/qapi/error.py
+++ b/scripts/qapi/error.py
@@ -25,6 +25,7 @@ def __init__(self, info, msg, col=None):
self.col = col
def __str__(self):
+ assert self.info is not None
loc = str(self.info)
if self.col is not None:
assert self.info.line is not None
--
2.30.2
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v2 5/8] qapi/error.py: move QAPIParseError to parser.py
2021-03-30 17:18 [PATCH v2 0/8] qapi: static typing conversion, pt4 John Snow
` (3 preceding siblings ...)
2021-03-30 17:18 ` [PATCH v2 4/8] qapi/error: Change assertion John Snow
@ 2021-03-30 17:18 ` John Snow
2021-03-30 17:18 ` [PATCH v2 6/8] qapi/error.py: enable pylint checks John Snow
` (2 subsequent siblings)
7 siblings, 0 replies; 22+ messages in thread
From: John Snow @ 2021-03-30 17:18 UTC (permalink / raw)
To: qemu-devel, Markus Armbruster
Cc: Peter Maydell, Eduardo Habkost, Michael Roth, Michael Roth,
Cleber Rosa, John Snow
Keeping it in error.py will create some cyclic import problems when we
add types to the QAPISchemaParser. Callers don't need to know the
details of QAPIParseError unless they are parsing or dealing directly
with the parser, so this won't create any harsh new requirements for
callers in the general case.
Signed-off-by: John Snow <jsnow@redhat.com>
---
scripts/qapi/error.py | 12 ------------
scripts/qapi/parser.py | 14 +++++++++++++-
2 files changed, 13 insertions(+), 13 deletions(-)
diff --git a/scripts/qapi/error.py b/scripts/qapi/error.py
index d0bc7af6e7..2183b8c6b7 100644
--- a/scripts/qapi/error.py
+++ b/scripts/qapi/error.py
@@ -33,17 +33,5 @@ def __str__(self):
return loc + ': ' + self.msg
-class QAPIParseError(QAPISourceError):
- """Error class for all QAPI schema parsing errors."""
- def __init__(self, parser, msg):
- col = 1
- for ch in parser.src[parser.line_pos:parser.pos]:
- if ch == '\t':
- col = (col + 7) % 8 + 1
- else:
- col += 1
- super().__init__(parser.info, msg, col)
-
-
class QAPISemError(QAPISourceError):
"""Error class for semantic QAPI errors."""
diff --git a/scripts/qapi/parser.py b/scripts/qapi/parser.py
index d5bf91f2b0..140f582ad9 100644
--- a/scripts/qapi/parser.py
+++ b/scripts/qapi/parser.py
@@ -18,10 +18,22 @@
import os
import re
-from .error import QAPIParseError, QAPISemError
+from .error import QAPISemError, QAPISourceError
from .source import QAPISourceInfo
+class QAPIParseError(QAPISourceError):
+ """Error class for all QAPI schema parsing errors."""
+ def __init__(self, parser, msg):
+ col = 1
+ for ch in parser.src[parser.line_pos:parser.pos]:
+ if ch == '\t':
+ col = (col + 7) % 8 + 1
+ else:
+ col += 1
+ super().__init__(parser.info, msg, col)
+
+
class QAPISchemaParser:
def __init__(self, fname, previously_included=None, incl_info=None):
--
2.30.2
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v2 6/8] qapi/error.py: enable pylint checks
2021-03-30 17:18 [PATCH v2 0/8] qapi: static typing conversion, pt4 John Snow
` (4 preceding siblings ...)
2021-03-30 17:18 ` [PATCH v2 5/8] qapi/error.py: move QAPIParseError to parser.py John Snow
@ 2021-03-30 17:18 ` John Snow
2021-03-30 17:18 ` [PATCH v2 7/8] qapi/error: Add type hints John Snow
2021-03-30 17:18 ` [PATCH v2 8/8] qapi/error.py: enable mypy checks John Snow
7 siblings, 0 replies; 22+ messages in thread
From: John Snow @ 2021-03-30 17:18 UTC (permalink / raw)
To: qemu-devel, Markus Armbruster
Cc: Peter Maydell, Eduardo Habkost, Michael Roth, Michael Roth,
Cleber Rosa, John Snow
Signed-off-by: John Snow <jsnow@redhat.com>
---
scripts/qapi/pylintrc | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/scripts/qapi/pylintrc b/scripts/qapi/pylintrc
index fb0386d529..88efbf71cb 100644
--- a/scripts/qapi/pylintrc
+++ b/scripts/qapi/pylintrc
@@ -2,8 +2,7 @@
# Add files or directories matching the regex patterns to the ignore list.
# The regex matches against base names, not paths.
-ignore-patterns=error.py,
- parser.py,
+ignore-patterns=parser.py,
schema.py,
--
2.30.2
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v2 7/8] qapi/error: Add type hints
2021-03-30 17:18 [PATCH v2 0/8] qapi: static typing conversion, pt4 John Snow
` (5 preceding siblings ...)
2021-03-30 17:18 ` [PATCH v2 6/8] qapi/error.py: enable pylint checks John Snow
@ 2021-03-30 17:18 ` John Snow
2021-04-15 7:15 ` Markus Armbruster
2021-03-30 17:18 ` [PATCH v2 8/8] qapi/error.py: enable mypy checks John Snow
7 siblings, 1 reply; 22+ messages in thread
From: John Snow @ 2021-03-30 17:18 UTC (permalink / raw)
To: qemu-devel, Markus Armbruster
Cc: Peter Maydell, Eduardo Habkost, Michael Roth, Michael Roth,
Cleber Rosa, John Snow
No functional change.
Signed-off-by: John Snow <jsnow@redhat.com>
---
scripts/qapi/error.py | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/scripts/qapi/error.py b/scripts/qapi/error.py
index 2183b8c6b7..6ba54821c9 100644
--- a/scripts/qapi/error.py
+++ b/scripts/qapi/error.py
@@ -11,6 +11,10 @@
# This work is licensed under the terms of the GNU GPL, version 2.
# See the COPYING file in the top-level directory.
+from typing import Optional
+
+from .source import QAPISourceInfo
+
class QAPIError(Exception):
"""Base class for all exceptions from the QAPI package."""
@@ -18,13 +22,16 @@ class QAPIError(Exception):
class QAPISourceError(QAPIError):
"""Error class for all exceptions identifying a source location."""
- def __init__(self, info, msg, col=None):
+ def __init__(self,
+ info: Optional[QAPISourceInfo],
+ msg: str,
+ col: Optional[int] = None):
super().__init__()
self.info = info
self.msg = msg
self.col = col
- def __str__(self):
+ def __str__(self) -> str:
assert self.info is not None
loc = str(self.info)
if self.col is not None:
--
2.30.2
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v2 8/8] qapi/error.py: enable mypy checks
2021-03-30 17:18 [PATCH v2 0/8] qapi: static typing conversion, pt4 John Snow
` (6 preceding siblings ...)
2021-03-30 17:18 ` [PATCH v2 7/8] qapi/error: Add type hints John Snow
@ 2021-03-30 17:18 ` John Snow
7 siblings, 0 replies; 22+ messages in thread
From: John Snow @ 2021-03-30 17:18 UTC (permalink / raw)
To: qemu-devel, Markus Armbruster
Cc: Peter Maydell, Eduardo Habkost, Michael Roth, Michael Roth,
Cleber Rosa, John Snow
Signed-off-by: John Snow <jsnow@redhat.com>
---
(This can be squashed with the previous commit when staged.)
Signed-off-by: John Snow <jsnow@redhat.com>
---
scripts/qapi/mypy.ini | 5 -----
1 file changed, 5 deletions(-)
diff --git a/scripts/qapi/mypy.ini b/scripts/qapi/mypy.ini
index 7797c83432..54ca4483d6 100644
--- a/scripts/qapi/mypy.ini
+++ b/scripts/qapi/mypy.ini
@@ -3,11 +3,6 @@ strict = True
disallow_untyped_calls = False
python_version = 3.6
-[mypy-qapi.error]
-disallow_untyped_defs = False
-disallow_incomplete_defs = False
-check_untyped_defs = False
-
[mypy-qapi.parser]
disallow_untyped_defs = False
disallow_incomplete_defs = False
--
2.30.2
^ permalink raw reply related [flat|nested] 22+ messages in thread
* Re: [PATCH v2 4/8] qapi/error: Change assertion
2021-03-30 17:18 ` [PATCH v2 4/8] qapi/error: Change assertion John Snow
@ 2021-04-08 15:31 ` John Snow
2021-04-15 7:00 ` Markus Armbruster
0 siblings, 1 reply; 22+ messages in thread
From: John Snow @ 2021-04-08 15:31 UTC (permalink / raw)
To: qemu-devel, Markus Armbruster
Cc: Peter Maydell, Eduardo Habkost, Michael Roth, Michael Roth, Cleber Rosa
On 3/30/21 1:18 PM, John Snow wrote:
Realizing now that this commit topic is wrong :)
A prior version modified the assertion, I decided it was less churn to
simply add one.
I think ideally we'd have no assertions here and we'd rely on the type
hints, but I don't think I can prove that this is safe until after part
6, so I did this for now instead.
> Eventually, we'll be able to prove that 'info.line' must be an int and
> is never None at static analysis time, and this assert can go
> away. Until then, it's a type error to assume that self.info is not
> None.
>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
> scripts/qapi/error.py | 1 +
> 1 file changed, 1 insertion(+)
>
> diff --git a/scripts/qapi/error.py b/scripts/qapi/error.py
> index d179a3bd0c..d0bc7af6e7 100644
> --- a/scripts/qapi/error.py
> +++ b/scripts/qapi/error.py
> @@ -25,6 +25,7 @@ def __init__(self, info, msg, col=None):
> self.col = col
>
> def __str__(self):
> + assert self.info is not None
> loc = str(self.info)
> if self.col is not None:
> assert self.info.line is not None
>
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v2 1/8] qapi/error: Repurpose QAPIError as a generic exception base class
2021-03-30 17:18 ` [PATCH v2 1/8] qapi/error: Repurpose QAPIError as a generic exception base class John Snow
@ 2021-04-15 6:44 ` Markus Armbruster
2021-04-15 15:28 ` John Snow
0 siblings, 1 reply; 22+ messages in thread
From: Markus Armbruster @ 2021-04-15 6:44 UTC (permalink / raw)
To: John Snow
Cc: Peter Maydell, Eduardo Habkost, Michael Roth, Michael Roth,
qemu-devel, Cleber Rosa
John Snow <jsnow@redhat.com> writes:
> Rename QAPIError to QAPISourceError, and then create a new QAPIError
> class that serves as the basis for all of our other custom exceptions.
Isn't the existing QAPIError such a base class already? Peeking
ahead... aha, your new base class is abstract. Can you explain why we
that's useful?
> Add docstrings to explain the intended function of each error class.
>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
> docs/sphinx/qapidoc.py | 3 ++-
> scripts/qapi/error.py | 11 +++++++++--
> scripts/qapi/schema.py | 5 +++--
> 3 files changed, 14 insertions(+), 5 deletions(-)
>
> diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
> index b7b86b5dff..458b1f477e 100644
> --- a/docs/sphinx/qapidoc.py
> +++ b/docs/sphinx/qapidoc.py
> @@ -34,7 +34,8 @@
> from sphinx.util.nodes import nested_parse_with_titles
> import sphinx
> from qapi.gen import QAPISchemaVisitor
> -from qapi.schema import QAPIError, QAPISemError, QAPISchema
> +from qapi.error import QAPIError, QAPISemError
> +from qapi.schema import QAPISchema
>
>
> # Sphinx up to 1.6 uses AutodocReporter; 1.7 and later
> diff --git a/scripts/qapi/error.py b/scripts/qapi/error.py
> index ae60d9e2fe..126dda7c9b 100644
> --- a/scripts/qapi/error.py
> +++ b/scripts/qapi/error.py
> @@ -13,6 +13,11 @@
>
>
> class QAPIError(Exception):
> + """Base class for all exceptions from the QAPI package."""
> +
> +
> +class QAPISourceError(QAPIError):
> + """Error class for all exceptions identifying a source location."""
> def __init__(self, info, col, msg):
> Exception.__init__(self)
> self.info = info
> @@ -27,7 +32,8 @@ def __str__(self):
> return loc + ': ' + self.msg
>
>
> -class QAPIParseError(QAPIError):
> +class QAPIParseError(QAPISourceError):
> + """Error class for all QAPI schema parsing errors."""
> def __init__(self, parser, msg):
> col = 1
> for ch in parser.src[parser.line_pos:parser.pos]:
> @@ -38,6 +44,7 @@ def __init__(self, parser, msg):
> super().__init__(parser.info, col, msg)
>
>
> -class QAPISemError(QAPIError):
> +class QAPISemError(QAPISourceError):
> + """Error class for semantic QAPI errors."""
> def __init__(self, info, msg):
> super().__init__(info, None, msg)
> diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
> index 01cdd753cd..1849c3e45f 100644
> --- a/scripts/qapi/schema.py
> +++ b/scripts/qapi/schema.py
> @@ -20,7 +20,7 @@
> from typing import Optional
>
> from .common import POINTER_SUFFIX, c_name
> -from .error import QAPIError, QAPISemError
> +from .error import QAPISemError, QAPISourceError
> from .expr import check_exprs
> from .parser import QAPISchemaParser
>
> @@ -878,7 +878,8 @@ def _def_entity(self, ent):
> other_ent = self._entity_dict.get(ent.name)
> if other_ent:
> if other_ent.info:
> - where = QAPIError(other_ent.info, None, "previous definition")
> + where = QAPISourceError(other_ent.info, None,
> + "previous definition")
> raise QAPISemError(
> ent.info,
> "'%s' is already defined\n%s" % (ent.name, where))
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v2 2/8] qapi/error: Use Python3-style super()
2021-03-30 17:18 ` [PATCH v2 2/8] qapi/error: Use Python3-style super() John Snow
@ 2021-04-15 6:47 ` Markus Armbruster
0 siblings, 0 replies; 22+ messages in thread
From: Markus Armbruster @ 2021-04-15 6:47 UTC (permalink / raw)
To: John Snow
Cc: Peter Maydell, Eduardo Habkost, Michael Roth, Michael Roth,
qemu-devel, Cleber Rosa
John Snow <jsnow@redhat.com> writes:
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
> scripts/qapi/error.py | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/scripts/qapi/error.py b/scripts/qapi/error.py
> index 126dda7c9b..38bd7c4dd6 100644
> --- a/scripts/qapi/error.py
> +++ b/scripts/qapi/error.py
> @@ -19,7 +19,7 @@ class QAPIError(Exception):
> class QAPISourceError(QAPIError):
> """Error class for all exceptions identifying a source location."""
> def __init__(self, info, col, msg):
> - Exception.__init__(self)
> + super().__init__()
> self.info = info
> self.col = col
> self.msg = msg
Missed in commit 2cae67bcb5 "qapi: Use super() now we have Python 3".
Reviewed-by: Markus Armbruster <armbru@redhat.com>
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v2 4/8] qapi/error: Change assertion
2021-04-08 15:31 ` John Snow
@ 2021-04-15 7:00 ` Markus Armbruster
2021-04-15 15:44 ` John Snow
0 siblings, 1 reply; 22+ messages in thread
From: Markus Armbruster @ 2021-04-15 7:00 UTC (permalink / raw)
To: John Snow
Cc: Peter Maydell, Eduardo Habkost, Michael Roth, Michael Roth,
qemu-devel, Cleber Rosa
John Snow <jsnow@redhat.com> writes:
> On 3/30/21 1:18 PM, John Snow wrote:
>
> Realizing now that this commit topic is wrong :)
>
> A prior version modified the assertion, I decided it was less churn to
> simply add one.
>
> I think ideally we'd have no assertions here and we'd rely on the type
> hints, but I don't think I can prove that this is safe until after part
> 6, so I did this for now instead.
>
>> Eventually, we'll be able to prove that 'info.line' must be an int and
>> is never None at static analysis time, and this assert can go
>> away. Until then, it's a type error to assume that self.info is not
>> None.
>>
>> Signed-off-by: John Snow <jsnow@redhat.com>
>> ---
>> scripts/qapi/error.py | 1 +
>> 1 file changed, 1 insertion(+)
>>
>> diff --git a/scripts/qapi/error.py b/scripts/qapi/error.py
>> index d179a3bd0c..d0bc7af6e7 100644
>> --- a/scripts/qapi/error.py
>> +++ b/scripts/qapi/error.py
>> @@ -25,6 +25,7 @@ def __init__(self, info, msg, col=None):
>> self.col = col
>>
>> def __str__(self):
>> + assert self.info is not None
>> loc = str(self.info)
>> if self.col is not None:
>> assert self.info.line is not None
>>
Please show us the revised commit message. I'm asking because the
patch's purpose isn't quite clear to me. Peeking ahead at PATCH 7, I
see that info becomes Optional[QAPISourceInfo]. This means something
passes None for info (else you wouldn't make it optional). None info
Works (in the sense of "doesn't crash") as long as col is also None.
After the patch, it doesn't. I'm not saying that's bad, only that I
don't quite understand what you're trying to accomplish here.
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v2 7/8] qapi/error: Add type hints
2021-03-30 17:18 ` [PATCH v2 7/8] qapi/error: Add type hints John Snow
@ 2021-04-15 7:15 ` Markus Armbruster
2021-04-15 15:52 ` John Snow
0 siblings, 1 reply; 22+ messages in thread
From: Markus Armbruster @ 2021-04-15 7:15 UTC (permalink / raw)
To: John Snow
Cc: Peter Maydell, Eduardo Habkost, Michael Roth, Michael Roth,
qemu-devel, Cleber Rosa
John Snow <jsnow@redhat.com> writes:
> No functional change.
>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
> scripts/qapi/error.py | 11 +++++++++--
> 1 file changed, 9 insertions(+), 2 deletions(-)
>
> diff --git a/scripts/qapi/error.py b/scripts/qapi/error.py
> index 2183b8c6b7..6ba54821c9 100644
> --- a/scripts/qapi/error.py
> +++ b/scripts/qapi/error.py
> @@ -11,6 +11,10 @@
> # This work is licensed under the terms of the GNU GPL, version 2.
> # See the COPYING file in the top-level directory.
>
> +from typing import Optional
> +
> +from .source import QAPISourceInfo
> +
>
> class QAPIError(Exception):
> """Base class for all exceptions from the QAPI package."""
> @@ -18,13 +22,16 @@ class QAPIError(Exception):
>
> class QAPISourceError(QAPIError):
> """Error class for all exceptions identifying a source location."""
> - def __init__(self, info, msg, col=None):
> + def __init__(self,
> + info: Optional[QAPISourceInfo],
The Optional is a bit surprising. Mind pointing to the / a reason in
the commit message?
> + msg: str,
> + col: Optional[int] = None):
> super().__init__()
> self.info = info
> self.msg = msg
> self.col = col
>
> - def __str__(self):
> + def __str__(self) -> str:
> assert self.info is not None
> loc = str(self.info)
> if self.col is not None:
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v2 1/8] qapi/error: Repurpose QAPIError as a generic exception base class
2021-04-15 6:44 ` Markus Armbruster
@ 2021-04-15 15:28 ` John Snow
2021-04-16 6:04 ` Markus Armbruster
0 siblings, 1 reply; 22+ messages in thread
From: John Snow @ 2021-04-15 15:28 UTC (permalink / raw)
To: Markus Armbruster
Cc: Peter Maydell, Eduardo Habkost, Michael Roth, Michael Roth,
qemu-devel, Cleber Rosa
On 4/15/21 2:44 AM, Markus Armbruster wrote:
> John Snow <jsnow@redhat.com> writes:
>
>> Rename QAPIError to QAPISourceError, and then create a new QAPIError
>> class that serves as the basis for all of our other custom exceptions.
>
> Isn't the existing QAPIError such a base class already? Peeking
> ahead... aha, your new base class is abstract. Can you explain why we
> that's useful?
>
Sure. The existing QAPIError class assumes that it's an error that has a
source location, but not all errors will. The idea is that an abstract
error class can be used as the ancestor for all other errors in a
type-safe way, such that:
try:
qapi.something_or_other()
except QAPIError as err:
err.some_sort_of_method()
(1) This is a type-safe construct, and
(2) This is sufficient to catch all errors that originate from within
the library, regardless of their exact type.
Primarily, it's a ploy to get the SourceInfo stuff out of the
common/root exception for type safety reasons.
(Later in the series, you'll see there's a few places where we try to
fake SourceInfo stuff in order to use QAPIError, by shuffling this
around, we allow ourselves to raise exceptions that don't need this
hackery.)
>> Add docstrings to explain the intended function of each error class.
>>
>> Signed-off-by: John Snow <jsnow@redhat.com>
>> ---
>> docs/sphinx/qapidoc.py | 3 ++-
>> scripts/qapi/error.py | 11 +++++++++--
>> scripts/qapi/schema.py | 5 +++--
>> 3 files changed, 14 insertions(+), 5 deletions(-)
>>
>> diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
>> index b7b86b5dff..458b1f477e 100644
>> --- a/docs/sphinx/qapidoc.py
>> +++ b/docs/sphinx/qapidoc.py
>> @@ -34,7 +34,8 @@
>> from sphinx.util.nodes import nested_parse_with_titles
>> import sphinx
>> from qapi.gen import QAPISchemaVisitor
>> -from qapi.schema import QAPIError, QAPISemError, QAPISchema
>> +from qapi.error import QAPIError, QAPISemError
>> +from qapi.schema import QAPISchema
>>
>>
>> # Sphinx up to 1.6 uses AutodocReporter; 1.7 and later
>> diff --git a/scripts/qapi/error.py b/scripts/qapi/error.py
>> index ae60d9e2fe..126dda7c9b 100644
>> --- a/scripts/qapi/error.py
>> +++ b/scripts/qapi/error.py
>> @@ -13,6 +13,11 @@
>>
>>
>> class QAPIError(Exception):
>> + """Base class for all exceptions from the QAPI package."""
>> +
>> +
>> +class QAPISourceError(QAPIError):
>> + """Error class for all exceptions identifying a source location."""
>> def __init__(self, info, col, msg):
>> Exception.__init__(self)
>> self.info = info
>> @@ -27,7 +32,8 @@ def __str__(self):
>> return loc + ': ' + self.msg
>>
>>
>> -class QAPIParseError(QAPIError):
>> +class QAPIParseError(QAPISourceError):
>> + """Error class for all QAPI schema parsing errors."""
>> def __init__(self, parser, msg):
>> col = 1
>> for ch in parser.src[parser.line_pos:parser.pos]:
>> @@ -38,6 +44,7 @@ def __init__(self, parser, msg):
>> super().__init__(parser.info, col, msg)
>>
>>
>> -class QAPISemError(QAPIError):
>> +class QAPISemError(QAPISourceError):
>> + """Error class for semantic QAPI errors."""
>> def __init__(self, info, msg):
>> super().__init__(info, None, msg)
>> diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
>> index 01cdd753cd..1849c3e45f 100644
>> --- a/scripts/qapi/schema.py
>> +++ b/scripts/qapi/schema.py
>> @@ -20,7 +20,7 @@
>> from typing import Optional
>>
>> from .common import POINTER_SUFFIX, c_name
>> -from .error import QAPIError, QAPISemError
>> +from .error import QAPISemError, QAPISourceError
>> from .expr import check_exprs
>> from .parser import QAPISchemaParser
>>
>> @@ -878,7 +878,8 @@ def _def_entity(self, ent):
>> other_ent = self._entity_dict.get(ent.name)
>> if other_ent:
>> if other_ent.info:
>> - where = QAPIError(other_ent.info, None, "previous definition")
>> + where = QAPISourceError(other_ent.info, None,
>> + "previous definition")
>> raise QAPISemError(
>> ent.info,
>> "'%s' is already defined\n%s" % (ent.name, where))
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v2 4/8] qapi/error: Change assertion
2021-04-15 7:00 ` Markus Armbruster
@ 2021-04-15 15:44 ` John Snow
2021-04-16 6:17 ` Markus Armbruster
0 siblings, 1 reply; 22+ messages in thread
From: John Snow @ 2021-04-15 15:44 UTC (permalink / raw)
To: Markus Armbruster
Cc: Peter Maydell, Eduardo Habkost, Michael Roth, Michael Roth,
qemu-devel, Cleber Rosa
On 4/15/21 3:00 AM, Markus Armbruster wrote:
> John Snow <jsnow@redhat.com> writes:
>
>> On 3/30/21 1:18 PM, John Snow wrote:
>>
>> Realizing now that this commit topic is wrong :)
>>
>> A prior version modified the assertion, I decided it was less churn to
>> simply add one.
>>
>> I think ideally we'd have no assertions here and we'd rely on the type
>> hints, but I don't think I can prove that this is safe until after part
>> 6, so I did this for now instead.
>>
>>> Eventually, we'll be able to prove that 'info.line' must be an int and
>>> is never None at static analysis time, and this assert can go
>>> away. Until then, it's a type error to assume that self.info is not
>>> None.
>>>
>>> Signed-off-by: John Snow <jsnow@redhat.com>
>>> ---
>>> scripts/qapi/error.py | 1 +
>>> 1 file changed, 1 insertion(+)
>>>
>>> diff --git a/scripts/qapi/error.py b/scripts/qapi/error.py
>>> index d179a3bd0c..d0bc7af6e7 100644
>>> --- a/scripts/qapi/error.py
>>> +++ b/scripts/qapi/error.py
>>> @@ -25,6 +25,7 @@ def __init__(self, info, msg, col=None):
>>> self.col = col
>>>
>>> def __str__(self):
>>> + assert self.info is not None
>>> loc = str(self.info)
>>> if self.col is not None:
>>> assert self.info.line is not None
>>>
>
> Please show us the revised commit message. I'm asking because the
> patch's purpose isn't quite clear to me. Peeking ahead at PATCH 7, I
> see that info becomes Optional[QAPISourceInfo]. This means something
> passes None for info (else you wouldn't make it optional). None info
> Works (in the sense of "doesn't crash") as long as col is also None.
> After the patch, it doesn't. I'm not saying that's bad, only that I
> don't quite understand what you're trying to accomplish here.
>
Sure.
If you recall, I tried to enforce that QAPISourceInfo was *never* None
by creating a special value for QSI that represented "No Source Info".
Ultimately, that idea didn't go through and we solidified that the
'info' parameter that gets passed around can sometimes be None.
Hence, this property is Optional[QAPISourceInfo].
Since I know you wanted to crash messily in the case that we allowed a
None-info to leak into a context like this, I added the new assertion to
make sure this remains the case.
(since str(None) evaluates to "None", it seemed like there was already
the implicit assumption that info would never be None. Plus, if 'col' is
set, mypy notices that we are performing an unsafe check on
self.info.line, which had to be remedied.)
Later in the series, after schema.py is typed, it may be possible to
remove these assertions as we may be able to rely on the strict typing
to prove that this situation can never occur. However, since schema.py
is not yet typed, we can't yet.
Alright. So given that, I think what you'd like to see for a commit
message is:
qapi/error: assert QAPISourceInfo is not None
We implicitly assume that self.info will never be None, as the error
reporting function will not produce meaningful output in this case, and
will crash if self.col was set. Assert that self.info is not None in
order to formalize this assumption.
We can not yet change the type of the initializer to prove that this is
provably true at static analysis time until the rest of this library is
fully typed.
--js
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v2 7/8] qapi/error: Add type hints
2021-04-15 7:15 ` Markus Armbruster
@ 2021-04-15 15:52 ` John Snow
0 siblings, 0 replies; 22+ messages in thread
From: John Snow @ 2021-04-15 15:52 UTC (permalink / raw)
To: Markus Armbruster
Cc: Peter Maydell, Eduardo Habkost, Michael Roth, Michael Roth,
qemu-devel, Cleber Rosa
On 4/15/21 3:15 AM, Markus Armbruster wrote:
> John Snow <jsnow@redhat.com> writes:
>
>> No functional change.
>>
>> Signed-off-by: John Snow <jsnow@redhat.com>
>> ---
>> scripts/qapi/error.py | 11 +++++++++--
>> 1 file changed, 9 insertions(+), 2 deletions(-)
>>
>> diff --git a/scripts/qapi/error.py b/scripts/qapi/error.py
>> index 2183b8c6b7..6ba54821c9 100644
>> --- a/scripts/qapi/error.py
>> +++ b/scripts/qapi/error.py
>> @@ -11,6 +11,10 @@
>> # This work is licensed under the terms of the GNU GPL, version 2.
>> # See the COPYING file in the top-level directory.
>>
>> +from typing import Optional
>> +
>> +from .source import QAPISourceInfo
>> +
>>
>> class QAPIError(Exception):
>> """Base class for all exceptions from the QAPI package."""
>> @@ -18,13 +22,16 @@ class QAPIError(Exception):
>>
>> class QAPISourceError(QAPIError):
>> """Error class for all exceptions identifying a source location."""
>> - def __init__(self, info, msg, col=None):
>> + def __init__(self,
>> + info: Optional[QAPISourceInfo],
>
> The Optional is a bit surprising. Mind pointing to the / a reason in
> the commit message?
>
We don't have a special object representing "No Source Info", so
schema.py passes around None objects to mean the same.
The problem, ultimately, is that a QAPISchemaEntity may or may not have
a source info; and various error reporting paths that in practice will
not involve such a type in its reporting cannot distinguish that using
the type system, because from the root-down, info is codified as
Optional[QAPISourceInfo].
There might be a way to introduce a constraint in schema.py later on,
but for now (prior to part 6, especially), we cannot.
The assertion should help remind a reader that we don't expect it to be
true.
--js
>> + msg: str,
>> + col: Optional[int] = None):
>> super().__init__()
>> self.info = info
>> self.msg = msg
>> self.col = col
>>
>> - def __str__(self):
>> + def __str__(self) -> str:
>> assert self.info is not None
>> loc = str(self.info)
>> if self.col is not None:
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v2 1/8] qapi/error: Repurpose QAPIError as a generic exception base class
2021-04-15 15:28 ` John Snow
@ 2021-04-16 6:04 ` Markus Armbruster
2021-04-16 17:59 ` John Snow
0 siblings, 1 reply; 22+ messages in thread
From: Markus Armbruster @ 2021-04-16 6:04 UTC (permalink / raw)
To: John Snow
Cc: Peter Maydell, Eduardo Habkost, Michael Roth, Michael Roth,
qemu-devel, Cleber Rosa
John Snow <jsnow@redhat.com> writes:
> On 4/15/21 2:44 AM, Markus Armbruster wrote:
>> John Snow <jsnow@redhat.com> writes:
>>
>>> Rename QAPIError to QAPISourceError, and then create a new QAPIError
>>> class that serves as the basis for all of our other custom exceptions.
>>
>> Isn't the existing QAPIError such a base class already? Peeking
>> ahead... aha, your new base class is abstract. Can you explain why we
>> that's useful?
>>
>
> Sure. The existing QAPIError class assumes that it's an error that has a
> source location, but not all errors will. The idea is that an abstract
> error class can be used as the ancestor for all other errors in a
> type-safe way, such that:
>
> try:
> qapi.something_or_other()
> except QAPIError as err:
> err.some_sort_of_method()
>
> (1) This is a type-safe construct, and
> (2) This is sufficient to catch all errors that originate from within
> the library, regardless of their exact type.
>
> Primarily, it's a ploy to get the SourceInfo stuff out of the
> common/root exception for type safety reasons.
>
> (Later in the series, you'll see there's a few places where we try to
> fake SourceInfo stuff in order to use QAPIError, by shuffling this
> around, we allow ourselves to raise exceptions that don't need this
> hackery.)
I think you're conflating a real issue with a non-issue.
The real issue: you want to get rid of fake QAPISourceInfo. This isn't
terribly important, but small cleanups count, too. I can't see the "few
places where we try to fake" in this series, though. Is it in a later
part, or am I just blind?
The non-issue: wanting a common base class. Yes, we want one, but we
already got one: QAPIError.
I think the commit message should explain the real issue more clearly,
without confusing readers with the non-issue.
Makes sense?
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v2 4/8] qapi/error: Change assertion
2021-04-15 15:44 ` John Snow
@ 2021-04-16 6:17 ` Markus Armbruster
2021-04-16 18:24 ` John Snow
0 siblings, 1 reply; 22+ messages in thread
From: Markus Armbruster @ 2021-04-16 6:17 UTC (permalink / raw)
To: John Snow
Cc: Peter Maydell, Eduardo Habkost, Michael Roth, Michael Roth,
qemu-devel, Cleber Rosa
John Snow <jsnow@redhat.com> writes:
> On 4/15/21 3:00 AM, Markus Armbruster wrote:
>> John Snow <jsnow@redhat.com> writes:
>>
>>> On 3/30/21 1:18 PM, John Snow wrote:
>>>
>>> Realizing now that this commit topic is wrong :)
>>>
>>> A prior version modified the assertion, I decided it was less churn to
>>> simply add one.
>>>
>>> I think ideally we'd have no assertions here and we'd rely on the type
>>> hints, but I don't think I can prove that this is safe until after part
>>> 6, so I did this for now instead.
>>>
>>>> Eventually, we'll be able to prove that 'info.line' must be an int and
>>>> is never None at static analysis time, and this assert can go
>>>> away. Until then, it's a type error to assume that self.info is not
>>>> None.
>>>>
>>>> Signed-off-by: John Snow <jsnow@redhat.com>
>>>> ---
>>>> scripts/qapi/error.py | 1 +
>>>> 1 file changed, 1 insertion(+)
>>>>
>>>> diff --git a/scripts/qapi/error.py b/scripts/qapi/error.py
>>>> index d179a3bd0c..d0bc7af6e7 100644
>>>> --- a/scripts/qapi/error.py
>>>> +++ b/scripts/qapi/error.py
>>>> @@ -25,6 +25,7 @@ def __init__(self, info, msg, col=None):
>>>> self.col = col
>>>> def __str__(self):
>>>> + assert self.info is not None
>>>> loc = str(self.info)
>>>> if self.col is not None:
>>>> assert self.info.line is not None
>>>>
>> Please show us the revised commit message. I'm asking because the
>> patch's purpose isn't quite clear to me. Peeking ahead at PATCH 7, I
>> see that info becomes Optional[QAPISourceInfo]. This means something
>> passes None for info (else you wouldn't make it optional). None info
>> Works (in the sense of "doesn't crash") as long as col is also None.
>> After the patch, it doesn't. I'm not saying that's bad, only that I
>> don't quite understand what you're trying to accomplish here.
>>
>
> Sure.
>
> If you recall, I tried to enforce that QAPISourceInfo was *never* None
> by creating a special value for QSI that represented "No Source
> Info". Ultimately, that idea didn't go through and we solidified that
> the 'info' parameter that gets passed around can sometimes be None.
>
> Hence, this property is Optional[QAPISourceInfo].
>
> Since I know you wanted to crash messily in the case that we allowed a
> None-info to leak into a context like this, I added the new assertion
> to make sure this remains the case.
>
> (since str(None) evaluates to "None", it seemed like there was already
> the implicit assumption that info would never be None. Plus, if 'col'
> is set, mypy notices that we are performing an unsafe check on
> self.info.line, which had to be remedied.)
>
> Later in the series, after schema.py is typed, it may be possible to
> remove these assertions as we may be able to rely on the strict typing
> to prove that this situation can never occur. However, since schema.py
> is not yet typed, we can't yet.
>
>
>
> Alright. So given that, I think what you'd like to see for a commit
> message is:
>
> qapi/error: assert QAPISourceInfo is not None
>
> We implicitly assume that self.info will never be None, as the error
> reporting function will not produce meaningful output in this case,
> and will crash if self.col was set. Assert that self.info is not None
> in order to formalize this assumption.
>
> We can not yet change the type of the initializer to prove that this
> is provably true at static analysis time until the rest of this
> library is fully typed.
Let's insert another paragraph to make the intent even clearer, and
adjust the remainder for it:
qapi/error: assert QAPISourceInfo is not None
Built-in stuff is not parsed from a source file, and therefore have no
QAPISourceInfo. If such None info was used for reporting an error,
built-in stuff would be broken. Programming error. Instead of
reporting a confusing error with bogus source location then, we better
crash.
We currently crash only if self.col was set. Assert that self.info is
not None in order to crash reliably.
We can not yet change the type of the initializer to prove this cannot
happen at static analysis time before the remainder of the code is
fully typed.
Note I also replaced "this library" because I'm not sure what it means.
What do you think?
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v2 1/8] qapi/error: Repurpose QAPIError as a generic exception base class
2021-04-16 6:04 ` Markus Armbruster
@ 2021-04-16 17:59 ` John Snow
0 siblings, 0 replies; 22+ messages in thread
From: John Snow @ 2021-04-16 17:59 UTC (permalink / raw)
To: Markus Armbruster
Cc: Peter Maydell, Eduardo Habkost, Michael Roth, Michael Roth,
qemu-devel, Cleber Rosa
On 4/16/21 2:04 AM, Markus Armbruster wrote:
> John Snow <jsnow@redhat.com> writes:
>
>> On 4/15/21 2:44 AM, Markus Armbruster wrote:
>>> John Snow <jsnow@redhat.com> writes:
>>>
>>>> Rename QAPIError to QAPISourceError, and then create a new QAPIError
>>>> class that serves as the basis for all of our other custom exceptions.
>>>
>>> Isn't the existing QAPIError such a base class already? Peeking
>>> ahead... aha, your new base class is abstract. Can you explain why we
>>> that's useful?
>>>
>>
>> Sure. The existing QAPIError class assumes that it's an error that has a
>> source location, but not all errors will. The idea is that an abstract
>> error class can be used as the ancestor for all other errors in a
>> type-safe way, such that:
>>
>> try:
>> qapi.something_or_other()
>> except QAPIError as err:
>> err.some_sort_of_method()
>>
>> (1) This is a type-safe construct, and
>> (2) This is sufficient to catch all errors that originate from within
>> the library, regardless of their exact type.
>>
>> Primarily, it's a ploy to get the SourceInfo stuff out of the
>> common/root exception for type safety reasons.
>>
>> (Later in the series, you'll see there's a few places where we try to
>> fake SourceInfo stuff in order to use QAPIError, by shuffling this
>> around, we allow ourselves to raise exceptions that don't need this
>> hackery.)
>
> I think you're conflating a real issue with a non-issue.
>
> The real issue: you want to get rid of fake QAPISourceInfo. This isn't
> terribly important, but small cleanups count, too. I can't see the "few
> places where we try to fake" in this series, though. Is it in a later
> part, or am I just blind?
>
I was mistaken, we don't fudge it except in one place, and that gets
fixed in the parser.py series, not this one.
What I really wanted: I wanted to make the base error object something
that doesn't have an info field at all, fake or not, so that it can be
ubiquitously re-used as an abstract, common ancestor.
A separate issue: Sometimes, we want to raise errors that *usually* have
source information, but *might* not sometimes, because of reasons.
So, I guess I don't really have a particularly strong technical
justification for this anymore, beyond "following a pattern I see and
use in other projects":
https://docs.python.org/3/tutorial/errors.html#user-defined-exceptions
"When creating a module that can raise several distinct errors, a common
practice is to create a base class for exceptions defined by that
module, and subclass that to create specific exception classes for
different error conditions"
Which QAPIError does not violate, though I usually see this pattern used
with a tabula rasa class to maximize re-use.
Some of my parser.py drafts that attempted to split out QAPIDoc using
the Exception-chaining method we discussed on call winds up using this
abstract class more directly, but we aren't sure we want that yet. (Or,
we're fairly sure we want to try to delay thinking about it. I am still
working on re-cleaning part 5.)
> The non-issue: wanting a common base class. Yes, we want one, but we
> already got one: QAPIError.
>
> I think the commit message should explain the real issue more clearly,
> without confusing readers with the non-issue.
>
> Makes sense?
>
I'll spend a few minutes and see if dropping this patch doesn't deeply
disturb later patches (or if it can be shuffled backwards to a point
where it makes more sense contextually).
I genuinely can't remember if it's going to wrench something else up
later or not right now, though; and I still haven't finished rebasing
part 5 so I don't have a "finished" product repository to test on anymore.
--js
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v2 4/8] qapi/error: Change assertion
2021-04-16 6:17 ` Markus Armbruster
@ 2021-04-16 18:24 ` John Snow
2021-04-17 12:10 ` Markus Armbruster
0 siblings, 1 reply; 22+ messages in thread
From: John Snow @ 2021-04-16 18:24 UTC (permalink / raw)
To: Markus Armbruster
Cc: Peter Maydell, Eduardo Habkost, Michael Roth, Michael Roth,
qemu-devel, Cleber Rosa
On 4/16/21 2:17 AM, Markus Armbruster wrote:
> John Snow <jsnow@redhat.com> writes:
>
>> On 4/15/21 3:00 AM, Markus Armbruster wrote:
>>> John Snow <jsnow@redhat.com> writes:
>>>
>>>> On 3/30/21 1:18 PM, John Snow wrote:
>>>>
>>>> Realizing now that this commit topic is wrong :)
>>>>
>>>> A prior version modified the assertion, I decided it was less churn to
>>>> simply add one.
>>>>
>>>> I think ideally we'd have no assertions here and we'd rely on the type
>>>> hints, but I don't think I can prove that this is safe until after part
>>>> 6, so I did this for now instead.
>>>>
>>>>> Eventually, we'll be able to prove that 'info.line' must be an int and
>>>>> is never None at static analysis time, and this assert can go
>>>>> away. Until then, it's a type error to assume that self.info is not
>>>>> None.
>>>>>
>>>>> Signed-off-by: John Snow <jsnow@redhat.com>
>>>>> ---
>>>>> scripts/qapi/error.py | 1 +
>>>>> 1 file changed, 1 insertion(+)
>>>>>
>>>>> diff --git a/scripts/qapi/error.py b/scripts/qapi/error.py
>>>>> index d179a3bd0c..d0bc7af6e7 100644
>>>>> --- a/scripts/qapi/error.py
>>>>> +++ b/scripts/qapi/error.py
>>>>> @@ -25,6 +25,7 @@ def __init__(self, info, msg, col=None):
>>>>> self.col = col
>>>>> def __str__(self):
>>>>> + assert self.info is not None
>>>>> loc = str(self.info)
>>>>> if self.col is not None:
>>>>> assert self.info.line is not None
>>>>>
>>> Please show us the revised commit message. I'm asking because the
>>> patch's purpose isn't quite clear to me. Peeking ahead at PATCH 7, I
>>> see that info becomes Optional[QAPISourceInfo]. This means something
>>> passes None for info (else you wouldn't make it optional). None info
>>> Works (in the sense of "doesn't crash") as long as col is also None.
>>> After the patch, it doesn't. I'm not saying that's bad, only that I
>>> don't quite understand what you're trying to accomplish here.
>>>
>>
>> Sure.
>>
>> If you recall, I tried to enforce that QAPISourceInfo was *never* None
>> by creating a special value for QSI that represented "No Source
>> Info". Ultimately, that idea didn't go through and we solidified that
>> the 'info' parameter that gets passed around can sometimes be None.
>>
>> Hence, this property is Optional[QAPISourceInfo].
>>
>> Since I know you wanted to crash messily in the case that we allowed a
>> None-info to leak into a context like this, I added the new assertion
>> to make sure this remains the case.
>>
>> (since str(None) evaluates to "None", it seemed like there was already
>> the implicit assumption that info would never be None. Plus, if 'col'
>> is set, mypy notices that we are performing an unsafe check on
>> self.info.line, which had to be remedied.)
>>
>> Later in the series, after schema.py is typed, it may be possible to
>> remove these assertions as we may be able to rely on the strict typing
>> to prove that this situation can never occur. However, since schema.py
>> is not yet typed, we can't yet.
>>
>>
>>
>> Alright. So given that, I think what you'd like to see for a commit
>> message is:
>>
>> qapi/error: assert QAPISourceInfo is not None
>>
>> We implicitly assume that self.info will never be None, as the error
>> reporting function will not produce meaningful output in this case,
>> and will crash if self.col was set. Assert that self.info is not None
>> in order to formalize this assumption.
>>
>> We can not yet change the type of the initializer to prove that this
>> is provably true at static analysis time until the rest of this
>> library is fully typed.
>
> Let's insert another paragraph to make the intent even clearer, and
> adjust the remainder for it:
>
> qapi/error: assert QAPISourceInfo is not None
>
> Built-in stuff is not parsed from a source file, and therefore have no
> QAPISourceInfo. If such None info was used for reporting an error,
> built-in stuff would be broken. Programming error. Instead of
> reporting a confusing error with bogus source location then, we better
> crash.
>
> We currently crash only if self.col was set. Assert that self.info is
> not None in order to crash reliably.
>
> We can not yet change the type of the initializer to prove this cannot
> happen at static analysis time before the remainder of the code is
> fully typed.
>
> Note I also replaced "this library" because I'm not sure what it means.
>
Wiggly-brain, wiggly-mouth. I meant "package". I get these terms
consistently wrong, and I need to really make a very direct effort to
treat them precisely correctly in the future.
MODULE: A single Python file. It may be a script, or serve only as a
library meant for consumption by other scripts.
PACKAGE: A collection of one or more Python modules. QAPI is a package
because it's a folder with an __init__.py file, containing several modules.
LIBRARY: No formal definition; however, I think of it as a Python module
that is meant primarily to be consumed by another script or entry-point.
It has some implications for things like Python's hierarchical logging
library where the distinction is important for how logger setup is
handled. Modules that use the "if __name__ == '__main__'" stanza serve
dual purpose as a script *and* library.
In this case, I meant "package". I think I accidentally avoid this term
because I don't want to imply that it is a "distributed package" in the
sense of a "PyPI package" and my brain skips a clock cycle and picks
another random word.
Bad habit. :(
> What do you think?
>
>
TLDR: I took your phrasing wholesale. Thanks!
--js
(Random aside on patch submission process: I do dislike how when I
change the topic of a commit, I lose out on git-backport-diff
functionality. I wish I had a persistent ID for commits that survived
beyond title changes. Sometimes I debate scripting adding some kind of
Patch-GUID: <...> tag, but I don't know if that would look like
undesirable clutter in the git log to everyone else. Maybe a "WAS:
old-topic-name-here" in the comment section would suffice well enough?)
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v2 4/8] qapi/error: Change assertion
2021-04-16 18:24 ` John Snow
@ 2021-04-17 12:10 ` Markus Armbruster
0 siblings, 0 replies; 22+ messages in thread
From: Markus Armbruster @ 2021-04-17 12:10 UTC (permalink / raw)
To: John Snow
Cc: Peter Maydell, Eduardo Habkost, Michael Roth, Michael Roth,
qemu-devel, Cleber Rosa
John Snow <jsnow@redhat.com> writes:
[...]
> (Random aside on patch submission process: I do dislike how when I
> change the topic of a commit, I lose out on git-backport-diff
> functionality. I wish I had a persistent ID for commits that survived
> beyond title changes. Sometimes I debate scripting adding some kind of
> Patch-GUID: <...> tag, but I don't know if that would look like
> undesirable clutter in the git log to everyone else. Maybe a "WAS:
> old-topic-name-here" in the comment section would suffice well enough?)
Have you tried git-range-diff?
^ permalink raw reply [flat|nested] 22+ messages in thread
end of thread, other threads:[~2021-04-17 12:11 UTC | newest]
Thread overview: 22+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-03-30 17:18 [PATCH v2 0/8] qapi: static typing conversion, pt4 John Snow
2021-03-30 17:18 ` [PATCH v2 1/8] qapi/error: Repurpose QAPIError as a generic exception base class John Snow
2021-04-15 6:44 ` Markus Armbruster
2021-04-15 15:28 ` John Snow
2021-04-16 6:04 ` Markus Armbruster
2021-04-16 17:59 ` John Snow
2021-03-30 17:18 ` [PATCH v2 2/8] qapi/error: Use Python3-style super() John Snow
2021-04-15 6:47 ` Markus Armbruster
2021-03-30 17:18 ` [PATCH v2 3/8] qapi/error: Make QAPISourceError 'col' parameter optional John Snow
2021-03-30 17:18 ` [PATCH v2 4/8] qapi/error: Change assertion John Snow
2021-04-08 15:31 ` John Snow
2021-04-15 7:00 ` Markus Armbruster
2021-04-15 15:44 ` John Snow
2021-04-16 6:17 ` Markus Armbruster
2021-04-16 18:24 ` John Snow
2021-04-17 12:10 ` Markus Armbruster
2021-03-30 17:18 ` [PATCH v2 5/8] qapi/error.py: move QAPIParseError to parser.py John Snow
2021-03-30 17:18 ` [PATCH v2 6/8] qapi/error.py: enable pylint checks John Snow
2021-03-30 17:18 ` [PATCH v2 7/8] qapi/error: Add type hints John Snow
2021-04-15 7:15 ` Markus Armbruster
2021-04-15 15:52 ` John Snow
2021-03-30 17:18 ` [PATCH v2 8/8] qapi/error.py: enable mypy checks John Snow
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).