diff mbox series

[16/37] qapi: establish mypy type-checking baseline

Message ID 20200915224027.2529813-17-jsnow@redhat.com
State Superseded
Headers show
Series qapi: static typing conversion, pt1 | expand

Commit Message

John Snow Sept. 15, 2020, 10:40 p.m. UTC
Fix two very minor issues, and then establish a mypy type-checking
baseline.

Like pylint, this should be run from the folder above:

 > mypy --config-file=qapi/mypy.ini qapi/

Signed-off-by: John Snow <jsnow@redhat.com>
---
 scripts/qapi/doc.py    |  3 +-
 scripts/qapi/mypy.ini  | 65 ++++++++++++++++++++++++++++++++++++++++++
 scripts/qapi/schema.py |  3 +-
 3 files changed, 69 insertions(+), 2 deletions(-)
 create mode 100644 scripts/qapi/mypy.ini

Comments

Markus Armbruster Sept. 18, 2020, 11:55 a.m. UTC | #1
Ignorant question: why does this come after PATCH 13 "qapi/common.py:
add notational type hints", but before all the other patches adding type
hints?

John Snow <jsnow@redhat.com> writes:

> Fix two very minor issues, and then establish a mypy type-checking

> baseline.

>

> Like pylint, this should be run from the folder above:

>

>  > mypy --config-file=qapi/mypy.ini qapi/


I get:

    $ mypy --config-file qapi/mypy.ini qapi
    qapi/mypy.ini: [mypy]: Strict mode is not supported in configuration files: specify individual flags instead (see 'mypy -h' for the list of flags enabled in strict mode)
    qapi/types.py:29: error: Need type annotation for 'objects_seen' (hint: "objects_seen: Set[<type>] = ...")
    Found 1 error in 1 file (checked 18 source files)

Is this expected?

In case it matters:

    $ mypy --version
    mypy 0.761

> Signed-off-by: John Snow <jsnow@redhat.com>

> ---

>  scripts/qapi/doc.py    |  3 +-

>  scripts/qapi/mypy.ini  | 65 ++++++++++++++++++++++++++++++++++++++++++

>  scripts/qapi/schema.py |  3 +-

>  3 files changed, 69 insertions(+), 2 deletions(-)

>  create mode 100644 scripts/qapi/mypy.ini

>

> diff --git a/scripts/qapi/doc.py b/scripts/qapi/doc.py

> index cbf7076ed9..70f7cdfaa6 100644

> --- a/scripts/qapi/doc.py

> +++ b/scripts/qapi/doc.py

> @@ -5,7 +5,8 @@

>  """This script produces the documentation of a qapi schema in texinfo format"""

>  

>  import re

> -from .gen import QAPIGenDoc, QAPISchemaVisitor

> +from .gen import QAPIGenDoc

> +from .schema import QAPISchemaVisitor


Your mypy doesn't like such lazy imports?  Mine seems not to care.

>  

>  

