From 0c03d3f982fe687b20f95fe078831e2cceea7785 Mon Sep 17 00:00:00 2001
From: Jukka Lehtosalo <jukka.lehtosalo@iki.fi>
Date: Fri, 26 May 2017 17:48:03 +0100
Subject: [PATCH 01/11] Refactor plugin system

Prepare for supporting more general plugins, for things other
than just functions.  The new design also makes it easier to
add support for user-defined plugins.
---
 mypy/build.py                      |  4 ++-
 mypy/checker.py                    | 10 ++++--
 mypy/checkexpr.py                  | 17 ++++++----
 mypy/{funcplugins.py => plugin.py} | 54 +++++++++++++++++++++---------
 4 files changed, 59 insertions(+), 26 deletions(-)
 rename mypy/{funcplugins.py => plugin.py} (61%)

diff --git a/mypy/build.py b/mypy/build.py
index f803929a8fdf..41434adf6c79 100644
--- a/mypy/build.py
+++ b/mypy/build.py
@@ -42,6 +42,7 @@
 from mypy.stats import dump_type_stats
 from mypy.types import Type
 from mypy.version import __version__
+from mypy.plugin import DefaultPlugin
 
 
 # We need to know the location of this file to load data, but
@@ -1505,8 +1506,9 @@ def type_check_first_pass(self) -> None:
         if self.options.semantic_analysis_only:
             return
         with self.wrap_context():
+            plugin = DefaultPlugin(self.options.python_version)
             self.type_checker = TypeChecker(manager.errors, manager.modules, self.options,
-                                            self.tree, self.xpath)
+                                            self.tree, self.xpath, plugin)
             self.type_checker.check_first_pass()
 
     def type_check_second_pass(self) -> bool:
diff --git a/mypy/checker.py b/mypy/checker.py
index fcd334edbcce..89730f894d24 100644
--- a/mypy/checker.py
+++ b/mypy/checker.py
@@ -56,6 +56,7 @@
 from mypy.binder import ConditionalTypeBinder, get_declaration
 from mypy.meet import is_overlapping_types
 from mypy.options import Options
+from mypy.plugin import Plugin
 
 from mypy import experiments
 
@@ -127,8 +128,12 @@ class TypeChecker(NodeVisitor[None]):
     # directly or indirectly.
     module_refs = None  # type: Set[str]
 
+    # Plugin that provides special type checking rules for specific library
+    # functions such as open(), etc.
+    plugin = None  # type: Plugin
+
     def __init__(self, errors: Errors, modules: Dict[str, MypyFile], options: Options,
-                 tree: MypyFile, path: str) -> None:
+                 tree: MypyFile, path: str, plugin: Optional[Plugin] = None) -> None:
         """Construct a type checker.
 
         Use errors to report type check errors.
@@ -139,7 +144,8 @@ def __init__(self, errors: Errors, modules: Dict[str, MypyFile], options: Option
         self.tree = tree
         self.path = path
         self.msg = MessageBuilder(errors, modules)
-        self.expr_checker = mypy.checkexpr.ExpressionChecker(self, self.msg)
+        self.plugin = plugin or Plugin(options.python_version)
+        self.expr_checker = mypy.checkexpr.ExpressionChecker(self, self.msg, self.plugin)
         self.scope = Scope(tree)
         self.binder = ConditionalTypeBinder()
         self.globals = tree.names
diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py
index 074438e53761..04d8f18de0e3 100644
--- a/mypy/checkexpr.py
+++ b/mypy/checkexpr.py
@@ -44,7 +44,7 @@
 from mypy.util import split_module_names
 from mypy.typevars import fill_typevars
 from mypy.visitor import ExpressionVisitor
-from mypy.funcplugins import get_function_plugin_callbacks, PluginCallback
+from mypy.plugin import Plugin
 from mypy.typeanal import make_optional_type
 
 from mypy import experiments
@@ -105,17 +105,18 @@ class ExpressionChecker(ExpressionVisitor[Type]):
     type_context = None  # type: List[Optional[Type]]
 
     strfrm_checker = None  # type: StringFormatterChecker
-    function_plugins = None  # type: Dict[str, PluginCallback]
+    plugin = None  # type: Plugin
 
     def __init__(self,
                  chk: 'mypy.checker.TypeChecker',
-                 msg: MessageBuilder) -> None:
+                 msg: MessageBuilder,
+                 plugin: Plugin) -> None:
         """Construct an expression type checker."""
         self.chk = chk
         self.msg = msg
+        self.plugin = plugin
         self.type_context = [None]
         self.strfrm_checker = StringFormatterChecker(self, self.chk, self.msg)
-        self.function_plugins = get_function_plugin_callbacks(self.chk.options.python_version)
 
     def visit_name_expr(self, e: NameExpr) -> Type:
         """Type check a name expression.
@@ -362,8 +363,10 @@ def apply_function_plugin(self,
             for actual in actuals:
                 formal_arg_types[formal].append(arg_types[actual])
                 formal_arg_exprs[formal].append(args[actual])
-        return self.function_plugins[fullname](
-            formal_arg_types, formal_arg_exprs, inferred_ret_type, self.chk.named_generic_type)
+        callback = self.plugin.get_function_hook(fullname)
+        assert callback is not None  # Assume that caller ensure this
+        return callback(formal_arg_types, formal_arg_exprs, inferred_ret_type,
+                        self.chk.named_generic_type)
 
     def check_call_expr_with_callee_type(self, callee_type: Type,
                                          e: CallExpr, callable_name: Optional[str]) -> Type:
@@ -443,7 +446,7 @@ def check_call(self, callee: Type, args: List[Expression],
             if callable_node:
                 # Store the inferred callable type.
                 self.chk.store_type(callable_node, callee)
-            if callable_name in self.function_plugins:
+            if self.plugin.get_function_hook(callable_name):
                 ret_type = self.apply_function_plugin(
                     arg_types, callee.ret_type, arg_kinds, formal_to_actual,
                     args, len(callee.arg_types), callable_name)
diff --git a/mypy/funcplugins.py b/mypy/plugin.py
similarity index 61%
rename from mypy/funcplugins.py
rename to mypy/plugin.py
index b1113ab30ae9..fea751410bf4 100644
--- a/mypy/funcplugins.py
+++ b/mypy/plugin.py
@@ -1,40 +1,62 @@
-"""Plugins that implement special type checking rules for individual functions.
+"""Plugin architecture for custom type checking rules for specific functions, etc.
 
-The plugins infer better types for tricky functions such as "open".
+A plugin can, for example, infer better types for tricky functions such as "open".
 """
 
-from typing import Tuple, Dict, Callable, List
+from typing import Callable, List, Tuple, Optional
 
 from mypy.nodes import Expression, StrExpr
 from mypy.types import Type, Instance, CallableType
 
 
+# Create an Instance given full name of class and type arguments.
+NamedInstanceCallback = Callable[[str, List[Type]], Type]
+
 # A callback that infers the return type of a function with a special signature.
 #
 # A no-op callback would just return the inferred return type, but a useful callback
 # at least sometimes can infer a more precise type.
-PluginCallback = Callable[
+FunctionHook = Callable[
     [
         List[List[Type]],        # List of types caller provides for each formal argument
         List[List[Expression]],  # Actual argument expressions for each formal argument
         Type,                    # Return type for call inferred using the regular signature
-        Callable[[str, List[Type]], Type]  # Callable for constructing a named instance type
+        NamedInstanceCallback    # Callable for constructing a named instance type
     ],
     Type  # Return type inferred by the callback
 ]
 
 
-def get_function_plugin_callbacks(python_version: Tuple[int, int]) -> Dict[str, PluginCallback]:
-    """Return all available function plugins for a given Python version."""
-    if python_version[0] == 3:
-        return {
-            'builtins.open': open_callback,
-            'contextlib.contextmanager': contextmanager_callback,
-        }
-    else:
-        return {
-            'contextlib.contextmanager': contextmanager_callback,
-        }
+class Plugin:
+    """Base class of type checker plugins.
+
+    This defines a no-op plugin.  Subclasses can override some methods to
+    provide some actual functionality.
+
+    All get_ methods are treated as pure functions (you should assume that
+    results might be cached).
+    """
+
+    # TODO: Way of chaining multiple plugins
+
+    def __init__(self, python_version: Tuple[int, int]) -> None:
+        self.python_version = python_version
+
+    def get_function_hook(self, fullname: str) -> Optional[FunctionHook]:
+        return None
+
+    # TODO: method / metaclass / class decorator hooks
+
+
+class DefaultPlugin(Plugin):
+    """Type checker plugin that is enabled by default."""
+
+    def get_function_hook(self, fullname: str) -> Optional[FunctionHook]:
+        if fullname == 'contextlib.contextmanager':
+            return contextmanager_callback
+        elif fullname == 'builtins.open' and self.python_version[0] == 3:
+            return open_callback
+        return None
 
 
 def open_callback(

From d82aa69ad9d588d3cf11a41251426b5947b2b969 Mon Sep 17 00:00:00 2001
From: Jukka Lehtosalo <jukka.lehtosalo@iki.fi>
Date: Mon, 5 Jun 2017 16:37:21 +0100
Subject: [PATCH 02/11] Special case type checking of TypedDict get and
 int.__pow__

Implement a general-purpose way of extending type inference of
methods.
---
 mypy/checkexpr.py                       |  82 +++++++++++++------
 mypy/nodes.py                           |   6 ++
 mypy/plugin.py                          | 104 ++++++++++++++++++++++--
 test-data/unit/check-expressions.test   |  17 ++++
 test-data/unit/check-typeddict.test     |  27 +++++-
 test-data/unit/fixtures/ops.pyi         |   1 +
 test-data/unit/fixtures/typing-full.pyi |   6 +-
 7 files changed, 209 insertions(+), 34 deletions(-)

diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py
index 04d8f18de0e3..941243bf5988 100644
--- a/mypy/checkexpr.py
+++ b/mypy/checkexpr.py
@@ -209,11 +209,20 @@ def visit_call_expr(self, e: CallExpr, allow_none_return: bool = False) -> Type:
                 isinstance(callee_type, CallableType)
                 and callee_type.implicit):
             return self.msg.untyped_function_call(callee_type, e)
+        object_type = None
         if not isinstance(e.callee, RefExpr):
             fullname = None
         else:
             fullname = e.callee.fullname
-        ret_type = self.check_call_expr_with_callee_type(callee_type, e, fullname)
+            if (fullname is None
+                    and isinstance(e.callee, MemberExpr)):
+                callee_expr_type = self.chk.type_map.get(e.callee.expr)
+                if isinstance(callee_expr_type, TypedDictType):
+                    info = callee_expr_type.fallback.type.get_containing_type_info(e.callee.name)
+                    if info:
+                        fullname = '{}.{}'.format(info.fullname(), e.callee.name)
+                        object_type = callee_expr_type
+        ret_type = self.check_call_expr_with_callee_type(callee_type, e, fullname, object_type)
         if isinstance(ret_type, UninhabitedType):
             self.chk.binder.unreachable()
         if not allow_none_return and isinstance(ret_type, NoneTyp):
@@ -352,7 +361,8 @@ def apply_function_plugin(self,
                               formal_to_actual: List[List[int]],
                               args: List[Expression],
                               num_formals: int,
-                              fullname: Optional[str]) -> Type:
+                              fullname: Optional[str],
+                              object_type: Optional[Type]) -> Type:
         """Use special case logic to infer the return type for of a particular named function.
 
         Return the inferred return type.
@@ -363,13 +373,22 @@ def apply_function_plugin(self,
             for actual in actuals:
                 formal_arg_types[formal].append(arg_types[actual])
                 formal_arg_exprs[formal].append(args[actual])
-        callback = self.plugin.get_function_hook(fullname)
-        assert callback is not None  # Assume that caller ensure this
-        return callback(formal_arg_types, formal_arg_exprs, inferred_ret_type,
-                        self.chk.named_generic_type)
-
-    def check_call_expr_with_callee_type(self, callee_type: Type,
-                                         e: CallExpr, callable_name: Optional[str]) -> Type:
+        if object_type is None:
+            callback = self.plugin.get_function_hook(fullname)
+            assert callback is not None  # Assume that caller ensure this
+            return callback(formal_arg_types, formal_arg_exprs, inferred_ret_type,
+                            self.chk.named_generic_type)
+        else:
+            callback = self.plugin.get_method_hook(fullname)
+            assert callback is not None  # Assume that caller ensure this
+            return callback(object_type, formal_arg_types, formal_arg_exprs, inferred_ret_type,
+                            self.chk.named_generic_type)
+
+    def check_call_expr_with_callee_type(self,
+                                         callee_type: Type,
+                                         e: CallExpr,
+                                         callable_name: Optional[str],
+                                         object_type: Optional[Type]) -> Type:
         """Type check call expression.
 
         The given callee type overrides the type of the callee
@@ -377,14 +396,16 @@ def check_call_expr_with_callee_type(self, callee_type: Type,
         """
         return self.check_call(callee_type, e.args, e.arg_kinds, e,
                                e.arg_names, callable_node=e.callee,
-                               callable_name=callable_name)[0]
+                               callable_name=callable_name,
+                               object_type=object_type)[0]
 
     def check_call(self, callee: Type, args: List[Expression],
                    arg_kinds: List[int], context: Context,
                    arg_names: List[str] = None,
                    callable_node: Expression = None,
                    arg_messages: MessageBuilder = None,
-                   callable_name: Optional[str] = None) -> Tuple[Type, Type]:
+                   callable_name: Optional[str] = None,
+                   object_type: Optional[Type] = None) -> Tuple[Type, Type]:
         """Type check a call.
 
         Also infer type arguments if the callee is a generic function.
@@ -392,14 +413,18 @@ def check_call(self, callee: Type, args: List[Expression],
         Return (result type, inferred callee type).
 
         Arguments:
-          callee: type of the called value
-          args: actual argument expressions
-          arg_kinds: contains nodes.ARG_* constant for each argument in args
-            describing whether the argument is positional, *arg, etc.
-          arg_names: names of arguments (optional)
-          callable_node: associate the inferred callable type to this node,
-            if specified
-          arg_messages: TODO
+            callee: type of the called value
+            args: actual argument expressions
+            arg_kinds: contains nodes.ARG_* constant for each argument in args
+                 describing whether the argument is positional, *arg, etc.
+            arg_names: names of arguments (optional)
+            callable_node: associate the inferred callable type to this node,
+                if specified
+            arg_messages: TODO
+            callable_name: Fully-qualified name of the function/method to call,
+                or None if unavaiable (examples: 'builtins.open', 'typing.Mapping.get')
+            object_type: If callable_name refers to a method, the type of the object
+                on which the method is being called
         """
         arg_messages = arg_messages or self.msg
         if isinstance(callee, CallableType):
@@ -446,10 +471,12 @@ def check_call(self, callee: Type, args: List[Expression],
             if callable_node:
                 # Store the inferred callable type.
                 self.chk.store_type(callable_node, callee)
-            if self.plugin.get_function_hook(callable_name):
+
+            if ((object_type is None and self.plugin.get_function_hook(callable_name))
+                    or (object_type is not None and self.plugin.get_method_hook(callable_name))):
                 ret_type = self.apply_function_plugin(
                     arg_types, callee.ret_type, arg_kinds, formal_to_actual,
-                    args, len(callee.arg_types), callable_name)
+                    args, len(callee.arg_types), callable_name, object_type)
                 callee = callee.copy_modified(ret_type=ret_type)
             return callee.ret_type, callee
         elif isinstance(callee, Overloaded):
@@ -464,7 +491,9 @@ def check_call(self, callee: Type, args: List[Expression],
                                                callee, context,
                                                messages=arg_messages)
             return self.check_call(target, args, arg_kinds, context, arg_names,
-                                   arg_messages=arg_messages)
+                                   arg_messages=arg_messages,
+                                   callable_name=callable_name,
+                                   object_type=object_type)
         elif isinstance(callee, AnyType) or not self.chk.in_checked_function():
             self.infer_arg_types_in_context(None, args)
             return AnyType(), AnyType()
@@ -1298,8 +1327,15 @@ def check_op_local(self, method: str, base_type: Type, arg: Expression,
         method_type = analyze_member_access(method, base_type, context, False, False, True,
                                             self.named_type, self.not_ready_callback, local_errors,
                                             original_type=base_type, chk=self.chk)
+        callable_name = None
+        object_type = None
+        if isinstance(base_type, Instance):
+            # TODO: Find out in which class the method was defined originally?
+            callable_name = '{}.{}'.format(base_type.type.fullname(), method)
+            object_type = base_type
         return self.check_call(method_type, [arg], [nodes.ARG_POS],
-                               context, arg_messages=local_errors)
+                               context, arg_messages=local_errors,
+                               callable_name=callable_name, object_type=object_type)
 
     def check_op(self, method: str, base_type: Type, arg: Expression,
                  context: Context,
diff --git a/mypy/nodes.py b/mypy/nodes.py
index a5cb96007750..48715662d9d6 100644
--- a/mypy/nodes.py
+++ b/mypy/nodes.py
@@ -2037,6 +2037,12 @@ def get(self, name: str) -> Optional['SymbolTableNode']:
                 return n
         return None
 
+    def get_containing_type_info(self, name: str) -> Optional['TypeInfo']:
+        for cls in self.mro:
+            if name in cls.names:
+                return cls
+        return None
+
     def __getitem__(self, name: str) -> 'SymbolTableNode':
         n = self.get(name)
         if n:
diff --git a/mypy/plugin.py b/mypy/plugin.py
index fea751410bf4..176c2246eac2 100644
--- a/mypy/plugin.py
+++ b/mypy/plugin.py
@@ -1,12 +1,7 @@
-"""Plugin architecture for custom type checking rules for specific functions, etc.
-
-A plugin can, for example, infer better types for tricky functions such as "open".
-"""
-
 from typing import Callable, List, Tuple, Optional
 
-from mypy.nodes import Expression, StrExpr
-from mypy.types import Type, Instance, CallableType
+from mypy.nodes import Expression, StrExpr, IntExpr, UnaryExpr
+from mypy.types import Type, Instance, CallableType, TypedDictType, UnionType, NoneTyp
 
 
 # Create an Instance given full name of class and type arguments.
@@ -26,6 +21,38 @@
     Type  # Return type inferred by the callback
 ]
 
+MethodHook = Callable[
+    [
+        Type,                    # Receiver object type
+        List[List[Type]],        # List of types caller provides for each formal argument
+        List[List[Expression]],  # Actual argument expressions for each formal argument
+        Type,                    # Return type for call inferred using the regular signature
+        NamedInstanceCallback    # Callable for constructing a named instance type
+    ],
+    Type  # Return type inferred by the callback
+]
+
+# Used to provide a custom syntax for a type.
+#
+# TODO: Maybe we should allow more stuff here, such as arbitrary string and int literals?
+#TypeAnalyzeHook = Callable[
+#    [
+#        Expression,                          # The <expression> in C[<expression>]
+#        Callable[[Type], Type],              # Callback for running semantic analysis
+#        NamedInstanceCallback
+#    ],
+#    Type  # Representation of the type
+#]
+
+# Used to provide a custom string representation for a class.
+#TypeToStrHook = Callable[
+#    [
+#        Type,
+#        Callable[[Type], str],  # Callback for ordinary pretty printing
+#    ],
+#    str
+#]
+
 
 class Plugin:
     """Base class of type checker plugins.
@@ -45,7 +72,16 @@ def __init__(self, python_version: Tuple[int, int]) -> None:
     def get_function_hook(self, fullname: str) -> Optional[FunctionHook]:
         return None
 
-    # TODO: method / metaclass / class decorator hooks
+    def get_method_hook(self, fullname: str) -> Optional[MethodHook]:
+        return None
+
+    #def get_type_analyze_hook(self, fullname: str) -> Optional[TypeAnalyzeHook]:
+    #    return None
+
+    #def get_type_to_str_hook(self, fullname: str) -> Optional[TypeToStrHook]:
+    #    return None
+
+    # TODO: metaclass / class decorator hook
 
 
 class DefaultPlugin(Plugin):
@@ -58,6 +94,13 @@ def get_function_hook(self, fullname: str) -> Optional[FunctionHook]:
             return open_callback
         return None
 
+    def get_method_hook(self, fullname: str) -> Optional[MethodHook]:
+        if fullname == 'typing.Mapping.get':
+            return typed_dict_get_callback
+        elif fullname == 'builtins.int.__pow__':
+            return int_pow_callback
+        return None
+
 
 def open_callback(
         arg_types: List[List[Type]],
@@ -100,3 +143,48 @@ def contextmanager_callback(
                 arg_kinds=arg_type.arg_kinds,
                 arg_names=arg_type.arg_names)
     return inferred_return_type
+
+
+def typed_dict_get_callback(
+        object_type: Type,
+        arg_types: List[List[Type]],
+        args: List[List[Expression]],
+        inferred_return_type: Type,
+        named_generic_type: Callable[[str, List[Type]], Type]) -> Type:
+    """Infer a precise return type for TypedDict.get with literal first argument."""
+    if (isinstance(object_type, TypedDictType)
+            and len(arg_types) >= 1
+            and len(arg_types[0]) == 1
+            and isinstance(args[0][0], StrExpr)):
+        key = args[0][0].value
+        value_type = object_type.items.get(key)
+        if value_type:
+            if len(arg_types) == 1:
+                return UnionType.make_simplified_union([value_type, NoneTyp()])
+            elif len(arg_types) == 2 and len(arg_types[1]) == 1:
+                return UnionType.make_simplified_union([value_type, arg_types[1][0]])
+    return inferred_return_type
+
+
+def int_pow_callback(
+        object_type: Type,
+        arg_types: List[List[Type]],
+        args: List[List[Expression]],
+        inferred_return_type: Type,
+        named_generic_type: Callable[[str, List[Type]], Type]) -> Type:
+    """Infer a more precise return type for int.__pow__."""
+    print(arg_types)
+    if (len(arg_types) == 1
+            and len(arg_types[0]) == 1):
+        arg = args[0][0]
+        if isinstance(arg, IntExpr):
+            exponent = arg.value
+        elif isinstance(arg, UnaryExpr) and arg.op == '-' and isinstance(arg.expr, IntExpr):
+            exponent = -arg.expr.value
+        else:
+            return inferred_return_type
+        if exponent >= 0:
+            return named_generic_type('builtins.int', [])
+        else:
+            return named_generic_type('builtins.float', [])
+    return inferred_return_type
diff --git a/test-data/unit/check-expressions.test b/test-data/unit/check-expressions.test
index ae1498acdadd..d562a0ee189a 100644
--- a/test-data/unit/check-expressions.test
+++ b/test-data/unit/check-expressions.test
@@ -1682,3 +1682,20 @@ d = {**a, **b, 'c': 3}
 e = {1: 'a', **a}  # E: Argument 1 to "update" of "dict" has incompatible type Dict[str, int]; expected Mapping[int, str]
 f = {**b}  # type: Dict[int, int]  # E: List item 0 has incompatible type Dict[str, int]
 [builtins fixtures/dict.pyi]
+
+
+-- Type checker default plugin
+-- ---------------------------
+
+
+[case testIntPow]
+a = 1
+b = a + 2
+reveal_type(a**0) # E: Revealed type is 'builtins.int'
+reveal_type(a**1) # E: Revealed type is 'builtins.int'
+reveal_type(a**2) # E: Revealed type is 'builtins.int'
+reveal_type(a**(-0)) # E: Revealed type is 'builtins.int'
+reveal_type(a**(-1)) # E: Revealed type is 'builtins.float'
+reveal_type(a**(-2)) # E: Revealed type is 'builtins.float'
+reveal_type(a**b) # E: Revealed type is 'Any'
+[builtins fixtures/ops.pyi]
diff --git a/test-data/unit/check-typeddict.test b/test-data/unit/check-typeddict.test
index 4714ec77f3dc..b56cac8c7eca 100644
--- a/test-data/unit/check-typeddict.test
+++ b/test-data/unit/check-typeddict.test
@@ -528,7 +528,7 @@ reveal_type(f(g))  # E: Revealed type is '<nothing>'
 
 -- Methods
 
--- TODO: iter() doesn't accept TypedDictType as an argument type. Figure out why.
+-- TODO: iter() does not accept TypedDictType as an argument type. Figure out why.
 --[case testCanCallMappingMethodsOnTypedDict]
 --from mypy_extensions import TypedDict
 --Cell = TypedDict('Cell', {'value': int})
@@ -727,7 +727,6 @@ f(dict(x=1, y=3, z=4))  # E: Expected items ['x', 'y'] but found ['x', 'y', 'z']
 
 [builtins fixtures/dict.pyi]
 
-
 [case testTypedDictExplicitTypes]
 from mypy_extensions import TypedDict
 
@@ -744,3 +743,27 @@ p3 = {'x': 'hi'}  # E: Expected items ['x', 'y'] but found ['x'].
 p4: Point = {'x': 1, 'y': 2}
 
 [builtins fixtures/dict.pyi]
+
+
+-- Other TypedDict methods
+
+[case testTypedDictGetMethod]
+# flags: --strict-optional
+from mypy_extensions import TypedDict
+class A: pass
+D = TypedDict('D', {'x': int, 'y': str})
+d: D
+reveal_type(d.get('x')) # E: Revealed type is 'Union[builtins.int, builtins.None]'
+reveal_type(d.get('y')) # E: Revealed type is 'Union[builtins.str, builtins.None]'
+reveal_type(d.get('x', A())) # E: Revealed type is 'Union[builtins.int, __main__.A]'
+reveal_type(d.get('x', 1)) # E: Revealed type is 'builtins.int'
+reveal_type(d.get('y', None)) # E: Revealed type is 'Union[builtins.str, builtins.None]'
+[builtins fixtures/dict.pyi]
+[typing fixtures/typing-full.pyi]
+
+[case testTypedDictMissingMethod]
+from mypy_extensions import TypedDict
+D = TypedDict('D', {'x': int, 'y': str})
+d: D
+d.bad(1) # E: "D" has no attribute "bad"
+[builtins fixtures/dict.pyi]
diff --git a/test-data/unit/fixtures/ops.pyi b/test-data/unit/fixtures/ops.pyi
index 2eb6f4d0a945..8e18aeae2afd 100644
--- a/test-data/unit/fixtures/ops.pyi
+++ b/test-data/unit/fixtures/ops.pyi
@@ -39,6 +39,7 @@ class int:
     def __mul__(self, x: 'int') -> 'int': pass
     def __mod__(self, x: 'int') -> 'int': pass
     def __floordiv__(self, x: 'int') -> 'int': pass
+    def __pow__(self, x: 'int') -> Any: pass
     def __pos__(self) -> 'int': pass
     def __neg__(self) -> 'int': pass
     def __eq__(self, x: object) -> bool: pass
diff --git a/test-data/unit/fixtures/typing-full.pyi b/test-data/unit/fixtures/typing-full.pyi
index 87b51cd0d340..463b117db48d 100644
--- a/test-data/unit/fixtures/typing-full.pyi
+++ b/test-data/unit/fixtures/typing-full.pyi
@@ -103,7 +103,11 @@ class Sequence(Iterable[T], Generic[T]):
     @abstractmethod
     def __getitem__(self, n: Any) -> T: pass
 
-class Mapping(Generic[T, U]): pass
+class Mapping(Generic[T, U]):
+    @overload
+    def get(self, k: T) -> Optional[U]: ...
+    @overload
+    def get(self, k: T, default: Union[U, V]) -> Union[U, V]: ...
 
 class MutableMapping(Generic[T, U]): pass
 

From 56613397c673b888de54639f47b53f58ecf350fa Mon Sep 17 00:00:00 2001
From: Jukka Lehtosalo <jukka.lehtosalo@iki.fi>
Date: Tue, 6 Jun 2017 12:14:46 +0100
Subject: [PATCH 03/11] Fix argument type context for TypedDict get

---
 mypy/checkexpr.py                   | 44 +++++++++++++++++++----
 mypy/plugin.py                      | 54 +++++++++++++++++++++++++++--
 test-data/unit/check-typeddict.test | 26 ++++++++++++++
 3 files changed, 115 insertions(+), 9 deletions(-)

diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py
index 941243bf5988..205c851d6ce0 100644
--- a/mypy/checkexpr.py
+++ b/mypy/checkexpr.py
@@ -44,7 +44,7 @@
 from mypy.util import split_module_names
 from mypy.typevars import fill_typevars
 from mypy.visitor import ExpressionVisitor
-from mypy.plugin import Plugin
+from mypy.plugin import Plugin, MethodSignatureHook
 from mypy.typeanal import make_optional_type
 
 from mypy import experiments
@@ -215,13 +215,18 @@ def visit_call_expr(self, e: CallExpr, allow_none_return: bool = False) -> Type:
         else:
             fullname = e.callee.fullname
             if (fullname is None
-                    and isinstance(e.callee, MemberExpr)):
+                    and isinstance(e.callee, MemberExpr)
+                    and isinstance(callee_type, FunctionLike)):
                 callee_expr_type = self.chk.type_map.get(e.callee.expr)
                 if isinstance(callee_expr_type, TypedDictType):
                     info = callee_expr_type.fallback.type.get_containing_type_info(e.callee.name)
                     if info:
                         fullname = '{}.{}'.format(info.fullname(), e.callee.name)
                         object_type = callee_expr_type
+                        signature_hook = self.plugin.get_method_signature_hook(fullname)
+                        if signature_hook:
+                            callee_type = self.apply_method_signature_hook(
+                                e, callee_type, object_type, signature_hook)
         ret_type = self.check_call_expr_with_callee_type(callee_type, e, fullname, object_type)
         if isinstance(ret_type, UninhabitedType):
             self.chk.binder.unreachable()
@@ -379,10 +384,37 @@ def apply_function_plugin(self,
             return callback(formal_arg_types, formal_arg_exprs, inferred_ret_type,
                             self.chk.named_generic_type)
         else:
-            callback = self.plugin.get_method_hook(fullname)
-            assert callback is not None  # Assume that caller ensure this
-            return callback(object_type, formal_arg_types, formal_arg_exprs, inferred_ret_type,
-                            self.chk.named_generic_type)
+            method_callback = self.plugin.get_method_hook(fullname)
+            assert method_callback is not None  # Assume that caller ensure this
+            return method_callback(object_type, formal_arg_types, formal_arg_exprs,
+                                   inferred_ret_type, self.chk.named_generic_type)
+
+    def apply_method_signature_hook(self, e: CallExpr, callee: FunctionLike, object_type: Type,
+                                    signature_hook: MethodSignatureHook) -> FunctionLike:
+        """Apply a plugin hook that may infer a more precise signature for a method."""
+        if isinstance(callee, CallableType):
+            arg_kinds = e.arg_kinds
+            arg_names = e.arg_names
+            args = e.args
+            num_formals = len(callee.arg_kinds)
+            formal_to_actual = map_actuals_to_formals(
+                arg_kinds, arg_names,
+                callee.arg_kinds, callee.arg_names,
+                lambda i: self.accept(args[i]))
+            formal_arg_exprs = [[] for _ in range(num_formals)]  # type: List[List[Expression]]
+            for formal, actuals in enumerate(formal_to_actual):
+                for actual in actuals:
+                    formal_arg_exprs[formal].append(args[actual])
+            return signature_hook(object_type, formal_arg_exprs, callee,
+                                  self.chk.named_generic_type)
+        else:
+            assert isinstance(callee, Overloaded)
+            items = []
+            for item in callee.items():
+                adjusted = self.apply_method_signature_hook(e, item, object_type, signature_hook)
+                assert isinstance(adjusted, CallableType)
+                items.append(adjusted)
+            return Overloaded(items)
 
     def check_call_expr_with_callee_type(self,
                                          callee_type: Type,
diff --git a/mypy/plugin.py b/mypy/plugin.py
index 176c2246eac2..fd3b0a0033f8 100644
--- a/mypy/plugin.py
+++ b/mypy/plugin.py
@@ -1,7 +1,9 @@
 from typing import Callable, List, Tuple, Optional
 
 from mypy.nodes import Expression, StrExpr, IntExpr, UnaryExpr
-from mypy.types import Type, Instance, CallableType, TypedDictType, UnionType, NoneTyp
+from mypy.types import (
+    Type, Instance, CallableType, TypedDictType, UnionType, NoneTyp, FunctionLike, TypeVarType
+)
 
 
 # Create an Instance given full name of class and type arguments.
@@ -21,9 +23,19 @@
     Type  # Return type inferred by the callback
 ]
 
+MethodSignatureHook = Callable[
+    [
+        Type,                    # Base object type
+        List[List[Expression]],  # Actual argument expressions for each formal argument
+        CallableType,            # Original signature of the method
+        NamedInstanceCallback    # Callable for constructing a named instance type
+    ],
+    CallableType  # Potentially more precise signature inferred for the method
+]
+
 MethodHook = Callable[
     [
-        Type,                    # Receiver object type
+        Type,                    # Base object type
         List[List[Type]],        # List of types caller provides for each formal argument
         List[List[Expression]],  # Actual argument expressions for each formal argument
         Type,                    # Return type for call inferred using the regular signature
@@ -72,6 +84,9 @@ def __init__(self, python_version: Tuple[int, int]) -> None:
     def get_function_hook(self, fullname: str) -> Optional[FunctionHook]:
         return None
 
+    def get_method_signature_hook(self, fullname: str) -> Optional[MethodSignatureHook]:
+        return None
+
     def get_method_hook(self, fullname: str) -> Optional[MethodHook]:
         return None
 
@@ -94,6 +109,11 @@ def get_function_hook(self, fullname: str) -> Optional[FunctionHook]:
             return open_callback
         return None
 
+    def get_method_signature_hook(self, fullname: str) -> Optional[MethodSignatureHook]:
+        if fullname == 'typing.Mapping.get':
+            return typed_dict_get_signature_callback
+        return None
+
     def get_method_hook(self, fullname: str) -> Optional[MethodHook]:
         if fullname == 'typing.Mapping.get':
             return typed_dict_get_callback
@@ -145,6 +165,35 @@ def contextmanager_callback(
     return inferred_return_type
 
 
+def typed_dict_get_signature_callback(
+        object_type: Type,
+        args: List[List[Expression]],
+        signature: CallableType,
+        named_generic_type: Callable[[str, List[Type]], Type]) -> CallableType:
+    """Try to infer a better signature type for TypedDict.get.
+
+    This is used to get better type context for the second argument that
+    depends on a TypedDict value type.
+    """
+    if (isinstance(object_type, TypedDictType)
+            and len(args) == 2
+            and len(args[0]) == 1
+            and isinstance(args[0][0], StrExpr)
+            and len(signature.arg_types) == 2
+            and len(signature.variables) == 1):
+        key = args[0][0].value
+        value_type = object_type.items.get(key)
+        if value_type:
+            # Tweak the signature to include the value type as context. It's
+            # only needed for type inference since there's a union with a type
+            # variable that accepts everything.
+            tv = TypeVarType(signature.variables[0])
+            return signature.copy_modified(
+                arg_types=[signature.arg_types[0],
+                           UnionType.make_simplified_union([value_type, tv])])
+    return signature
+
+
 def typed_dict_get_callback(
         object_type: Type,
         arg_types: List[List[Type]],
@@ -173,7 +222,6 @@ def int_pow_callback(
         inferred_return_type: Type,
         named_generic_type: Callable[[str, List[Type]], Type]) -> Type:
     """Infer a more precise return type for int.__pow__."""
-    print(arg_types)
     if (len(arg_types) == 1
             and len(arg_types[0]) == 1):
         arg = args[0][0]
diff --git a/test-data/unit/check-typeddict.test b/test-data/unit/check-typeddict.test
index b56cac8c7eca..3c979b097eae 100644
--- a/test-data/unit/check-typeddict.test
+++ b/test-data/unit/check-typeddict.test
@@ -761,6 +761,32 @@ reveal_type(d.get('y', None)) # E: Revealed type is 'Union[builtins.str, builtin
 [builtins fixtures/dict.pyi]
 [typing fixtures/typing-full.pyi]
 
+[case testTypedDictGetMethodTypeContext]
+# flags: --strict-optional
+from typing import List
+from mypy_extensions import TypedDict
+class A: pass
+D = TypedDict('D', {'x': List[int], 'y': int})
+d: D
+reveal_type(d.get('x', [])) # E: Revealed type is 'builtins.list[builtins.int]'
+d.get('x', ['x']) # E: List item 0 has incompatible type "str"
+a = ['']
+reveal_type(d.get('x', a)) # E: Revealed type is 'Union[builtins.list[builtins.int], builtins.list[builtins.str*]]'
+[builtins fixtures/dict.pyi]
+[typing fixtures/typing-full.pyi]
+
+[case testTypedDictGetMethodInvalidArgs]
+from mypy_extensions import TypedDict
+D = TypedDict('D', {'x': int, 'y': str})
+d: D
+d.get() # E: No overload variant of "get" of "Mapping" matches argument types []
+d.get('x', 1, 2) # E: No overload variant of "get" of "Mapping" matches argument types [builtins.str, builtins.int, builtins.int]
+reveal_type(d.get('z')) # E: Revealed type is 'builtins.object*'
+s = ''
+reveal_type(d.get(s)) # E: # E: Revealed type is 'builtins.object*'
+[builtins fixtures/dict.pyi]
+[typing fixtures/typing-full.pyi]
+
 [case testTypedDictMissingMethod]
 from mypy_extensions import TypedDict
 D = TypedDict('D', {'x': int, 'y': str})

From 753d52c58b664317743df687280acafd35e0dc9a Mon Sep 17 00:00:00 2001
From: Jukka Lehtosalo <jukka.lehtosalo@iki.fi>
Date: Tue, 6 Jun 2017 13:16:13 +0100
Subject: [PATCH 04/11] Report invalid TypedDict get key argument

---
 mypy/checkexpr.py                   | 12 ++++---
 mypy/plugin.py                      | 49 +++++++++++++++++++----------
 test-data/unit/check-typeddict.test |  6 ++--
 3 files changed, 44 insertions(+), 23 deletions(-)

diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py
index 205c851d6ce0..4435b647e7b8 100644
--- a/mypy/checkexpr.py
+++ b/mypy/checkexpr.py
@@ -44,7 +44,7 @@
 from mypy.util import split_module_names
 from mypy.typevars import fill_typevars
 from mypy.visitor import ExpressionVisitor
-from mypy.plugin import Plugin, MethodSignatureHook
+from mypy.plugin import Plugin, PluginContext, MethodSignatureHook
 from mypy.typeanal import make_optional_type
 
 from mypy import experiments
@@ -367,7 +367,8 @@ def apply_function_plugin(self,
                               args: List[Expression],
                               num_formals: int,
                               fullname: Optional[str],
-                              object_type: Optional[Type]) -> Type:
+                              object_type: Optional[Type],
+                              context: Context) -> Type:
         """Use special case logic to infer the return type for of a particular named function.
 
         Return the inferred return type.
