8000 Turn `literal` and `literal_hash` into functions by elazarg · Pull Request #3071 · python/mypy · GitHub
[go: up one dir, main page]

Skip to content

Turn literal and literal_hash into functions #3071

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

Merged
merged 17 commits into from
Aug 30, 2017

Conversation

elazarg
Copy link
Contributor
@elazarg elazarg commented Mar 28, 2017

This PR groups the handling of literal/literal_hash in one place, and removes the auxiliary attributes. I think it is clearer this way, and removes clutter from nodes.py. It is easier to check for consistency (e.g. serialization is not possible at all).

I don't know what is the impact performance-wise; in general it should reduce memory footprint for AST nodes (many can become NamedTuple now), which might have some cost in execution time. If it is noticeable, I guess lru_cache can eliminate that.

The functions can be moved to a different module (binder.py?) but they curry dependencies with them.

@elazarg
Copy link
Contributor Author
elazarg commented Mar 28, 2017

The idea has been discussed briefly in #2238.

@elazarg
Copy link
Contributor Author
elazarg commented Mar 28, 2017

If it means anything, the tests take the same time to execute.

Copy link
Member
@gvanrossum gvanrossum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm honestly not sure this is worth doing. Leaving the decision up to Jukka (who may also have PRs in flight that would require rebasing against this).

@@ -21,7 +21,7 @@
DictionaryComprehension, ComplexExpr, EllipsisExpr, StarExpr, AwaitExpr, YieldExpr,
YieldFromExpr, TypedDictExpr, PromoteExpr, NewTypeExpr, NamedTupleExpr, TypeVarExpr,
TypeAliasExpr, BackquoteExpr, ARG_POS, ARG_NAMED, ARG_STAR, ARG_STAR2, MODULE_REF,
UNBOUND_TVAR, BOUND_TVAR, LITERAL_TYPE
UNBOUND_TVAR, BOUND_TVAR, LITERAL_TYPE, literal
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trailing comma.

mypy/binder.py Outdated
@@ -2,7 +2,7 @@
from contextlib import contextmanager

from mypy.types import Type, AnyType, PartialType
from mypy.nodes import (Key, Node, Expression, Var, RefExpr, SymbolTableNode)
from mypy.nodes import (Key, Expression, Var, RefExpr, literal, literal_hash)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While you're at it drop the parentheses.

mypy/checker.py Outdated
@@ -21,7 +21,7 @@
Context, Decorator, PrintStmt, LITERAL_TYPE, BreakStmt, PassStmt, ContinueStmt,
ComparisonExpr, StarExpr, EllipsisExpr, RefExpr, ImportFrom, ImportAll, ImportBase,
ARG_POS, CONTRAVARIANT, COVARIANT, ExecStmt, GlobalDecl, Import, NonlocalDecl,
MDEF, Node
MDEF, Node, literal, literal_hash
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add trailing comma.

mypy/nodes.py Outdated
# of an index expression, or the operands of an operator expression).