>  MSG_FMT = """

> diff --git a/scripts/qapi/mypy.ini b/scripts/qapi/mypy.ini

> new file mode 100644

> index 0000000000..a0f2365a53

> --- /dev/null

> +++ b/scripts/qapi/mypy.ini

> @@ -0,0 +1,65 @@

> +[mypy]

> +strict = True

> +strict_optional = False

> +disallow_untyped_calls = False

> +python_version = 3.6

> +

> +[mypy-qapi.commands]

> +disallow_untyped_defs = False

> +disallow_incomplete_defs = False

> +check_untyped_defs = False

> +

> +[mypy-qapi.doc]

> +disallow_subclassing_any = False

> +disallow_untyped_defs = False

> +disallow_incomplete_defs = False

> +

> +[mypy-qapi.error]

> +disallow_untyped_defs = False

> +disallow_incomplete_defs = False

> +check_untyped_defs = False

> +

> +[mypy-qapi.events]

> +disallow_untyped_defs = False

> +disallow_incomplete_defs = False

> +check_untyped_defs = False

> +

> +[mypy-qapi.expr]

> +disallow_untyped_defs = False

> +disallow_incomplete_defs = False

> +check_untyped_defs = False

> +

> +[mypy-qapi.gen]

> +disallow_untyped_defs = False

> +disallow_incomplete_defs = False

> +check_untyped_defs = False

> +

> +[mypy-qapi.introspect]

> +disallow_untyped_defs = False

> +disallow_incomplete_defs = False

> +check_untyped_defs = False

> +

> +[mypy-qapi.parser]

> +disallow_untyped_defs = False

> +disallow_incomplete_defs = False

> +check_untyped_defs = False

> +

> +[mypy-qapi.schema]

> +disallow_untyped_defs = False

> +disallow_incomplete_defs = False

> +check_untyped_defs = False

> +

> +[mypy-qapi.source]

> +disallow_untyped_defs = False

> +disallow_incomplete_defs = False

> +check_untyped_defs = False

> +

> +[mypy-qapi.types]

> +disallow_untyped_defs = False

> +disallow_incomplete_defs = False

> +check_untyped_defs = False

> +

> +[mypy-qapi.visit]

> +disallow_untyped_defs = False

> +disallow_incomplete_defs = False

> +check_untyped_defs = False


Greek to me.  I'll learn in due time.

> diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py

> index b4921b46c9..bb0cd717f1 100644

> --- a/scripts/qapi/schema.py

> +++ b/scripts/qapi/schema.py

> @@ -17,6 +17,7 @@

>  import os

>  import re

>  from collections import OrderedDict

> +from typing import Optional

>  

>  from .common import c_name, POINTER_SUFFIX

>  from .error import QAPIError, QAPISemError

> @@ -25,7 +26,7 @@

>  

>  

>  class QAPISchemaEntity:

> -    meta = None

> +    meta: Optional[str] = None

>  

>      def __init__(self, name, info, doc, ifcond=None, features=None):

>          assert name is None or isinstance(name, str)
John Snow Sept. 18, 2020, 2:27 p.m. UTC | #2
On 9/18/20 7:55 AM, Markus Armbruster wrote:
> Ignorant question: why does this come after PATCH 13 "qapi/common.py:

> add notational type hints", but before all the other patches adding type

> hints?

> 

> John Snow <jsnow@redhat.com> writes:

> 

>> Fix two very minor issues, and then establish a mypy type-checking

>> baseline.

>>

>> Like pylint, this should be run from the folder above:

>>

>>   > mypy --config-file=qapi/mypy.ini qapi/

> 

> I get:

> 

>      $ mypy --config-file qapi/mypy.ini qapi

>      qapi/mypy.ini: [mypy]: Strict mode is not supported in configuration files: specify individual flags instead (see 'mypy -h' for the list of flags enabled in strict mode)

>      qapi/types.py:29: error: Need type annotation for 'objects_seen' (hint: "objects_seen: Set[<type>] = ...")

>      Found 1 error in 1 file (checked 18 source files)

> 

> Is this expected?

> 


Nope.

> In case it matters:

> 

>      $ mypy --version

>      mypy 0.761

> 


I am using mypy 0.782.

I will investigate to see if there is an *easy* win to allow older 
versions to work.

In the meantime, please consider trying this:

pip install --user mypy==0.782
~/.local/bin/mypy --config-file=qapi/mypy.ini qapi/

>> Signed-off-by: John Snow <jsnow@redhat.com>

>> ---

>>   scripts/qapi/doc.py    |  3 +-

>>   scripts/qapi/mypy.ini  | 65 ++++++++++++++++++++++++++++++++++++++++++

>>   scripts/qapi/schema.py |  3 +-

>>   3 files changed, 69 insertions(+), 2 deletions(-)

>>   create mode 100644 scripts/qapi/mypy.ini

>>

>> diff --git a/scripts/qapi/doc.py b/scripts/qapi/doc.py

>> index cbf7076ed9..70f7cdfaa6 100644

>> --- a/scripts/qapi/doc.py

>> +++ b/scripts/qapi/doc.py

>> @@ -5,7 +5,8 @@

>>   """This script produces the documentation of a qapi schema in texinfo format"""

>>   

>>   import re

>> -from .gen import QAPIGenDoc, QAPISchemaVisitor

>> +from .gen import QAPIGenDoc

>> +from .schema import QAPISchemaVisitor

> 

> Your mypy doesn't like such lazy imports?  Mine seems not to care.

> 


Yeah, it specifically complained that no such definition existed in that 
file.

>>   

>>   

>>   MSG_FMT = """

>> diff --git a/scripts/qapi/mypy.ini b/scripts/qapi/mypy.ini

>> new file mode 100644

>> index 0000000000..a0f2365a53

>> --- /dev/null

>> +++ b/scripts/qapi/mypy.ini

>> @@ -0,0 +1,65 @@

>> +[mypy]

>> +strict = True

>> +strict_optional = False

>> +disallow_untyped_calls = False

>> +python_version = 3.6

>> +

>> +[mypy-qapi.commands]

>> +disallow_untyped_defs = False

>> +disallow_incomplete_defs = False

>> +check_untyped_defs = False

>> +

>> +[mypy-qapi.doc]

>> +disallow_subclassing_any = False

>> +disallow_untyped_defs = False

>> +disallow_incomplete_defs = False

>> +

>> +[mypy-qapi.error]

>> +disallow_untyped_defs = False

>> +disallow_incomplete_defs = False

>> +check_untyped_defs = False

>> +

>> +[mypy-qapi.events]

>> +disallow_untyped_defs = False

>> +disallow_incomplete_defs = False

>> +check_untyped_defs = False

>> +

>> +[mypy-qapi.expr]

>> +disallow_untyped_defs = False

>> +disallow_incomplete_defs = False

>> +check_untyped_defs = False

>> +

>> +[mypy-qapi.gen]

>> +disallow_untyped_defs = False

>> +disallow_incomplete_defs = False

>> +check_untyped_defs = False

>> +

>> +[mypy-qapi.introspect]

>> +disallow_untyped_defs = False

>> +disallow_incomplete_defs = False

>> +check_untyped_defs = False

>> +

>> +[mypy-qapi.parser]

>> +disallow_untyped_defs = False

>> +disallow_incomplete_defs = False

>> +check_untyped_defs = False

>> +

>> +[mypy-qapi.schema]

>> +disallow_untyped_defs = False

>> +disallow_incomplete_defs = False

>> +check_untyped_defs = False

>> +

>> +[mypy-qapi.source]

>> +disallow_untyped_defs = False

>> +disallow_incomplete_defs = False

>> +check_untyped_defs = False

>> +

>> +[mypy-qapi.types]

>> +disallow_untyped_defs = False

>> +disallow_incomplete_defs = False

>> +check_untyped_defs = False

>> +

>> +[mypy-qapi.visit]

>> +disallow_untyped_defs = False

>> +disallow_incomplete_defs = False

>> +check_untyped_defs = False

> 

> Greek to me.  I'll learn in due time.

> 


I am using these options:

--strict, which is effectively -Wall.

--no-strict-optional, which disables type checking errors on conflict 
between Optional[T] and T. Namely, when you initialize a class field to 
None and set that variable after initialization, callers must be 
prepared to see if that field was None or not. We do that effectively 
nowhere.

As Python will surely explode in a noticeable way if we got an 
unexpected 'None', I am just suppressing these warnings "for now".

--allow-untyped-calls silences errors in files that have calls to 
functions in files I still have not typed. By the end of the series, 
this option goes away, because there's nothing untyped left.


For each untyped file, we are actually starting with all of the above 
options and then layering these options on top. Any egregious typing 
errors present in these "ignored" files will be spotted.

To get the bad files to pass, we only need three options:

allow untyped defs -- Simply permits us to have functions without 
annotations.

allow incomplete defs -- allows functions that are only partially typed.

check untyped defs = False -- Don't try to type check untyped definitions.

>> diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py

>> index b4921b46c9..bb0cd717f1 100644

>> --- a/scripts/qapi/schema.py

>> +++ b/scripts/qapi/schema.py

>> @@ -17,6 +17,7 @@

>>   import os

>>   import re

>>   from collections import OrderedDict

>> +from typing import Optional

>>   

>>   from .common import c_name, POINTER_SUFFIX

>>   from .error import QAPIError, QAPISemError

>> @@ -25,7 +26,7 @@

>>   

>>   

>>   class QAPISchemaEntity:

>> -    meta = None

>> +    meta: Optional[str] = None

>>   

>>       def __init__(self, name, info, doc, ifcond=None, features=None):

>>           assert name is None or isinstance(name, str)
John Snow Sept. 18, 2020, 7:03 p.m. UTC | #3
On 9/18/20 7:55 AM, Markus Armbruster wrote:
> Ignorant question: why does this come after PATCH 13 "qapi/common.py:
> add notational type hints", but before all the other patches adding type
> hints?
> 
> John Snow <jsnow@redhat.com> writes:
> 
>> Fix two very minor issues, and then establish a mypy type-checking
>> baseline.
>>
>> Like pylint, this should be run from the folder above:
>>
>>   > mypy --config-file=qapi/mypy.ini qapi/
> 
> I get:
> 
>      $ mypy --config-file qapi/mypy.ini qapi
>      qapi/mypy.ini: [mypy]: Strict mode is not supported in configuration files: specify individual flags instead (see 'mypy -h' for the list of flags enabled in strict mode)
>      qapi/types.py:29: error: Need type annotation for 'objects_seen' (hint: "objects_seen: Set[<type>] = ...")
>      Found 1 error in 1 file (checked 18 source files)
> 
> Is this expected?
> 
> In case it matters:
> 
>      $ mypy --version
>      mypy 0.761
> 

(Warning; FiSH and stgit ahead)

cd ~/src/qemu/scripts
pipenv --python 3.6
pipenv shell
pip install pylint flake8

### Testing mypy 0.770

pip install mypy==0.770
stg goto qapi-establish-mypy-type

while true; and flake8 qapi/; and pylint --rcfile=qapi/pylintrc qapi/; 
and mypy --config-file=qapi/mypy.ini qapi/; and stg push; end

pip uninstall mypy

###



0.782 - OK
0.770 - OK
0.760 - FAIL, Fixable*
0.750 - OK*
0.740 - OK*
0.730 - OK*
0.720 - OK*
0.710 - OK** (Does not recognize 'no_implicit_reexport' option)
0.700 - OK*** (Not compatible with bleeding edge pylint/flake8)
0.670 - OK***
0.660 - OK***
0.650 - OK***
0.641 - OK***
0.630 - Fails again.



0.760 doesn't support strict in the config file (It needs component 
options), and it wants a few extra annotations where its inference power 
is weaker. Well, easy enough to fix up.

0.630 fails again and insists that __init__ should have a return type 
annotation of None. Modern mypy is smart enough to know that's what that 
type is supposed to be. Arbitrarily, this is my cutoff for what seems 
reasonable to even want to support.

I still find the lack of "strict=true" in the config file irritating and 
might ask to target 0.770 or newer. There should be no reason we can't 
install that in a venv for CI to chew on.

Humbly ask I take the lazy way out and document that we support mypy >= 
0.770.

--js
Markus Armbruster Sept. 21, 2020, 8:05 a.m. UTC | #4
John Snow <jsnow@redhat.com> writes:

> On 9/18/20 7:55 AM, Markus Armbruster wrote:

>> Ignorant question: why does this come after PATCH 13 "qapi/common.py:

>> add notational type hints", but before all the other patches adding type

>> hints?

>> John Snow <jsnow@redhat.com> writes:

>> 

>>> Fix two very minor issues, and then establish a mypy type-checking

>>> baseline.

>>>

>>> Like pylint, this should be run from the folder above:

>>>

>>>   > mypy --config-file=qapi/mypy.ini qapi/

>> I get:

>>      $ mypy --config-file qapi/mypy.ini qapi

>>      qapi/mypy.ini: [mypy]: Strict mode is not supported in configuration files: specify individual flags instead (see 'mypy -h' for the list of flags enabled in strict mode)

>>      qapi/types.py:29: error: Need type annotation for 'objects_seen' (hint: "objects_seen: Set[<type>] = ...")

>>      Found 1 error in 1 file (checked 18 source files)

>> Is this expected?

>> 

>

> Nope.

>

>> In case it matters:

>>      $ mypy --version

>>      mypy 0.761

>> 

>

> I am using mypy 0.782.

>

> I will investigate to see if there is an *easy* win to allow older

> versions to work.

>

> In the meantime, please consider trying this:

>

> pip install --user mypy==0.782

> ~/.local/bin/mypy --config-file=qapi/mypy.ini qapi/


I'll consider dragging my feet until upgrading Fedora gives it to me for
free.

The less I interact with package managers, the happier I am.

>>> Signed-off-by: John Snow <jsnow@redhat.com>

>>> ---

>>>   scripts/qapi/doc.py    |  3 +-

>>>   scripts/qapi/mypy.ini  | 65 ++++++++++++++++++++++++++++++++++++++++++

>>>   scripts/qapi/schema.py |  3 +-

>>>   3 files changed, 69 insertions(+), 2 deletions(-)

>>>   create mode 100644 scripts/qapi/mypy.ini

>>>

>>> diff --git a/scripts/qapi/doc.py b/scripts/qapi/doc.py

>>> index cbf7076ed9..70f7cdfaa6 100644

>>> --- a/scripts/qapi/doc.py

>>> +++ b/scripts/qapi/doc.py

>>> @@ -5,7 +5,8 @@

>>>   """This script produces the documentation of a qapi schema in texinfo format"""

>>>     import re

>>> -from .gen import QAPIGenDoc, QAPISchemaVisitor

>>> +from .gen import QAPIGenDoc

>>> +from .schema import QAPISchemaVisitor

>> Your mypy doesn't like such lazy imports?  Mine seems not to care.

>> 

>

> Yeah, it specifically complained that no such definition existed in

> that file.


I sense a certain wobbliness in mypy.  Perhaps to be expected from a
tool with major version zero.  There's a risk that developers' local
"make check" and our gating CI differ too much.  We'll see.

>>>     

