Skip to content

Spurious "Expression has type Any" error following attribute access #5842

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

Closed
noyainrain opened this issue Oct 26, 2018 · 6 comments · Fixed by #7898
Closed

Spurious "Expression has type Any" error following attribute access #5842

noyainrain opened this issue Oct 26, 2018 · 6 comments · Fixed by #7898
Labels

Comments

@noyainrain
Copy link

As originally described in #5788, I've run into a problem with --disallow-any-expr reporting spurious errors.

Consider the following file any.py:

def access_before_declaration(self) -> None:
    obj = Object('foo')
    obj.value
    x = 1

    reveal_type(x) # Revealed type is 'builtins.int'
    x = x + 1 # Expression has type "Any"

class Object:
    def __init__(self, value: str) -> None:
        self.value = value

def access_after_declaration(self) -> None:
    obj = Object('foo')
    obj.value
    x = 1

    reveal_type(x) # Revealed type is 'builtins.int'
    x = x + 1

Running mypy on it produces:

$ python3 -m mypy --version
mypy 0.641
$ python3 -m mypy --disallow-any-expr any.py
any.py:6: error: Revealed type is 'builtins.int'
any.py:7: error: Expression has type "Any"
any.py:18: error: Revealed type is 'builtins.int'

What we see is that x is correctly identified as int in access_after_declaration, but incorrectly as Any in access_before_declaration. The error is only triggered in very specific circumstances:

  • There is a class defined after the inspected line
  • An attribute of an instance of that class is accessed before the inspected line
@noyainrain
Copy link
Author

noyainrain commented Oct 26, 2018

I set aside some time and followed the debugging technique suggested by @gvanrossum.

First, while experimenting, I changed the code snippet given above a bit to see if it fails for different kinds of operations (yes, it does):

def access_before_declaration(self) -> None:
    obj = Object('foo')
    obj.value
    x = [1]
    print(x[1]) # Expression has type "Any"

# ...

The error is triggered in ExpressionChecker.accept() when examining x[1]:

(Pdb) node.base.name, node.index.value, node.method_type, node.analyzed
('x', 1, Any, None)

Most of the logic for determining the type of the node happens in visit_index_expr_helper(), where first left_type is determined:

...
  mypy/checkexpr.py(3011)accept()
-> typ = node.accept(self)
  mypy/nodes.py(1464)accept()
-> return visitor.visit_index_expr(self)
  mypy/checkexpr.py(2228)visit_index_expr()
-> result = self.visit_index_expr_helper(e)
  mypy/checkexpr.py(2235)visit_index_expr_helper()
-> left_type = self.accept(e.base)
  mypy/checkexpr.py(3011)accept()
-> typ = node.accept(self)
  mypy/nodes.py(1350)accept()
-> return visitor.visit_name_expr(self)
  mypy/checkexpr.py(155)visit_name_expr()
-> result = self.analyze_ref_expr(e)
  mypy/checkexpr.py(163)analyze_ref_expr()
-> result = self.analyze_var_ref(node, e)
> mypy/checkexpr.py(218)analyze_var_ref()
-> return AnyType(TypeOfAny.special_form)

Any is returned here because var.type is None:

(Pdb) var.serialize()
{'.class': 'Var', 'name': 'x', 'fullname': 'x', 'type': None, 'flags': []}

A few steps later, visit_index_expr_helper() will check the getitem operation:

...
  mypy/checkexpr.py(3011)accept()
-> typ = node.accept(self)
  mypy/nodes.py(1464)accept()
-> return visitor.visit_index_expr(self)
  mypy/checkexpr.py(2228)visit_index_expr()
-> result = self.visit_index_expr_helper(e)
  mypy/checkexpr.py(2260)visit_index_expr_helper()
-> result, method_type = self.check_op('__getitem__', left_type, e.index, e)
  mypy/checkexpr.py(2128)check_op()
-> local_errors=self.msg,
  mypy/checkexpr.py(1818)check_op_local_by_name()
-> return self.check_op_local(method, method_type, base_type, arg, context, local_errors)
  mypy/checkexpr.py(1840)check_op_local()
-> callable_name=callable_name, object_type=object_type)
> mypy/checkexpr.py(695)check_call()
-> return (AnyType(TypeOfAny.from_another_any, source_any=callee),

With left_type / base_type / method_type = Any as input, it will figure the overall result
to be Any, resulting in the mentioned error.

To me it seems that the deduction of Any is correct here and the problem is with the input,
i.e. why is the ref a Var with type None?

This is my first look at the code base though, so maybe someone more familiar with the inner
workings of mypy has thoughts if this is the right direction to investigate further or if there are
more promising leads :)

@noyainrain
Copy link
Author

noyainrain commented Oct 23, 2019

Just a small update, this can still be reproduced on the latest mypy:

$ python3 -m mypy --version
mypy 0.740
$ python3 -m mypy --disallow-any-expr any.py
any.py:6: error: Expression has type "Any"
any.py:6: note: Revealed type is 'builtins.int'
any.py:7: error: Expression has type "Any"
any.py:18: note: Revealed type is 'builtins.int'
Found 2 errors in 1 file (checked 1 source file)

(Almost the same result as before, now with an additional Any error for reveal_type(x) in line 6)

FYI, we use --disallow-any-expr for our now completely annotated code base, and this bug triggers dozens of errors. Is there anything we can do to move this issue forward? 🙂

@ilevkivskyi
Copy link
Member

I think there may be a very simple fix: don't emit this error if current node is deferred, i.e. add a not self.chk.current_node_deferred to the long bunch of ands near the end of mypy.checkexpr.ExpressionChecker.accept().

@MasonRemaley
Copy link

Is this expected to still be a problem in mypy 0.560?

The problem code:

class TokenKind(Enum):
	IDENT = 0
	SPECIAL = 1
test = TokenKind.IDENT # error: Expression type contains "Any" (has type "Type[TokenKind]")

@JelleZijlstra
Copy link
Member

@MasonRemaley 0.560 is very old. Could you try a newer release?

@MasonRemaley
Copy link

Huh, I attempted to update to the latest version w/ pip before reporting this, but I must have misunderstand pip's CLI or something. I uninstalled and reinstalled, got version 0.782, and now it works. Thanks! :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants