Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Function calls (and other shenanigans) inside isinstance() calls #9

Open
Tirasz opened this issue Apr 10, 2022 · 1 comment
Open

Function calls (and other shenanigans) inside isinstance() calls #9

Tirasz opened this issue Apr 10, 2022 · 1 comment
Labels
bug Something isn't working

Comments

@Tirasz
Copy link
Owner

Tirasz commented Apr 10, 2022

While testing, i came accros something like this:

if isinstance(obj, type(obj2)):
   something()
elif isinstance(obj, type(obj3)):
   something_else()
elif isinstance(obj, SomeClass):
   some_function()

That got turned into:

match obj:
   case type(obj2)():
       something()
   case type(obj3)():
       something_else()
   case SomeClass():
       some_function()

The produced code has syntax errors, so the transformer resets the transformation, but still, this is an obvious oversight.
Im not exactly sure whats going on here, but the interpreter sometimes throws a TypeError, sometimes doesnt, when i try to do something like this:

match obj:
   case type(obj2):
       something()
   case type(obj3):
       something_else()
   case SomeClass():
       some_function()

image

Other weird things I've seen while testing regarding isinstance calls:

if isinstance(obj, (variable, (Class1, Class2))):
   something()
elif isinstance(obj, tuple(SOME_DICT['something'])):
   something_else()

This gets turned into:

match obj:
   case variable() | (Class1, Class2)():
       something()
   case tuple(SOME_DICT['something'])():
       something_else()
   case 2:
       asd()

Besides causing brain damage to anyone who reads the transformed version (sorry), it also causes a syntax error, so thankfully it never actually gets transformed.
I'm going to be honest, I have no idea what the first branch is even trying to accomplish in the original code, so I'm not suprised the transformer was also confused in this regard.
The second branch is pretty similar to the isinstance(obj, type(var)) case mentioned before. I'm pretty sure pattern matching cannot support dynamic classpatterns, so the solution i propose is:

ClassPattern should only accept isinstance calls, where its second argument is either a tuple of Name nodes, or a single Name node.
I'm a bit worried that it might be a little too strict, but i definitely cannot transform isinstance calls where the second argument is (or contains) a Call node (type(var)), or a Subscript node (SOME_DICT['something']), etc..
As soon as i figure out what this type of isinstance call does: isinstance(obj, (Class1, (Class2, Class3))) then i might be able to handle nested tuples as the second argument for an isinstance call.

@Tirasz Tirasz added the bug Something isn't working label Apr 10, 2022
@Tirasz
Copy link
Owner Author

Tirasz commented Apr 11, 2022

So first of all, I'm almost certain that this: isinstance(obj, (Class1, (Class2, Class3))) means the same thing as this:
isinstance(obj, (Class1, Class2, Class3)).

Second of all, limiting the possible arguments to only Name nodes is definitively too strict, since classes used from imported modules are represented by Attribute nodes in the ast. Example:

import some_module 
from other_module import OtherClass

tuple_of_classes = (str, int, OtherClass, some_module.SomeClass)

if isinstance(obj, some_module.SomeClass): 
# Second argument is an ast.Attribute(attr='SomeClass', value=ast.Name('some_module'))
    pass
elif isinstance(obj, OtherClass):
# Second argument is an ast.Name('OtherClass')
    pass
elif isinstance(obj, tuple_of_classes):
# Second argument is an ast.Name('tuple_of_classes')
    pass

I think the problem is clear: in the example above, the first 2 branches could be transformed, even though one of them is contains an Attribute node, and the third branch only contains a Name node, yet it cannot be transformed into a valid ClassPattern.
Right now if i try to transform the code it produces:

match obj:
    case some_module.SomeClass(): # Valid ClassPattern
        pass
    case Other_Class(): # Also Valid
        pass
    case tuple_of_classes(): # Throws: "TypeError: called match pattern must be a type"
        pass

The root of the problem is, is that it doesnt throw a SyntaxError, in other words, i cannot detect this by only looking at the source code, the error only occurs in runtime.
I could maybe infer it by looking at how the Name node is written. For example, if its written in snake_case its probably a variable, not a class. But this is only guessing, so i dont think i can actually guarantee valid ClassPattern transformation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

1 participant