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

Add support for namedtuple methods (issue #1076) #1810

Merged
merged 85 commits into from
Sep 2, 2016
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
ecca142
namedtuple: add (shaky) support for _replace, _asdict + naive test
elazarg Jul 6, 2016
8ef445f
namedtuple: add tests and refine types
elazarg Jul 6, 2016
5e429cd
minor cleanup
elazarg Jul 6, 2016
0214e3c
minor cleanup
elazarg Jul 6, 2016
fbb75c3
flake8-ing
elazarg Jul 6, 2016
17d7f8b
fix type errors
elazarg Jul 6, 2016
6dd370c
fix type errors
elazarg Jul 6, 2016
1370fbf
Merge branch 'namedtuple' of https://github.com/elazarg/mypy into nam…
elazarg Jul 6, 2016
560eb3e
_fields will not be supported
elazarg Jul 6, 2016
2627511
slight refactoring
elazarg Jul 6, 2016
46475d4
restore tests for _replace and _asdict
elazarg Jul 6, 2016
c1dc1c7
NamedTupleTypeInfo
elazarg Jul 9, 2016
763e0c0
run both Replace and AsTuple
elazarg Jul 10, 2016
4b10a35
fix most of the tests
elazarg Jul 10, 2016
ee047f8
complete support for _replace
elazarg Jul 10, 2016
725d53c
flake8-ing
elazarg Jul 10, 2016
044e640
Add partial support for calling '_replace' statically
elazarg Jul 12, 2016
eb6534e
fix method name
elazarg Aug 11, 2016
aa0d5da
namedtuple tests pass. list has problems
elazarg Aug 14, 2016
e12bd01
tests pass. OrderedDict unsupported. cleanup still required
elazarg Aug 14, 2016
dc9d8da
fix line endings
elazarg Aug 14, 2016
158b068
flake8-ing
elazarg Aug 14, 2016
90ffb08
s/self_type/with_fallback. Fix visitor type
elazarg Aug 14, 2016
9a6e6cd
uniform copy_with. typechecks.
elazarg Aug 14, 2016
76d709e
uniform copy_with. typechecks.
elazarg Aug 14, 2016
20bf222
rename tests to startwith NamedTuple
elazarg Aug 15, 2016
a2696c2
Read-only attributes. Can't set new attributes in subclasses yet
elazarg Aug 15, 2016
9e1b866
update semantic namedtuple repr name
elazarg Aug 15, 2016
f7b531f
support _fields
elazarg Aug 15, 2016
67bdafe
support _source. test for unit namedtuple
elazarg Aug 15, 2016
39bce74
support _asdict()->OrderedDict as an UnboundType
elazarg Aug 15, 2016
a2db6a0
minor cleanup
elazarg Aug 15, 2016
8784ce0
support _make and _field_types
elazarg Aug 16, 2016
191de7c
namedtuple: add (shaky) support for _replace, _asdict + naive test
elazarg Jul 6, 2016
6d2d6d1
namedtuple: add tests and refine types
elazarg Jul 6, 2016
8819af9
minor cleanup
elazarg Jul 6, 2016
9fb5137
flake8-ing
elazarg Jul 6, 2016
35c30a2
fix type errors
elazarg Jul 6, 2016
a642a7f
_fields will not be supported
elazarg Jul 6, 2016
c505cbc
slight refactoring
elazarg Jul 6, 2016
9f96ca7
restore tests for _replace and _asdict
elazarg Jul 6, 2016
5c2d3ac
NamedTupleTypeInfo
elazarg Jul 9, 2016
41ed075
run both Replace and AsTuple
elazarg Jul 10, 2016
6dd568f
fix most of the tests
elazarg Jul 10, 2016
b18fc25
complete support for _replace
elazarg Jul 10, 2016
d7dbd3f
flake8-ing
elazarg Jul 10, 2016
2b67380
Add partial support for calling '_replace' statically
elazarg Jul 12, 2016
525c081
fix method name
elazarg Aug 11, 2016
15acfe0
namedtuple tests pass. list has problems
elazarg Aug 14, 2016
fb9ba7f
tests pass. OrderedDict unsupported. cleanup still required
elazarg Aug 14, 2016
99635c5
fix line endings
elazarg Aug 14, 2016
8f61c13
flake8-ing
elazarg Aug 14, 2016
ff21b0c
s/self_type/with_fallback. Fix visitor type
elazarg Aug 14, 2016
4c1bfe4
uniform copy_with. typechecks.
elazarg Aug 14, 2016
b0d78ba
uniform copy_with. typechecks.
elazarg Aug 14, 2016
87c804c
rename tests to startwith NamedTuple
elazarg Aug 15, 2016
6adc2f9
Read-only attributes. Can't set new attributes in subclasses yet
elazarg Aug 15, 2016
7ed7b0e
update semantic namedtuple repr name
elazarg Aug 15, 2016
c7dc9a7
support _fields
elazarg Aug 15, 2016
b39925b
support _source. test for unit namedtuple
elazarg Aug 15, 2016
030307e
support _asdict()->OrderedDict as an UnboundType
elazarg Aug 15, 2016
12d3407
minor cleanup
elazarg Aug 15, 2016
1f944fc
support _make and _field_types
elazarg Aug 16, 2016
7825f79
Merge branch 'namedtuple'
elazarg Aug 16, 2016
8a0ce40
merge upstream
elazarg Aug 16, 2016
7978357
merge fix except Awaitable lookup
elazarg Aug 16, 2016
badc3f9
merge fix
elazarg Aug 16, 2016
c051f3f
Merge remote
elazarg Aug 17, 2016
44c124b
Merge branch 'master' into namedtuple
elazarg Aug 17, 2016
d067c9e
fix syntax error
elazarg Aug 17, 2016
42342de
Merge branch 'master' into namedtuple
elazarg Aug 18, 2016
a5ccc50
join/meet namedtuples as namedtuple only if attrs are equal
elazarg Aug 20, 2016
eaf4b99
join/meet namedtuples as namedtuple only if attrs are equal. add tests.
elazarg Aug 20, 2016
9829b25
Fix conflicts:
elazarg Aug 21, 2016
b1988f8
Merge branch 'master' into namedtuple
elazarg Aug 28, 2016
8ced59e
Merge remote-tracking branch 'upstream/master' into namedtuple
elazarg Aug 28, 2016
053ee56
remove NamedTupleType
elazarg Aug 30, 2016
94bb8d0
Merge remote-tracking branch 'upstream/master' into namedtuple
elazarg Aug 30, 2016
71f0d78
restore semanal test
elazarg Aug 30, 2016
66a77fb
fix signatures
elazarg Sep 1, 2016
922dd91
_asdict() -> Dict
elazarg Sep 1, 2016
6460839
_asdict() -> OrderedDict
elazarg Sep 1, 2016
cd5b933
use buitlins.dict; rearrange build_namedtuple_typeinfo
elazarg Sep 1, 2016
4b93761
Merge remote-tracking branch 'upstream/master' into namedtuple
elazarg Sep 1, 2016
500a847
remove unneeded 'class dict' from proprty.pyi
elazarg Sep 1, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 3 additions & 2 deletions mypy/join.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,8 +231,9 @@ def visit_tuple_type(self, t: TupleType) -> Type:
items = [] # type: List[Type]
for i in range(t.length()):
items.append(self.join(t.items[i], self.s.items[i]))
# TODO: What if the fallback types are different?
return TupleType(items, t.fallback)
# join fallback types if they are different
from typing import cast
return TupleType(items, cast(Instance, join_instances(self.s.fallback, t.fallback)))
else:
return self.default(self.s)

Expand Down
5 changes: 3 additions & 2 deletions mypy/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -2027,8 +2027,9 @@ def deserialize(cls, data: JsonDict) -> 'TypeInfo':
return ti


def namedtuple_type_info(tup: 'mypy.types.TupleType', *args) -> TypeInfo:
info = TypeInfo(*args)
def namedtuple_type_info(tup: 'mypy.types.TupleType',
names: 'SymbolTable', defn: ClassDef) -> TypeInfo:
info = TypeInfo(names, defn)
info.tuple_type = tup
info.bases = [tup.fallback]
info.is_named_tuple = True
Expand Down
76 changes: 39 additions & 37 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,6 @@
traverse the entire AST.
"""

from functools import partial

import sys
from typing import (
List, Dict, Set, Tuple, cast, Any, TypeVar, Union, Optional, Callable
)
Expand All @@ -62,7 +59,7 @@
SymbolTableNode, BOUND_TVAR, UNBOUND_TVAR, ListComprehension, GeneratorExpr,
FuncExpr, MDEF, FuncBase, Decorator, SetExpr, TypeVarExpr, NewTypeExpr,
StrExpr, BytesExpr, PrintStmt, ConditionalExpr, PromoteExpr,
ComparisonExpr, StarExpr, ARG_POS, ARG_NAMED, ARG_OPT, MroError, type_aliases,
ComparisonExpr, StarExpr, ARG_POS, ARG_NAMED, MroError, type_aliases,
YieldFromExpr, NamedTupleExpr, NonlocalDecl,
SetComprehension, DictionaryComprehension, TYPE_ALIAS, TypeAliasExpr,
YieldExpr, ExecStmt, Argument, BackquoteExpr, ImportBase, AwaitExpr,
Expand All @@ -75,9 +72,8 @@
from mypy.errors import Errors, report_internal_error
from mypy.types import (
NoneTyp, CallableType, Overloaded, Instance, Type, TypeVarType, AnyType,
FunctionLike, UnboundType, TypeList, TypeVarDef, Void,
replace_leading_arg_type, TupleType, UnionType, StarType, EllipsisType
)
FunctionLike, UnboundType, TypeList, TypeVarDef,
replace_leading_arg_type, TupleType, UnionType, StarType, EllipsisType, TypeType)
from mypy.nodes import function_type, implicit_module_attrs
from mypy.typeanal import TypeAnalyser, TypeAnalyserPass3, analyze_type_alias
from mypy.exprtotype import expr_to_unanalyzed_type, TypeTranslationError
Expand Down Expand Up @@ -319,9 +315,8 @@ def visit_func_def(self, defn: FuncDef) -> None:
# A coroutine defined as `async def foo(...) -> T: ...`
# has external return type `Awaitable[T]`.
defn.type = defn.type.copy_modified(
ret_type=Instance(
self.named_type_or_none('typing.Awaitable').type,
[defn.type.ret_type]))
ret_type=self.external_instance('typing.Awaitable',
[defn.type.ret_type]))
self.errors.pop_function()

def prepare_method_signature(self, func: FuncDef) -> None:
Expand Down Expand Up @@ -874,6 +869,9 @@ def named_type_or_none(self, qualified_name: str) -> Instance:
return None
return Instance(cast(TypeInfo, sym.node), [])

def external_instance(self, qualified_name: str, args: List[Type] = None) -> Instance:
return Instance(self.named_type_or_none(qualified_name).type, args or None)
Copy link
Collaborator

Choose a reason for hiding this comment

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

The second argument shouldn't be None -- it should be an empty list if there are no arguments.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oops.


def is_instance_type(self, t: Type) -> bool:
return isinstance(t, Instance)

Expand Down Expand Up @@ -1642,7 +1640,7 @@ def parse_namedtuple_args(self, call: CallExpr,
if (fullname == 'collections.namedtuple'
and isinstance(args[1], (StrExpr, BytesExpr, UnicodeExpr))):
str_expr = cast(StrExpr, args[1])
items = str_expr.value.split()
items = str_expr.value.replace(',', ' ').split()
else:
return self.fail_namedtuple_arg(
"List literal expected as the second argument to namedtuple()", call)
Expand Down Expand Up @@ -1696,51 +1694,53 @@ def build_namedtuple_typeinfo(self, name: str, items: List[str],
tup = TupleType(types, self.named_type('__builtins__.tuple', types))
class_def = ClassDef(name, Block([]))
class_def.fullname = self.qualified_name(name)

info = namedtuple_type_info(tup, symbols, class_def)

vars = [Var(item, typ) for item, typ in zip(items, types)]
this_type = self_type(info)
add_field = partial(self.add_namedtuple_field, symbols, info)
strtype = self.named_type('__builtins__.str')
strtype = self.named_type('__builtins__.str') # type: Type

def add_field(var: Var, is_initialized_in_class: bool = False,
is_property: bool = False) -> None:
var.info = info
var.is_initialized_in_class = is_initialized_in_class
var.is_property = is_property
symbols[var.name()] = SymbolTableNode(MDEF, var)

for var in vars:
add_field(var, is_property=True)

tuple_of_strings = TupleType([strtype for _ in items],
self.named_type('__builtins__.tuple', [AnyType()]))
add_field(Var('_fields', tuple_of_strings),
is_initialized_in_class=True)
add_field(Var('_field_types', UnboundType('Dict', [strtype, AnyType()])),
is_initialized_in_class=True)
add_field(Var('_source', strtype),
is_initialized_in_class=True)

add_method = partial(self.add_namedtuple_method, symbols, info, this_type)
# TODO: refine type of values
dictype = self.external_instance('typing.Dict', [strtype, AnyType()])
Copy link
Collaborator

Choose a reason for hiding this comment

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

This should probably be builtins.dict. Internally the type is defined in builtins and typing only has an alias.

add_field(Var('_fields', tuple_of_strings), is_initialized_in_class=True)
add_field(Var('_field_types', dictype), is_initialized_in_class=True)
add_field(Var('_source', strtype), is_initialized_in_class=True)

def add_method(funcname: str, ret: Type, args: List[Argument], name=None,
is_classmethod=False) -> None:
self.add_namedtuple_method(symbols, info, this_type,
funcname, ret, args, name, is_classmethod)

add_method('_replace', ret=this_type,
args=self.factory_args(vars, ARG_NAMED, initializer=EllipsisExpr()))
add_method('__init__', ret=NoneTyp(),
args=self.factory_args(vars, ARG_POS), name=info.name())
# TODO: refine to OrderedDict[str, Union[types]]
add_method('_asdict', ret=UnboundType('collections.OrderedDict', is_ret_type=True),
args=[])

# TODO: refine signature
union = UnboundType('Iterable', [UnionType(types)])
# actual signature should return OrderedDict[str, Union[types]]
dictype = self.external_instance('typing.Dict', [strtype, AnyType()])
Copy link
Collaborator

Choose a reason for hiding this comment

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

Use builtins.dict.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

But the lookup fails for it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

(Similarly the lookup for collections fails)

Copy link
Collaborator

@JukkaL JukkaL Sep 1, 2016

Choose a reason for hiding this comment

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

Actually, it seems that collections will only work if you are using collections.namedtuple, not when using typing.NamedTuple. builtins.dict should work if you use a fixture that defines it, as far as I can see. What happens if you try to use it?

The only easy way to make sure that collections is available here seems to be via adding a dummy import collections (or similar) to typing.pyi, but that would also be a little unfortunate as then the minimal set of modules needed for type checking anything would also include collections.pyi. Also, there could be some cyclic import issues that could be painful to resolve, as collections.pyi also depends on typing.pyi and builtins.pyi.

Copy link
Contributor Author

@elazarg elazarg Sep 1, 2016

Choose a reason for hiding this comment

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

That's unfortunate that namedtuple is in collections when it isn't one, and
it must have intrinsic support while it refers to non-builtin collections.

builtins.dict does not work. I don't know why.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I'll play around with the code to see if I can figure out what is going on with builtins.dict.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Adding [builtins fixtures/dict.pyi] to the test case seems to work around the issue after I change the code to this:

        # actual signature should return OrderedDict[str, Union[types]]
        dictype = self.external_instance('builtins.dict', [strtype, AnyType()])

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It does, but it means adding this fixture to every test case (in some, replacing list), regardless of actual calls to _asdict(), and getting strange errors when you forget - even if it's secondary to the test (since there are cases outside check-namedtuple.test).

What's wrong with typing.Dict? It should always be there.

And OrderedDict is appropriate in typing too; we might call it _OrderedDict until it makes its way there. Given PEP 520: Preserving Class Attribute Definition Order and PEP 468: Preserving the order of **kwargs in a function, I'll expect this type to occur more frequently inside the checker in the future.

Copy link
Collaborator

Choose a reason for hiding this comment

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

typing.Dict is not a real type, it's basically a reference that gets mapped to builtins.dict internally in certain places. If there's no builtins.dict then it's basically a dangling reference. The fact that it appears to work is an accident -- it would likely cause trouble elsewhere when you actually try to use it as a type.

Adding OrderedDict to typing is not a bad idea, but it's a separate issue and should be handled in the typing issue tracker, not here or typeshed.

Copy link
Collaborator

Choose a reason for hiding this comment

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

You may not need to have dict in all the fixtures -- you could have a utility function that falls back to object or something if dict is not defined in a fixture, as long as you have at least some test cases that use a stub with dict. Though adding the fixture to all named tuple test cases is totally fine by me as well.

We may want to reconsider adding dict to the default fixtures, but that's a separate issue.

It's unfortunate that you've encountered a bunch of shard edges when working on this PR -- sorry about that!

add_method('_asdict', args=[], ret=dictype)

# note: actual signature should accept an invariant version of Iterable[UnionType[types]].
# but it can't be expressed
# 'new' and 'len' as function types.
iterable_type = self.external_instance('typing.Iterable', [AnyType()])
add_method('_make', ret=this_type, is_classmethod=True,
args=[Argument(Var('iterable', union), union, None, ARG_POS),
args=[Argument(Var('iterable', iterable_type), iterable_type, None, ARG_POS),
Argument(Var('new'), AnyType(), EllipsisExpr(), ARG_NAMED),
Argument(Var('len'), AnyType(), EllipsisExpr(), ARG_NAMED)])
return info

def add_namedtuple_field(self, symbols: SymbolTable, info: TypeInfo, var: Var,
is_initialized_in_class: bool = False,
is_property: bool = False) -> None:
var.info = info
var.is_initialized_in_class = is_initialized_in_class
var.is_property = is_property
symbols[var.name()] = SymbolTableNode(MDEF, var)

def factory_args(self, vars: List[Var], kind: int,
initializer: Expression = None) -> List[Argument]:
return [Argument(var, var.type, initializer, kind) for var in vars]
Expand Down Expand Up @@ -2511,6 +2511,8 @@ def fail(self, msg: str, ctx: Context, serious: bool = False, *,
self.function_stack and
self.function_stack[-1].is_dynamic()):
return
# In case it's a bug and we don't really have context
assert ctx is not None, msg
self.errors.report(ctx.get_line(), msg, blocker=blocker)

def fail_blocker(self, msg: str, ctx: Context) -> None:
Expand Down