>>>   MSG_FMT = """

>>> diff --git a/scripts/qapi/mypy.ini b/scripts/qapi/mypy.ini

>>> new file mode 100644

>>> index 0000000000..a0f2365a53

>>> --- /dev/null

>>> +++ b/scripts/qapi/mypy.ini

>>> @@ -0,0 +1,65 @@

>>> +[mypy]

>>> +strict = True

>>> +strict_optional = False

>>> +disallow_untyped_calls = False

>>> +python_version = 3.6

>>> +

>>> +[mypy-qapi.commands]

>>> +disallow_untyped_defs = False

>>> +disallow_incomplete_defs = False

>>> +check_untyped_defs = False

>>> +

>>> +[mypy-qapi.doc]

>>> +disallow_subclassing_any = False

>>> +disallow_untyped_defs = False

>>> +disallow_incomplete_defs = False

>>> +

>>> +[mypy-qapi.error]

>>> +disallow_untyped_defs = False

>>> +disallow_incomplete_defs = False

>>> +check_untyped_defs = False

>>> +

>>> +[mypy-qapi.events]

>>> +disallow_untyped_defs = False

>>> +disallow_incomplete_defs = False

>>> +check_untyped_defs = False

>>> +

>>> +[mypy-qapi.expr]

>>> +disallow_untyped_defs = False

>>> +disallow_incomplete_defs = False

>>> +check_untyped_defs = False

>>> +

>>> +[mypy-qapi.gen]

>>> +disallow_untyped_defs = False

>>> +disallow_incomplete_defs = False

>>> +check_untyped_defs = False

>>> +

>>> +[mypy-qapi.introspect]

>>> +disallow_untyped_defs = False

>>> +disallow_incomplete_defs = False

>>> +check_untyped_defs = False

>>> +

>>> +[mypy-qapi.parser]

>>> +disallow_untyped_defs = False

>>> +disallow_incomplete_defs = False

>>> +check_untyped_defs = False

>>> +

>>> +[mypy-qapi.schema]

>>> +disallow_untyped_defs = False

>>> +disallow_incomplete_defs = False

>>> +check_untyped_defs = False

>>> +

>>> +[mypy-qapi.source]

>>> +disallow_untyped_defs = False

>>> +disallow_incomplete_defs = False

>>> +check_untyped_defs = False

>>> +

>>> +[mypy-qapi.types]

>>> +disallow_untyped_defs = False

>>> +disallow_incomplete_defs = False

>>> +check_untyped_defs = False

>>> +

>>> +[mypy-qapi.visit]

>>> +disallow_untyped_defs = False

>>> +disallow_incomplete_defs = False

>>> +check_untyped_defs = False

>> Greek to me.  I'll learn in due time.

>> 

>

> I am using these options:

>

> --strict, which is effectively -Wall.

>

> --no-strict-optional, which disables type checking errors on conflict

>   between Optional[T] and T. Namely, when you initialize a class field

>  to None and set that variable after initialization, callers must be 

> prepared to see if that field was None or not. We do that effectively

> nowhere.

>

> As Python will surely explode in a noticeable way if we got an

> unexpected 'None', I am just suppressing these warnings "for now".


Okay.

We may want to rethink how we define and initialize these variables.  We
initialize mostly to keep pylint happy.  We initialize to None precisely
to make use before the real initialization explode.

> --allow-untyped-calls silences errors in files that have calls to

>   functions in files I still have not typed. By the end of the series, 

> this option goes away, because there's nothing untyped left.

>

>

> For each untyped file, we are actually starting with all of the above

> options and then layering these options on top. Any egregious typing 

> errors present in these "ignored" files will be spotted.

>

> To get the bad files to pass, we only need three options:

>

> allow untyped defs -- Simply permits us to have functions without

> annotations.

>

> allow incomplete defs -- allows functions that are only partially typed.

>

> check untyped defs = False -- Don't try to type check untyped definitions.


Thanks!

[...]
Markus Armbruster Sept. 21, 2020, 8:05 a.m. UTC | #5
John Snow <jsnow@redhat.com> writes:

> On 9/18/20 7:55 AM, Markus Armbruster wrote:
>> Ignorant question: why does this come after PATCH 13 "qapi/common.py:
>> add notational type hints", but before all the other patches adding type
>> hints?
>> John Snow <jsnow@redhat.com> writes:
>> 
>>> Fix two very minor issues, and then establish a mypy type-checking
>>> baseline.
>>>
>>> Like pylint, this should be run from the folder above:
>>>
>>>   > mypy --config-file=qapi/mypy.ini qapi/
>> I get:
>>      $ mypy --config-file qapi/mypy.ini qapi
>>      qapi/mypy.ini: [mypy]: Strict mode is not supported in configuration files: specify individual flags instead (see 'mypy -h' for the list of flags enabled in strict mode)
>>      qapi/types.py:29: error: Need type annotation for 'objects_seen' (hint: "objects_seen: Set[<type>] = ...")
>>      Found 1 error in 1 file (checked 18 source files)
>> Is this expected?
>> In case it matters:
>>      $ mypy --version
>>      mypy 0.761
>> 
>
> (Warning; FiSH and stgit ahead)
>
> cd ~/src/qemu/scripts
> pipenv --python 3.6
> pipenv shell
> pip install pylint flake8
>
> ### Testing mypy 0.770
>
> pip install mypy==0.770
> stg goto qapi-establish-mypy-type
>
> while true; and flake8 qapi/; and pylint --rcfile=qapi/pylintrc qapi/;
> and mypy --config-file=qapi/mypy.ini qapi/; and stg push; end
>
> pip uninstall mypy
>
> ###
>
>
>
> 0.782 - OK
> 0.770 - OK
> 0.760 - FAIL, Fixable*
> 0.750 - OK*
> 0.740 - OK*
> 0.730 - OK*
> 0.720 - OK*
> 0.710 - OK** (Does not recognize 'no_implicit_reexport' option)
> 0.700 - OK*** (Not compatible with bleeding edge pylint/flake8)
> 0.670 - OK***
> 0.660 - OK***
> 0.650 - OK***
> 0.641 - OK***
> 0.630 - Fails again.
>
>
>
> 0.760 doesn't support strict in the config file (It needs component
> options), and it wants a few extra annotations where its inference
> power is weaker. Well, easy enough to fix up.
>
> 0.630 fails again and insists that __init__ should have a return type
> annotation of None. Modern mypy is smart enough to know that's what
> that type is supposed to be. Arbitrarily, this is my cutoff for what
> seems reasonable to even want to support.
>
> I still find the lack of "strict=true" in the config file irritating
> and might ask to target 0.770 or newer. There should be no reason we
> can't install that in a venv for CI to chew on.
>
> Humbly ask I take the lazy way out and document that we support mypy
> >= 0.770.

Our "supported build platforms" policy puts hard limits on the minimum
versions for tools the build requires.

Mypy is not such a tool.  I hope we get to the point where we can have
"make check" run it, but skipping the test when we don't have a
sufficiently modern mypy feels okay to me, as long as our gating CI
still guards the master branch.
John Snow Sept. 21, 2020, 2:41 p.m. UTC | #6
On 9/21/20 4:05 AM, Markus Armbruster wrote:
> John Snow <jsnow@redhat.com> writes:

> 

>> On 9/18/20 7:55 AM, Markus Armbruster wrote:

>>> Ignorant question: why does this come after PATCH 13 "qapi/common.py:

>>> add notational type hints", but before all the other patches adding type

>>> hints?

>>> John Snow <jsnow@redhat.com> writes:

>>>

>>>> Fix two very minor issues, and then establish a mypy type-checking

>>>> baseline.

>>>>

>>>> Like pylint, this should be run from the folder above:

>>>>

>>>>    > mypy --config-file=qapi/mypy.ini qapi/

>>> I get:

>>>       $ mypy --config-file qapi/mypy.ini qapi

>>>       qapi/mypy.ini: [mypy]: Strict mode is not supported in configuration files: specify individual flags instead (see 'mypy -h' for the list of flags enabled in strict mode)

>>>       qapi/types.py:29: error: Need type annotation for 'objects_seen' (hint: "objects_seen: Set[<type>] = ...")

>>>       Found 1 error in 1 file (checked 18 source files)

>>> Is this expected?

>>>

>>

>> Nope.

>>

>>> In case it matters:

>>>       $ mypy --version

>>>       mypy 0.761

>>>

>>

>> I am using mypy 0.782.

>>

>> I will investigate to see if there is an *easy* win to allow older

>> versions to work.

>>

>> In the meantime, please consider trying this:

>>

>> pip install --user mypy==0.782

>> ~/.local/bin/mypy --config-file=qapi/mypy.ini qapi/

> 

> I'll consider dragging my feet until upgrading Fedora gives it to me for

> free.

> 

> The less I interact with package managers, the happier I am.

> 


I'll probably be working on making sure CI runs in a virtual environment 
with specifically pinned versions.

We don't need mypy to run or build, so I have been less worried about 
requiring bleeding edge versions for development and regression testing.

>>>> Signed-off-by: John Snow <jsnow@redhat.com>

>>>> ---

>>>>    scripts/qapi/doc.py    |  3 +-

>>>>    scripts/qapi/mypy.ini  | 65 ++++++++++++++++++++++++++++++++++++++++++

>>>>    scripts/qapi/schema.py |  3 +-

>>>>    3 files changed, 69 insertions(+), 2 deletions(-)

>>>>    create mode 100644 scripts/qapi/mypy.ini

>>>>

>>>> diff --git a/scripts/qapi/doc.py b/scripts/qapi/doc.py

>>>> index cbf7076ed9..70f7cdfaa6 100644

>>>> --- a/scripts/qapi/doc.py

>>>> +++ b/scripts/qapi/doc.py

>>>> @@ -5,7 +5,8 @@

>>>>    """This script produces the documentation of a qapi schema in texinfo format"""

>>>>      import re

>>>> -from .gen import QAPIGenDoc, QAPISchemaVisitor

>>>> +from .gen import QAPIGenDoc

>>>> +from .schema import QAPISchemaVisitor

>>> Your mypy doesn't like such lazy imports?  Mine seems not to care.

>>>

>>

>> Yeah, it specifically complained that no such definition existed in

>> that file.

> 

> I sense a certain wobbliness in mypy.  Perhaps to be expected from a

> tool with major version zero.  There's a risk that developers' local

> "make check" and our gating CI differ too much.  We'll see.

> 


It is indeed very cutting edge. It wasn't really viable to use until 
Python 3.6.

>>>>      

>>>>    MSG_FMT = """

