Pyright: Prevent reassigning types?

Created on 18 Apr 2019  路  16Comments  路  Source: microsoft/pyright

Maybe there is something that I am missing, but with pyright this code:

i = 42
i = "F"

or this:

i = 42
def f() -> str:
    return "F"
i = f()

produces

Found 1 source file
0 errors, 0 warnings

Is reassigning types not enabled in the default configs or are there plans for the future about this?
(Or is this not in the scope of pyright?)
Couldn't find anything about this.

Thanks

enhancement request

Most helpful comment

I think that's an interesting suggestion. Most python programmers would find that overly-restrictive, but I think it might make sense to add a "strict" mode.

All 16 comments

There's nothing wrong with the code above, so Pyright doesn't complain about it.

Perhaps you meant to add type constraints, like the following:

i: int = 42
i = "F"

This will generate an error because you've told Pyright that imust be an int.

Note that the above syntax requires Python 3.6. If you are using an older version of python, you can use comment-based type annotations like this:

i = 42 # type: int
i = "F"

doesn't generate an error even with i: int

Ah, my bad. Yes, I haven't implemented assignment type constraint checks yet. It's one of the next things on my list.

Even once I add that feature though, your original code sample won't generate an error. Pyright will infer that the type of iis meant to be Union[int, str] since you haven't told it otherwise.

Then I would like to propose the idea that:

i = 42
i = "F"

should be flagged as error because pyright can understand the type from the first assignment
or maybe a config to prevent variables in code without a type annotation

For me the main problem Pyright needs to fix is any type of dynamicness and it's on the right track now!

Or a more generic config setting to prevent/disallow Unions

I think that's an interesting suggestion. Most python programmers would find that overly-restrictive, but I think it might make sense to add a "strict" mode.

waiting for the config option:
python.dynamic = False
馃槃

I think this would work only for local variables and function parameters though. In those cases, there's a well-defined order, so the type can be derived from the first assignment.

For instance variables, class variables, globals and non-locals, it's not obvious which assignment should be used in inferring the type. In those cases, I think I'll need to continue to assume that it's the union of all assigned types unless an explicit type annotation is provided. Perhaps in "strict mode", the type checker could complain if no annotation is provided in these cases.

I'll think about it more once I implement the assignment constraints.

I think the strict mode should disable all "assumptions"

So like you said if a variable type isn't obvious then an error will be outputted

Regardless of the mode, the type checker needs to make some assumptions. It's just a question of how restrictive those assumptions should be. For example, if it sees foo = True, should it assume that foo is constrained to be only True, or should it assume that it can be assigned True or False? How about None?

Also, consider the two pieces of code:

foo = "str" if condition() else 3
foo = 3
if conditition():
   foo = "str"

These are logically equivalent and both perfectly legal. Should the second produce an error whereas the first doesn't? Maybe.

Keep in mind that the main motivation for static type checking is to detect errors that will occur at runtime if they aren't corrected. Your code example above is perfectly fine Python code, and it will not cause any errors at runtime. So I think we'd need to consider the value of making the assumptions more restrictive.

I think the strict mode should treat Python like C++/C# code
So foo = True should be treated as a Bool in those languages only being allowed to be True or False
A sane developer would never think of assigning None to a bool

about the two pieces of code:
first example: it's like a ternary operator, I would emit an error, because the type isn't obvious
second example: it's correct to say that foo can't be assigned "str" because it was already assigned an int type

also by disabling Unions with strict mode that "ternary operator" syntax in Python would be completely illegal*

technically it's like writing

foo:Union(the types here) = None
if condition(): foo = "str"
else: foo = 3

*should only be allowed when both the types assigned are the same

The type of the assigned value is obvious in the first example. The result of the ternary operation is of type Union[str, int]. :)

I don't think it's feasible to disable unions in Python code because all of the built-in libraries and most third-party libraries assume that unions are supported. You could avoid using them within your code, but as soon as you import another package and make calls into those modules, you'll run across union types.

Using C++/C# assumptions would be very restrictive indeed. Python isn't a statically-compiled language, so it's not constrained in the same manner as those languages. I'm curious why you think that level of restrictiveness is valuable. If it's not assisting in finding bugs, what value would those additional restrictions provide?

The strict mode I had in mind when I mentioned it above would be more akin to the inference assumptions built in to TypeScript, which are less restrictive than C++/C# but more restrictive than Pyright currently.

Keep in mind that this discussion is about type _inference_. If you want strong type constraints, you can always use explicit type annotations on variables and parameters, and Pyright will enforce those constraints. In C++ and C#, variable and parameter types are typically typed in an explicit manner. (I realize that they support limited forms of inference in the form of "auto" and "var" keywords.)

foo = "str" if condition() else 3
foo = 12837 #noerror 
foo = "str" #noerror

But if Unions are not disabled in strict mode some code could look like this and it would be _strange_ to have reassignment "allowed" only for that "ternary operator" and a programmer would need to find that ternary operation line to understand why it's allowed and which types are allowed (unless the programmer uses VSCode or a specific IDE tooltip showing the type when hovering)

I think a solution would be to emit a warning every time Unions are produced in strict mode, maybe?

The original problem you reported (where pyright wasn't correctly flagging type inconsistencies for variables with a declared type) is now fixed. It's in version 1.0.15, which I just published.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

namoshizun picture namoshizun  路  3Comments

tamuhey picture tamuhey  路  3Comments

giyyapan picture giyyapan  路  3Comments

giyyapan picture giyyapan  路  3Comments

lambdalisue picture lambdalisue  路  3Comments