def literal_hash(e: Expression) -> Key:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just begging to become a visitor.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will use a visitor if the PR will be accepted.
(I don't know why exactly, but it feels cleaner this way. OTOH a visitor might help catching non-exhaustive match)

mypy/nodes.py Outdated
return None


def literal(e: Expression) -> int:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(This less so.)

mypy/semanal.py Outdated
@@ -66,7 +66,7 @@
YieldExpr, ExecStmt, Argument, BackquoteExpr, ImportBase, AwaitExpr,
IntExpr, FloatExpr, UnicodeExpr, EllipsisExpr, TempNode,
COVARIANT, CONTRAVARIANT, INVARIANT, UNBOUND_IMPORTED, LITERAL_YES, ARG_OPT, nongen_builtins,
collections_type_aliases, get_member_expr_fullname,
collections_type_aliases, get_member_expr_fullname, literal
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trailing comma.

@JukkaL
Copy link
Collaborator
JukkaL commented Apr 25, 2017

Can you fix the merge conflicts?

@elazarg
Copy link
Contributor Author
elazarg commented Apr 25, 2017

(Fixed online, I hope it'll pass)

mypy/nodes.py Outdated
@@ -155,9 +155,6 @@ def accept(self, visitor: StatementVisitor[T]) -> T:

class Expression(Node):
"""An expression node."""
literal = LITERAL_NO
literal_hash = None # type: Key
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ilevkivskyi isn't this a violation of strict optional? This value is actually read, it's not just a declaration. I assume this should be Optional[Key]?
(This is why the build for this PR breaks; the new literal_hash() returns None when there's no match, which is what the client code expects).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an exception for historical reasons. Before PEP 526 there was no way do define an instance variable without a default value x: int. Therefore it is allowed to write this even with --strict-optional (the same code should fail at module and function scopes).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And yes, if this is not a declaration, then you should use explicitly Optional[Key] here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. I will open a PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JukkaL
Copy link
Collaborator
JukkaL commented Jul 21, 2017

The general idea of this factoring looks good to me -- it moves spread-out special-purpose logic away from node classes to a single location. This makes it easier to verify that the literal hash logic is consistent. I prefer node classes to be (at least mostly) pure data. This should also reduce memory use somewhat.

Some things are needed:

  • Fix merge conflicts.
  • We need to make sure that this doesn't significantly slow down type checking. I can test the PR against Dropbox internal codebases once you've fixed merge conflicts. Performance is currently one of the top issues at Dropbox so we'll prefer retaining minor code quality problems over slowing things down.
  • Use visitor for literal_hash. This might even be necessary to avoid a performance hit.

I will do a detailed review once the above things have been resolved.

Copy link
Collaborator
@JukkaL JukkaL left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Explicitly requesting the above changes so that the review status is shown correctly in the PR list.

@elazarg
Copy link
Contributor Author
elazarg commented Jul 21, 2017

I will make the visitor later, it's much more verbose (code duplication and implementing empty methods) so I'll try to see the cleanest way to do it.

In the meantime, is it possible to check the performance on against the internal codebase before that? I am not sure that a visitor will be faster, since it adds a method call (or two) at each level, and several cases are grouped to a single isinstance check.

@ilevkivskyi
Copy link
Member

@elazarg

and several cases are grouped to a single isinstance check.

isinstance() can be quite slow, especially with ABCs, and we have some of them in mypy.

@elazarg
Copy link
Contributor Author
elazarg commented Jul 21, 2017

There are no checks against ABCs in this code.

@elazarg
Copy link
Contributor Author
elazarg commented Aug 10, 2017

@JukkaL did you have the opportunity to test the performance implications of this PR?

@JukkaL
Copy link
Collaborator
JukkaL commented Aug 10, 2017

I'm now back from vacation but I'll have more vacation starting a week from now. I may be able to check performance next week, but not sure yet.

Copy link
Collaborator
@JukkaL JukkaL left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the updates! Looks good now (just a few minor comments). I verified the performance with internal Dropbox repos and mypy actually seems slightly faster with this PR than before, which is excellent. Memory should be lower as well, but I didn't check it. The suggested changes are optional except for the IndexExpr thing.

mypy/nodes.py Outdated

elif isinstance(e, IndexExpr):
if literal(e.index) == LITERAL_YES:
return literal(e.base)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add else where we return LITERAL_NO. Otherwise, it looks like we can return LITERAL_YES because of the if literal_hash(e): check.

mypy/nodes.py Outdated
return ('Index', literal_hash(e.base), literal_hash(e.index))
return None

def visit_call_expr(self, e: CallExpr) -> None:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe you can have a single method definition and then create a bunch of aliases. Example (not tried):

    def visit_non_literal(self, e: Node) -> None:
        return None

    visit_call_expr = visit_non_literal
    visit_slice_expr = visit_non_literal
    ...

This would reduce some of the verbosity.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This result in Incompatible types in assignment (expression has type Callable[[Node], None], base class "ExpressionVisitor" defined the type as Callable[[CallExpr], Tuple[Any, ...]])
Which kinda looks like a bug to me since the returned value should be defined as Optional[Key].

mypy/nodes.py Outdated
@@ -2559,3 +2470,215 @@ def check_arg_names(names: List[Optional[str]], nodes: List[T], fail: Callable[[
fail("Duplicate argument '{}' in {}".format(name, description), node)
break
seen_names.add(name)

# [Note Literals and literal_hash]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be worth moving all the literal and literal hash specific code and comments to a new module to make mypy.nodes smaller. After all, these aren't relevant for most users of nodes. For example, it could be named mypy.literals.

@JukkaL
Copy link
Collaborator
JukkaL commented Aug 16, 2017

Thanks for the updates! This almost ready to be merged. Can you fix the merge conflicts and the build failures?

@elazarg
Copy link
Contributor Author
elazarg commented Aug 16, 2017

Oops. Sorry about that, I got busy and didn't look at the build results. Just a moment.

@elazarg
Copy link
Contributor Author
elazarg commented Aug 16, 2017

Done

@JukkaL JukkaL merged commit abc228e into python:master Aug 30, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants
0