>>>> diff --git a/scripts/qapi/mypy.ini b/scripts/qapi/mypy.ini

>>>> new file mode 100644

>>>> index 0000000000..a0f2365a53

>>>> --- /dev/null

>>>> +++ b/scripts/qapi/mypy.ini

>>>> @@ -0,0 +1,65 @@

>>>> +[mypy]

>>>> +strict = True

>>>> +strict_optional = False

>>>> +disallow_untyped_calls = False

>>>> +python_version = 3.6

>>>> +

>>>> +[mypy-qapi.commands]

>>>> +disallow_untyped_defs = False

>>>> +disallow_incomplete_defs = False

>>>> +check_untyped_defs = False

>>>> +

>>>> +[mypy-qapi.doc]

>>>> +disallow_subclassing_any = False

>>>> +disallow_untyped_defs = False

>>>> +disallow_incomplete_defs = False

>>>> +

>>>> +[mypy-qapi.error]

>>>> +disallow_untyped_defs = False

>>>> +disallow_incomplete_defs = False

>>>> +check_untyped_defs = False

>>>> +

>>>> +[mypy-qapi.events]

>>>> +disallow_untyped_defs = False

>>>> +disallow_incomplete_defs = False

>>>> +check_untyped_defs = False

>>>> +

>>>> +[mypy-qapi.expr]

>>>> +disallow_untyped_defs = False

>>>> +disallow_incomplete_defs = False

>>>> +check_untyped_defs = False

>>>> +

>>>> +[mypy-qapi.gen]

>>>> +disallow_untyped_defs = False

>>>> +disallow_incomplete_defs = False

>>>> +check_untyped_defs = False

>>>> +

>>>> +[mypy-qapi.introspect]

>>>> +disallow_untyped_defs = False

>>>> +disallow_incomplete_defs = False

>>>> +check_untyped_defs = False

>>>> +

>>>> +[mypy-qapi.parser]

