python-minifier icon indicating copy to clipboard operation
python-minifier copied to clipboard

wish: remove attribute annotations not generating code

Open wrohdewald opened this issue 2 years ago • 4 comments

var: str

is a legal annotation but it creates no executable code. Would be nice to remove these.

I am using python-minifier for asserting that a commit only changes annotations but not real code. Very helpful for annotating old source code. Or would there be a better tool for this?

wrohdewald avatar Oct 14 '23 14:10 wrohdewald

Hi @wrohdewald, that's an interesting use of python-minifier!

A statement like var: str is an assignment without a value, but it puts the namevar in the local scope the same way var: str = 'foo' would do. You're right it has no effect when executed, but it's presence can affect the behaviour of a program.

Consider this program:

var = 'hello'
def func():
    var: str
    print(var)

When func() is executed it should raise UnboundLocalError because var is in the function namespace but has no value. If we removed the var: str statement it would print 'hello' instead.

What python-minifier should be doing is replacing the annotation value with '0', e.g

var = 'hello'
def func():
    var: 0
    print(var)

which should preserve the behaviour but make the annotation shorter.

dflook avatar Oct 14 '23 15:10 dflook

OK, I see why this cannot be safely removed - at least not without a special option. Shortening is not much of a help - the diff output I have to read does not get shorter.

OTOH - what about code under if TYPE_CHECKING - if and only if TYPE_CHECKING is the original typing.TYPE_CHECKING - I see no reason why this could not be safely removed.

Like in

from typing import TYPE_CHECKING

b = cast(int, 1)
reveal_type(b)

if TYPE_CHECKING:
        a = 5

assert 'b' in globals()
assert 'a' not in globals()

expecting

b=1
assert'b'in globals()
assert'a'not in globals()

typing.cast() would be another candidate. reveal_type() too - it is only recognized by mypy.

wrohdewald avatar Oct 28 '23 12:10 wrohdewald

Hi @wrohdewald, that's a good idea

dflook avatar Oct 28 '23 15:10 dflook

This works for me, but I do not know enough about ast to finish this. Like when somebody renames or redefines TYPE_CHECKING or cast()

class RemoveAnnotations(SuiteTransformer):
   ...
    def visit_If(self, node):
        if hasattr(node.test, 'id') and node.test.id == 'TYPE_CHECKING':
            return ast.Pass()
        return node

    def visit_ImportFrom(self, node):
        if node.module == 'typing':
            return ast.Pass()
        return node

   def visit_Call(self, node):
        if hasattr(node, 'func'):
            if hasattr(node.func, 'id'):
                if node.func.id == 'cast' and len(node.args) == 2:
                    return node.args[1]
        return node

wrohdewald avatar Oct 28 '23 17:10 wrohdewald