@@ -387,7 +388,7 @@ def apply_function_plugin(self,
             method_callback = self.plugin.get_method_hook(fullname)
             assert method_callback is not None  # Assume that caller ensure this
             return method_callback(object_type, formal_arg_types, formal_arg_exprs,
-                                   inferred_ret_type, self.chk.named_generic_type)
+                                   inferred_ret_type, self.create_plugin_context(context))
 
     def apply_method_signature_hook(self, e: CallExpr, callee: FunctionLike, object_type: Type,
                                     signature_hook: MethodSignatureHook) -> FunctionLike:
@@ -416,6 +417,9 @@ def apply_method_signature_hook(self, e: CallExpr, callee: FunctionLike, object_
                 items.append(adjusted)
             return Overloaded(items)
 
+    def create_plugin_context(self, context: Context) -> PluginContext:
+        return PluginContext(self.chk.named_generic_type, self.msg, context)
+
     def check_call_expr_with_callee_type(self,
                                          callee_type: Type,
                                          e: CallExpr,
@@ -508,7 +512,7 @@ def check_call(self, callee: Type, args: List[Expression],
                     or (object_type is not None and self.plugin.get_method_hook(callable_name))):
                 ret_type = self.apply_function_plugin(
                     arg_types, callee.ret_type, arg_kinds, formal_to_actual,
-                    args, len(callee.arg_types), callable_name, object_type)
+                    args, len(callee.arg_types), callable_name, object_type, context)
                 callee = callee.copy_modified(ret_type=ret_type)
             return callee.ret_type, callee
         elif isinstance(callee, Overloaded):
diff --git a/mypy/plugin.py b/mypy/plugin.py
index fd3b0a0033f8..c8fe39910d53 100644
--- a/mypy/plugin.py
+++ b/mypy/plugin.py
@@ -1,14 +1,23 @@
-from typing import Callable, List, Tuple, Optional
+from typing import Callable, List, Tuple, Optional, NamedTuple
 
-from mypy.nodes import Expression, StrExpr, IntExpr, UnaryExpr
+from mypy.nodes import Expression, StrExpr, IntExpr, UnaryExpr, Context
 from mypy.types import (
-    Type, Instance, CallableType, TypedDictType, UnionType, NoneTyp, FunctionLike, TypeVarType
+    Type, Instance, CallableType, TypedDictType, UnionType, NoneTyp, FunctionLike, TypeVarType,
+    AnyType
 )
+from mypy.messages import MessageBuilder
 
 
 # Create an Instance given full name of class and type arguments.
 NamedInstanceCallback = Callable[[str, List[Type]], Type]
 
+# Objects and callbacks that plugins use to get information from type checking
+# context or report errors.
+PluginContext = NamedTuple('PluginContext', [('named_instance', NamedInstanceCallback),
+                                             ('msg', MessageBuilder),
+                                             ('context', Context)])
+
+
 # A callback that infers the return type of a function with a special signature.
 #
 # A no-op callback would just return the inferred return type, but a useful callback
@@ -39,7 +48,7 @@
         List[List[Type]],        # List of types caller provides for each formal argument
         List[List[Expression]],  # Actual argument expressions for each formal argument
         Type,                    # Return type for call inferred using the regular signature
-        NamedInstanceCallback    # Callable for constructing a named instance type
+        PluginContext            # Access to type checking context
     ],
     Type  # Return type inferred by the callback
 ]