>>>> +disallow_untyped_defs = False

>>>> +disallow_incomplete_defs = False

>>>> +check_untyped_defs = False

>>>> +

>>>> +[mypy-qapi.schema]

>>>> +disallow_untyped_defs = False

>>>> +disallow_incomplete_defs = False

>>>> +check_untyped_defs = False

>>>> +

>>>> +[mypy-qapi.source]

>>>> +disallow_untyped_defs = False

>>>> +disallow_incomplete_defs = False

>>>> +check_untyped_defs = False

>>>> +

>>>> +[mypy-qapi.types]

>>>> +disallow_untyped_defs = False

>>>> +disallow_incomplete_defs = False

>>>> +check_untyped_defs = False

>>>> +

>>>> +[mypy-qapi.visit]

>>>> +disallow_untyped_defs = False

>>>> +disallow_incomplete_defs = False

>>>> +check_untyped_defs = False

>>> Greek to me.  I'll learn in due time.

>>>

>>

>> I am using these options:

>>

>> --strict, which is effectively -Wall.

>>

>> --no-strict-optional, which disables type checking errors on conflict

>>    between Optional[T] and T. Namely, when you initialize a class field

>>   to None and set that variable after initialization, callers must be

>> prepared to see if that field was None or not. We do that effectively

>> nowhere.

>>

>> As Python will surely explode in a noticeable way if we got an

>> unexpected 'None', I am just suppressing these warnings "for now".

> 

> Okay.

> 

> We may want to rethink how we define and initialize these variables.  We

> initialize mostly to keep pylint happy.  We initialize to None precisely

> to make use before the real initialization explode.

> 


The pattern we have is roughly correct; it's just that we rely on that 
implicit explosion, which mypy will warn about if I don't disable this 
option.

It's not worth addressing in our first pass, but if you look at 
schema.py's @property ifcond code, that's the type of thing that would 
"fix" this warning -- basically access shims that add a "real" error 
message when it's None, but convert the return type from Optional[T] to T.

It's later stuff.

>> --allow-untyped-calls silences errors in files that have calls to

>>    functions in files I still have not typed. By the end of the series,

>> this option goes away, because there's nothing untyped left.

>>

>>

>> For each untyped file, we are actually starting with all of the above

>> options and then layering these options on top. Any egregious typing

>> errors present in these "ignored" files will be spotted.

>>

>> To get the bad files to pass, we only need three options:

>>

>> allow untyped defs -- Simply permits us to have functions without

>> annotations.

>>

>> allow incomplete defs -- allows functions that are only partially typed.

>>

>> check untyped defs = False -- Don't try to type check untyped definitions.

> 

> Thanks!

> 

> [...]

>
John Snow Sept. 21, 2020, 2:46 p.m. UTC | #7
On 9/21/20 4:05 AM, Markus Armbruster wrote:
> John Snow <jsnow@redhat.com> writes:
> 
>> On 9/18/20 7:55 AM, Markus Armbruster wrote:
>>> Ignorant question: why does this come after PATCH 13 "qapi/common.py:
>>> add notational type hints", but before all the other patches adding type
>>> hints?
>>> John Snow <jsnow@redhat.com> writes:
>>>
>>>> Fix two very minor issues, and then establish a mypy type-checking
>>>> baseline.
>>>>
>>>> Like pylint, this should be run from the folder above:
>>>>
>>>>    > mypy --config-file=qapi/mypy.ini qapi/
>>> I get:
>>>       $ mypy --config-file qapi/mypy.ini qapi
>>>       qapi/mypy.ini: [mypy]: Strict mode is not supported in configuration files: specify individual flags instead (see 'mypy -h' for the list of flags enabled in strict mode)
>>>       qapi/types.py:29: error: Need type annotation for 'objects_seen' (hint: "objects_seen: Set[<type>] = ...")
>>>       Found 1 error in 1 file (checked 18 source files)
>>> Is this expected?
>>> In case it matters:
>>>       $ mypy --version
>>>       mypy 0.761
>>>
>>
>> (Warning; FiSH and stgit ahead)
>>
>> cd ~/src/qemu/scripts
>> pipenv --python 3.6
>> pipenv shell
>> pip install pylint flake8
>>
>> ### Testing mypy 0.770
>>
>> pip install mypy==0.770
>> stg goto qapi-establish-mypy-type
>>
>> while true; and flake8 qapi/; and pylint --rcfile=qapi/pylintrc qapi/;
>> and mypy --config-file=qapi/mypy.ini qapi/; and stg push; end
>>
>> pip uninstall mypy
>>
>> ###
>>
>>
>>
>> 0.782 - OK
>> 0.770 - OK
>> 0.760 - FAIL, Fixable*
>> 0.750 - OK*
>> 0.740 - OK*
>> 0.730 - OK*
>> 0.720 - OK*
>> 0.710 - OK** (Does not recognize 'no_implicit_reexport' option)
>> 0.700 - OK*** (Not compatible with bleeding edge pylint/flake8)
>> 0.670 - OK***
>> 0.660 - OK***
>> 0.650 - OK***
>> 0.641 - OK***
>> 0.630 - Fails again.
>>
>>
>>
>> 0.760 doesn't support strict in the config file (It needs component
>> options), and it wants a few extra annotations where its inference
>> power is weaker. Well, easy enough to fix up.
>>
>> 0.630 fails again and insists that __init__ should have a return type
>> annotation of None. Modern mypy is smart enough to know that's what
>> that type is supposed to be. Arbitrarily, this is my cutoff for what
>> seems reasonable to even want to support.
>>
>> I still find the lack of "strict=true" in the config file irritating
>> and might ask to target 0.770 or newer. There should be no reason we
>> can't install that in a venv for CI to chew on.
>>
>> Humbly ask I take the lazy way out and document that we support mypy
>>> = 0.770.
> 
> Our "supported build platforms" policy puts hard limits on the minimum
> versions for tools the build requires.
> 
> Mypy is not such a tool.  I hope we get to the point where we can have
> "make check" run it, but skipping the test when we don't have a
> sufficiently modern mypy feels okay to me, as long as our gating CI
> still guards the master branch.
> 