@@ -199,19 +208,25 @@ def typed_dict_get_callback(
         arg_types: List[List[Type]],
         args: List[List[Expression]],
         inferred_return_type: Type,
-        named_generic_type: Callable[[str, List[Type]], Type]) -> Type:
+        context: PluginContext) -> Type:
     """Infer a precise return type for TypedDict.get with literal first argument."""
     if (isinstance(object_type, TypedDictType)
             and len(arg_types) >= 1
-            and len(arg_types[0]) == 1
-            and isinstance(args[0][0], StrExpr)):
-        key = args[0][0].value
-        value_type = object_type.items.get(key)
-        if value_type:
-            if len(arg_types) == 1:
-                return UnionType.make_simplified_union([value_type, NoneTyp()])
-            elif len(arg_types) == 2 and len(arg_types[1]) == 1:
-                return UnionType.make_simplified_union([value_type, arg_types[1][0]])
+            and len(arg_types[0]) == 1):
+        if isinstance(args[0][0], StrExpr):
+            key = args[0][0].value
+            value_type = object_type.items.get(key)
+            if value_type:
+                if len(arg_types) == 1:
+                    return UnionType.make_simplified_union([value_type, NoneTyp()])
+                elif len(arg_types) == 2 and len(arg_types[1]) == 1:
+                    return UnionType.make_simplified_union([value_type, arg_types[1][0]])
+            else:
+                context.msg.typeddict_item_name_not_found(object_type, key, context.context)
+                return AnyType()
+        else:
+            context.msg.typeddict_item_name_must_be_string_literal(object_type, context.context)
+            return AnyType()
     return inferred_return_type
 
 
@@ -220,7 +235,7 @@ def int_pow_callback(
         arg_types: List[List[Type]],
         args: List[List[Expression]],
         inferred_return_type: Type,
-        named_generic_type: Callable[[str, List[Type]], Type]) -> Type:
+        context: PluginContext) -> Type:
     """Infer a more precise return type for int.__pow__."""
     if (len(arg_types) == 1
             and len(arg_types[0]) == 1):
@@ -232,7 +247,7 @@ def int_pow_callback(
         else:
             return inferred_return_type
         if exponent >= 0:
-            return named_generic_type('builtins.int', [])
+            return context.named_instance('builtins.int', [])
         else:
-            return named_generic_type('builtins.float', [])
+            return context.named_instance('builtins.float', [])
     return inferred_return_type
diff --git a/test-data/unit/check-typeddict.test b/test-data/unit/check-typeddict.test
index 3c979b097eae..568c1ff95d96 100644
--- a/test-data/unit/check-typeddict.test
+++ b/test-data/unit/check-typeddict.test
@@ -781,9 +781,11 @@ D = TypedDict('D', {'x': int, 'y': str})
 d: D
 d.get() # E: No overload variant of "get" of "Mapping" matches argument types []
 d.get('x', 1, 2) # E: No overload variant of "get" of "Mapping" matches argument types [builtins.str, builtins.int, builtins.int]
-reveal_type(d.get('z')) # E: Revealed type is 'builtins.object*'
+x = d.get('z') # E: 'z' is not a valid item name; expected one of ['x', 'y']
+reveal_type(x) # E: Revealed type is 'Any'
 s = ''
-reveal_type(d.get(s)) # E: # E: Revealed type is 'builtins.object*'
+y = d.get(s) # E: Cannot prove expression is a valid item name; expected one of ['x', 'y']
+reveal_type(y) # E: Revealed type is 'Any'
 [builtins fixtures/dict.pyi]
 [typing fixtures/typing-full.pyi]
 

From 166a911ce4b1b10e0dea74c2bad9c37b48505bcc Mon Sep 17 00:00:00 2001
From: Jukka Lehtosalo <jukka.lehtosalo@iki.fi>
Date: Tue, 6 Jun 2017 13:22:43 +0100
Subject: [PATCH 05/11] Add test case that uses typeshed stubs

---
 test-data/unit/pythoneval.test | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/test-data/unit/pythoneval.test b/test-data/unit/pythoneval.test
index c9ee1f322a28..91c92a39209f 100644
--- a/test-data/unit/pythoneval.test
+++ b/test-data/unit/pythoneval.test
@@ -1336,3 +1336,23 @@ _program.py:13: error: Revealed type is 'def (x: builtins.int) -> contextlib.Gen
 _program.py:14: error: Revealed type is 'def (*x: builtins.str) -> contextlib.GeneratorContextManager[builtins.int*]'
 _program.py:16: error: Argument 1 to "f" has incompatible type "str"; expected "int"
 _program.py:17: error: Revealed type is 'builtins.str*'
+
+[case testTypedDictGet]
+# Test that TypedDict get plugin works with typeshed stubs
+# TODO: Make it possible to use strict optional here
+from mypy_extensions import TypedDict
+class A: pass
+D = TypedDict('D', {'x': int, 'y': str})
+d: D
+reveal_type(d.get('x'))
+reveal_type(d.get('y'))
+d.get('z')
+d.get()
+s = ''
+d.get(s)
+[out]
+_testTypedDictGet.py:7: error: Revealed type is 'builtins.int'
+_testTypedDictGet.py:8: error: Revealed type is 'builtins.str'
+_testTypedDictGet.py:9: error: 'z' is not a valid item name; expected one of ['x', 'y']
+_testTypedDictGet.py:10: error: No overload variant of "get" of "Mapping" matches argument types []
+_testTypedDictGet.py:12: error: Cannot prove expression is a valid item name; expected one of ['x', 'y']

From b02599146a257a47e80805893ae8bebb15d50027 Mon Sep 17 00:00:00 2001
From: Jukka Lehtosalo <jukka.lehtosalo@iki.fi>
Date: Tue, 6 Jun 2017 13:38:50 +0100
Subject: [PATCH 06/11] Generalize method hook to work with Instances

---
 mypy/checkexpr.py                     | 20 ++++++++++++--------
 test-data/unit/check-expressions.test |  1 +
 2 files changed, 13 insertions(+), 8 deletions(-)

diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py
index 4435b647e7b8..28cbbb6a861b 100644
--- a/mypy/checkexpr.py
+++ b/mypy/checkexpr.py
@@ -218,15 +218,19 @@ def visit_call_expr(self, e: CallExpr, allow_none_return: bool = False) -> Type:
                     and isinstance(e.callee, MemberExpr)
                     and isinstance(callee_type, FunctionLike)):
                 callee_expr_type = self.chk.type_map.get(e.callee.expr)
-                if isinstance(callee_expr_type, TypedDictType):
+                info = None
+                # TODO: Support fallbacks of other kinds of types as well?
+                if isinstance(callee_expr_type, Instance):
+                    info = callee_expr_type.type
+                elif isinstance(callee_expr_type, TypedDictType):
                     info = callee_expr_type.fallback.type.get_containing_type_info(e.callee.name)
-                    if info:
-                        fullname = '{}.{}'.format(info.fullname(), e.callee.name)
-                        object_type = callee_expr_type
-                        signature_hook = self.plugin.get_method_signature_hook(fullname)
-                        if signature_hook:
-                            callee_type = self.apply_method_signature_hook(
-                                e, callee_type, object_type, signature_hook)
+                if info:
+                    fullname = '{}.{}'.format(info.fullname(), e.callee.name)
+                    object_type = callee_expr_type
+                    signature_hook = self.plugin.get_method_signature_hook(fullname)
+                    if signature_hook:
+                        callee_type = self.apply_method_signature_hook(
+                            e, callee_type, object_type, signature_hook)
         ret_type = self.check_call_expr_with_callee_type(callee_type, e, fullname, object_type)
         if isinstance(ret_type, UninhabitedType):
             self.chk.binder.unreachable()
diff --git a/test-data/unit/check-expressions.test b/test-data/unit/check-expressions.test
index d562a0ee189a..9f5fc395f712 100644
--- a/test-data/unit/check-expressions.test
+++ b/test-data/unit/check-expressions.test
@@ -1698,4 +1698,5 @@ reveal_type(a**(-0)) # E: Revealed type is 'builtins.int'
 reveal_type(a**(-1)) # E: Revealed type is 'builtins.float'
 reveal_type(a**(-2)) # E: Revealed type is 'builtins.float'
 reveal_type(a**b) # E: Revealed type is 'Any'
+reveal_type(a.__pow__(2)) # E: Revealed type is 'builtins.int'
 [builtins fixtures/ops.pyi]

From 2cf5cd72057c20a06d521d1354f625ac808d9d76 Mon Sep 17 00:00:00 2001
From: Jukka Lehtosalo <jukka.lehtosalo@iki.fi>
Date: Tue, 6 Jun 2017 14:55:57 +0100
Subject: [PATCH 07/11] TypedDict get tweaks

Some of the tests are adapted from #2620 by @rowillia.
---
 mypy/checkexpr.py                   | 20 +++++++++++++++++---
 mypy/plugin.py                      |  3 ---
 test-data/unit/check-typeddict.test | 22 ++++++++++++++++++++--
 3 files changed, 37 insertions(+), 8 deletions(-)

diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py
index 28cbbb6a861b..a0a4fa90e122 100644
--- a/mypy/checkexpr.py
+++ b/mypy/checkexpr.py
@@ -1848,13 +1848,14 @@ def visit_dict_expr(self, e: DictExpr) -> Type:
         # an error, but returns the TypedDict type that matches the literal it found
         # that would cause a second error when that TypedDict type is returned upstream
         # to avoid the second error, we always return TypedDict type that was requested
-        if isinstance(self.type_context[-1], TypedDictType):
+        typeddict_context = self.find_typeddict_context(self.type_context[-1])
+        if typeddict_context:
             self.check_typeddict_call_with_dict(
-                callee=self.type_context[-1],
+                callee=typeddict_context,
                 kwargs=e,
                 context=e
             )
-            return self.type_context[-1].copy_modified()
+            return typeddict_context.copy_modified()
 
         # Collect function arguments, watching out for **expr.
         args = []  # type: List[Expression]  # Regular "key: value"
@@ -1905,6 +1906,19 @@ def visit_dict_expr(self, e: DictExpr) -> Type:
                     self.check_call(method, [arg], [nodes.ARG_POS], arg)
         return rv
 
+    def find_typeddict_context(self, context: Type) -> Optional[TypedDictType]:
+        if isinstance(context, TypedDictType):
+            return context
+        elif isinstance(context, UnionType):
+            items = []
+            for item in context.items:
+                item_context = self.find_typeddict_context(item)
+                if item_context:
+                    items.append(item_context)
+            if len(items) == 1:
+                return items[0]
+        return None
+
     def visit_lambda_expr(self, e: LambdaExpr) -> Type:
         """Type check lambda expression."""
         inferred_type, type_override = self.infer_lambda_type_using_context(e)
diff --git a/mypy/plugin.py b/mypy/plugin.py
index c8fe39910d53..adc4074b4ce3 100644
--- a/mypy/plugin.py
+++ b/mypy/plugin.py
@@ -224,9 +224,6 @@ def typed_dict_get_callback(
             else:
                 context.msg.typeddict_item_name_not_found(object_type, key, context.context)
                 return AnyType()
-        else:
-            context.msg.typeddict_item_name_must_be_string_literal(object_type, context.context)
-            return AnyType()
     return inferred_return_type
 
 
diff --git a/test-data/unit/check-typeddict.test b/test-data/unit/check-typeddict.test
index 568c1ff95d96..c29aad48af92 100644
--- a/test-data/unit/check-typeddict.test
+++ b/test-data/unit/check-typeddict.test
@@ -784,8 +784,8 @@ d.get('x', 1, 2) # E: No overload variant of "get" of "Mapping" matches argument
 x = d.get('z') # E: 'z' is not a valid item name; expected one of ['x', 'y']
 reveal_type(x) # E: Revealed type is 'Any'
 s = ''
-y = d.get(s) # E: Cannot prove expression is a valid item name; expected one of ['x', 'y']
-reveal_type(y) # E: Revealed type is 'Any'
+y = d.get(s)
+reveal_type(y) # E: Revealed type is 'builtins.object*'
 [builtins fixtures/dict.pyi]
 [typing fixtures/typing-full.pyi]
 
@@ -795,3 +795,21 @@ D = TypedDict('D', {'x': int, 'y': str})
 d: D
 d.bad(1) # E: "D" has no attribute "bad"
 [builtins fixtures/dict.pyi]
+
+[case testTypedDictChainedGetMethodWithDictFallback]
+from mypy_extensions import TypedDict
+D = TypedDict('D', {'x': int, 'y': str})
+E = TypedDict('E', {'d': D})
+p = E(d=D(x=0, y=''))
+reveal_type(p.get('d', {'x': 1, 'y': ''})) # E: Revealed type is 'TypedDict(x=builtins.int, y=builtins.str, _fallback=__main__.D)'
+p.get('d', {}) # E: Expected items ['x', 'y'] but found [].
+[builtins fixtures/dict.pyi]
+[typing fixtures/typing-full.pyi]
+
+[case testTypedDictGetDefaultParameterStillTypeChecked]
+from mypy_extensions import TypedDict
+TaggedPoint = TypedDict('TaggedPoint', {'type': str, 'x': int, 'y': int})
+p = TaggedPoint(type='2d', x=42, y=1337)
+p.get('x', 1 + 'y')     # E: Unsupported operand types for + ("int" and "str")
+[builtins fixtures/dict.pyi]
+[typing fixtures/typing-full.pyi]

From abe29a96610377e8b0e742a6779072663bffd1a7 Mon Sep 17 00:00:00 2001
From: Jukka Lehtosalo <jukka.lehtosalo@iki.fi>
Date: Tue, 6 Jun 2017 15:18:54 +0100
Subject: [PATCH 08/11] Remove commented-out code

---
 mypy/plugin.py | 27 ---------------------------
 1 file changed, 27 deletions(-)

diff --git a/mypy/plugin.py b/mypy/plugin.py
index adc4074b4ce3..1dc211d97132 100644
--- a/mypy/plugin.py
+++ b/mypy/plugin.py
@@ -53,27 +53,6 @@
     Type  # Return type inferred by the callback
 ]
 
-# Used to provide a custom syntax for a type.
-#
-# TODO: Maybe we should allow more stuff here, such as arbitrary string and int literals?
-#TypeAnalyzeHook = Callable[
-#    [
-#        Expression,                          # The <expression> in C[<expression>]
-#        Callable[[Type], Type],              # Callback for running semantic analysis
-#        NamedInstanceCallback
-#    ],
-#    Type  # Representation of the type
-#]
-
-# Used to provide a custom string representation for a class.
-#TypeToStrHook = Callable[
-#    [
-#        Type,
-#        Callable[[Type], str],  # Callback for ordinary pretty printing
-#    ],
-#    str
-#]
-
 
 class Plugin:
     """Base class of type checker plugins.
@@ -99,12 +78,6 @@ def get_method_signature_hook(self, fullname: str) -> Optional[MethodSignatureHo
     def get_method_hook(self, fullname: str) -> Optional[MethodHook]:
         return None
 
-    #def get_type_analyze_hook(self, fullname: str) -> Optional[TypeAnalyzeHook]:
-    #    return None
-
-    #def get_type_to_str_hook(self, fullname: str) -> Optional[TypeToStrHook]:
-    #    return None
-
     # TODO: metaclass / class decorator hook
 
 

From 8a240dc294718e84d4f13195852e88698c80e1e9 Mon Sep 17 00:00:00 2001
From: Jukka Lehtosalo <jukka.lehtosalo@iki.fi>
Date: Tue, 6 Jun 2017 15:36:27 +0100
Subject: [PATCH 09/11] Various tweaks

---
 mypy/checkexpr.py                     | 15 ++++++++++++---
 mypy/plugin.py                        | 10 ++++++++--
 test-data/unit/check-expressions.test |  2 ++
 3 files changed, 22 insertions(+), 5 deletions(-)

diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py
index a0a4fa90e122..d089b56a36d6 100644
--- a/mypy/checkexpr.py
+++ b/mypy/checkexpr.py
@@ -209,6 +209,7 @@ def visit_call_expr(self, e: CallExpr, allow_none_return: bool = False) -> Type:
                 isinstance(callee_type, CallableType)
                 and callee_type.implicit):
             return self.msg.untyped_function_call(callee_type, e)
+        # Figure out the full name of the callee for plugin loopup.
         object_type = None
         if not isinstance(e.callee, RefExpr):
             fullname = None
@@ -217,6 +218,8 @@ def visit_call_expr(self, e: CallExpr, allow_none_return: bool = False) -> Type:
             if (fullname is None
                     and isinstance(e.callee, MemberExpr)
                     and isinstance(callee_type, FunctionLike)):
+                # For method calls we include the defining class for the method
+                # in the full name (example: 'typing.Mapping.get').
                 callee_expr_type = self.chk.type_map.get(e.callee.expr)
                 info = None
                 # TODO: Support fallbacks of other kinds of types as well?
@@ -227,6 +230,7 @@ def visit_call_expr(self, e: CallExpr, allow_none_return: bool = False) -> Type:
                 if info:
                     fullname = '{}.{}'.format(info.fullname(), e.callee.name)
                     object_type = callee_expr_type
+                    # Apply plugin signature hook that may generate a better signature.
                     signature_hook = self.plugin.get_method_signature_hook(fullname)
                     if signature_hook:
                         callee_type = self.apply_method_signature_hook(
@@ -373,7 +377,7 @@ def apply_function_plugin(self,
                               fullname: Optional[str],
                               object_type: Optional[Type],
                               context: Context) -> Type:
-        """Use special case logic to infer the return type for of a particular named function.
+        """Use special case logic to infer the return type of a specific named function/method.
 
         Return the inferred return type.
         """