Yes, that's been my view. These tools *are* new and they *do* change 
often. Chasing a wide compatibility window with them is prohibitively 
difficult. Luckily, they're not needed for running the packages at all.

If I do add linting to 'make check', it is going to be through a virtual 
environment that deploys *specific versions* of these tools, and it will 
happen transparently. The VM tests already do this.

--js
Eduardo Habkost Sept. 25, 2020, 1:18 a.m. UTC | #8
On Mon, Sep 21, 2020 at 10:05:24AM +0200, Markus Armbruster wrote:
[...]
> I sense a certain wobbliness in mypy.  Perhaps to be expected from a

> tool with major version zero.  There's a risk that developers' local

> "make check" and our gating CI differ too much.  We'll see.


This possibility shouldn't exist.  If you don't have the required
version in your system, the validation script must know how to
create a virtualenv and install the right version inside it.

-- 
Eduardo
diff mbox series

Patch

diff --git a/scripts/qapi/doc.py b/scripts/qapi/doc.py
index cbf7076ed9..70f7cdfaa6 100644
--- a/scripts/qapi/doc.py
+++ b/scripts/qapi/doc.py
@@ -5,7 +5,8 @@ 
 """This script produces the documentation of a qapi schema in texinfo format"""
 
 import re
-from .gen import QAPIGenDoc, QAPISchemaVisitor
+from .gen import QAPIGenDoc
+from .schema import QAPISchemaVisitor
 
 
 MSG_FMT = """
diff --git a/scripts/qapi/mypy.ini b/scripts/qapi/mypy.ini
new file mode 100644
index 0000000000..a0f2365a53
--- /dev/null
+++ b/scripts/qapi/mypy.ini
@@ -0,0 +1,65 @@ 
+[mypy]
+strict = True
+strict_optional = False
+disallow_untyped_calls = False
+python_version = 3.6
+
+[mypy-qapi.commands]
+disallow_untyped_defs = False
+disallow_incomplete_defs = False
+check_untyped_defs = False
+
+[mypy-qapi.doc]
+disallow_subclassing_any = False
+disallow_untyped_defs = False
+disallow_incomplete_defs = False
+
+[mypy-qapi.error]
+disallow_untyped_defs = False
+disallow_incomplete_defs = False
+check_untyped_defs = False
+
+[mypy-qapi.events]
+disallow_untyped_defs = False
+disallow_incomplete_defs = False
+check_untyped_defs = False
+
+[mypy-qapi.expr]
+disallow_untyped_defs = False
+disallow_incomplete_defs = False
+check_untyped_defs = False
+
+[mypy-qapi.gen]
+disallow_untyped_defs = False
+disallow_incomplete_defs = False
+check_untyped_defs = False
+
+[mypy-qapi.introspect]
+disallow_untyped_defs = False
+disallow_incomplete_defs = False
+check_untyped_defs = False
+
+[mypy-qapi.parser]
+disallow_untyped_defs = False
+disallow_incomplete_defs = False
+check_untyped_defs = False
+
+[mypy-qapi.schema]
+disallow_untyped_defs = False
+disallow_incomplete_defs = False
+check_untyped_defs = False
+
+[mypy-qapi.source]
+disallow_untyped_defs = False
+disallow_incomplete_defs = False
+check_untyped_defs = False
+
+[mypy-qapi.types]
+disallow_untyped_defs = False
+disallow_incomplete_defs = False
+check_untyped_defs = False
+
+[mypy-qapi.visit]
+disallow_untyped_defs = False
+disallow_incomplete_defs = False
+check_untyped_defs = False
diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
index b4921b46c9..bb0cd717f1 100644
--- a/scripts/qapi/schema.py
+++ b/scripts/qapi/schema.py
@@ -17,6 +17,7 @@ 
 import os
 import re
 from collections import OrderedDict
+from typing import Optional
 
 from .common import c_name, POINTER_SUFFIX
 from .error import QAPIError, QAPISemError
@@ -25,7 +26,7 @@ 
 
 
 class QAPISchemaEntity:
-    meta = None
+    meta: Optional[str] = None
 
     def __init__(self, name, info, doc, ifcond=None, features=None):
         assert name is None or isinstance(name, str)