@@ -384,13 +388,15 @@ def apply_function_plugin(self,
                 formal_arg_types[formal].append(arg_types[actual])
                 formal_arg_exprs[formal].append(args[actual])
         if object_type is None:
+            # Apply function plugin
             callback = self.plugin.get_function_hook(fullname)
-            assert callback is not None  # Assume that caller ensure this
+            assert callback is not None  # Assume that caller ensures this
             return callback(formal_arg_types, formal_arg_exprs, inferred_ret_type,
                             self.chk.named_generic_type)
         else:
+            # Apply method plugin
             method_callback = self.plugin.get_method_hook(fullname)
-            assert method_callback is not None  # Assume that caller ensure this
+            assert method_callback is not None  # Assume that caller ensures this
             return method_callback(object_type, formal_arg_types, formal_arg_exprs,
                                    inferred_ret_type, self.create_plugin_context(context))
 
@@ -1371,6 +1377,7 @@ def check_op_local(self, method: str, base_type: Type, arg: Expression,
         object_type = None
         if isinstance(base_type, Instance):
             # TODO: Find out in which class the method was defined originally?
+            # TODO: Support non-Instance types.
             callable_name = '{}.{}'.format(base_type.type.fullname(), method)
             object_type = base_type
         return self.check_call(method_type, [arg], [nodes.ARG_POS],
@@ -1916,7 +1923,9 @@ def find_typeddict_context(self, context: Type) -> Optional[TypedDictType]:
                 if item_context:
                     items.append(item_context)
             if len(items) == 1:
+                # Only one union item is TypedDict, so use the context as it's unambiguous.
                 return items[0]
+        # No TypedDict type in context.
         return None
 
     def visit_lambda_expr(self, e: LambdaExpr) -> Type:
diff --git a/mypy/plugin.py b/mypy/plugin.py
index 1dc211d97132..5015f7b4c940 100644
--- a/mypy/plugin.py
+++ b/mypy/plugin.py
@@ -11,8 +11,8 @@
 # Create an Instance given full name of class and type arguments.
 NamedInstanceCallback = Callable[[str, List[Type]], Type]
 
-# Objects and callbacks that plugins use to get information from type checking
-# context or report errors.
+# Some objects and callbacks that plugins can use to get information from the
+# type checker or to report errors.
 PluginContext = NamedTuple('PluginContext', [('named_instance', NamedInstanceCallback),
                                              ('msg', MessageBuilder),
                                              ('context', Context)])
@@ -32,6 +32,8 @@
     Type  # Return type inferred by the callback
 ]
 
+# A callback that may infer a better signature for a method.  Note that argument types aren't
+# available yet.  If you need them, you have to use a MethodHook instead.
 MethodSignatureHook = Callable[
     [
         Type,                    # Base object type
@@ -42,6 +44,9 @@
     CallableType  # Potentially more precise signature inferred for the method
 ]
 
+# A callback that infers the return type of a method with a special signature.
+#
+# This is pretty similar to FunctionHook.
 MethodHook = Callable[
     [
         Type,                    # Base object type
@@ -215,6 +220,7 @@ def int_pow_callback(
         elif isinstance(arg, UnaryExpr) and arg.op == '-' and isinstance(arg.expr, IntExpr):
             exponent = -arg.expr.value
         else:
+            # Right operand not an int literal or a negated literal -- give up.
             return inferred_return_type
         if exponent >= 0:
             return context.named_instance('builtins.int', [])
diff --git a/test-data/unit/check-expressions.test b/test-data/unit/check-expressions.test
index 9f5fc395f712..cc8c10945589 100644
--- a/test-data/unit/check-expressions.test
+++ b/test-data/unit/check-expressions.test
@@ -1699,4 +1699,6 @@ reveal_type(a**(-1)) # E: Revealed type is 'builtins.float'
 reveal_type(a**(-2)) # E: Revealed type is 'builtins.float'
 reveal_type(a**b) # E: Revealed type is 'Any'
 reveal_type(a.__pow__(2)) # E: Revealed type is 'builtins.int'
+reveal_type(a.__pow__(a)) # E: Revealed type is 'Any'
+a.__pow__() # E: Too few arguments for "__pow__" of "int"
 [builtins fixtures/ops.pyi]

From e43d94a60d716c47c1a7e5981c8d3fdd4fa74dc9 Mon Sep 17 00:00:00 2001
From: Jukka Lehtosalo <jukka.lehtosalo@iki.fi>
Date: Tue, 6 Jun 2017 15:41:50 +0100
Subject: [PATCH 10/11] Fix test case

---
 test-data/unit/pythoneval.test | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/test-data/unit/pythoneval.test b/test-data/unit/pythoneval.test
index 91c92a39209f..1035a436c53d 100644
--- a/test-data/unit/pythoneval.test
+++ b/test-data/unit/pythoneval.test
@@ -1349,10 +1349,10 @@ reveal_type(d.get('y'))
 d.get('z')
 d.get()
 s = ''
-d.get(s)
+reveal_type(d.get(s))
 [out]
 _testTypedDictGet.py:7: error: Revealed type is 'builtins.int'
 _testTypedDictGet.py:8: error: Revealed type is 'builtins.str'
 _testTypedDictGet.py:9: error: 'z' is not a valid item name; expected one of ['x', 'y']
 _testTypedDictGet.py:10: error: No overload variant of "get" of "Mapping" matches argument types []
-_testTypedDictGet.py:12: error: Cannot prove expression is a valid item name; expected one of ['x', 'y']
+_testTypedDictGet.py:12: error: Revealed type is 'builtins.object*'

From 6db7d204172722e03ebf453eed4ed1008bc2624e Mon Sep 17 00:00:00 2001
From: Jukka Lehtosalo <jukka.lehtosalo@iki.fi>
Date: Wed, 7 Jun 2017 17:29:54 +0100
Subject: [PATCH 11/11] Address review feedback

---
 mypy/checker.py                       | 4 ++--
 test-data/unit/check-expressions.test | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/mypy/checker.py b/mypy/checker.py
index 89730f894d24..89fd4d9ad987 100644
--- a/mypy/checker.py
+++ b/mypy/checker.py
@@ -133,7 +133,7 @@ class TypeChecker(NodeVisitor[None]):
     plugin = None  # type: Plugin
 
     def __init__(self, errors: Errors, modules: Dict[str, MypyFile], options: Options,
-                 tree: MypyFile, path: str, plugin: Optional[Plugin] = None) -> None:
+                 tree: MypyFile, path: str, plugin: Plugin) -> None:
         """Construct a type checker.
 
         Use errors to report type check errors.
@@ -144,7 +144,7 @@ def __init__(self, errors: Errors, modules: Dict[str, MypyFile], options: Option
         self.tree = tree
         self.path = path
         self.msg = MessageBuilder(errors, modules)
-        self.plugin = plugin or Plugin(options.python_version)
+        self.plugin = plugin
         self.expr_checker = mypy.checkexpr.ExpressionChecker(self, self.msg, self.plugin)
         self.scope = Scope(tree)
         self.binder = ConditionalTypeBinder()
diff --git a/test-data/unit/check-expressions.test b/test-data/unit/check-expressions.test
index cc8c10945589..ab2bd1e92543 100644
--- a/test-data/unit/check-expressions.test
+++ b/test-data/unit/check-expressions.test
@@ -1694,8 +1694,8 @@ b = a + 2
 reveal_type(a**0) # E: Revealed type is 'builtins.int'
 reveal_type(a**1) # E: Revealed type is 'builtins.int'
 reveal_type(a**2) # E: Revealed type is 'builtins.int'
-reveal_type(a**(-0)) # E: Revealed type is 'builtins.int'
-reveal_type(a**(-1)) # E: Revealed type is 'builtins.float'
+reveal_type(a**-0) # E: Revealed type is 'builtins.int'
+reveal_type(a**-1) # E: Revealed type is 'builtins.float'
 reveal_type(a**(-2)) # E: Revealed type is 'builtins.float'
 reveal_type(a**b) # E: Revealed type is 'Any'
 reveal_type(a.__pow__(2)) # E: Revealed type is 'builtins.int'