// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.python;

import com.google.common.collect.ImmutableList;
import com.intellij.openapi.util.RecursionManager;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.testFramework.LightProjectDescriptor;
import com.jetbrains.python.documentation.docstrings.DocStringFormat;
import com.jetbrains.python.fixtures.PyTestCase;
import com.jetbrains.python.psi.LanguageLevel;
import com.jetbrains.python.psi.PyExpression;
import com.jetbrains.python.psi.PyTargetExpression;
import com.jetbrains.python.psi.impl.PyBuiltinCache;
import com.jetbrains.python.psi.types.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Collections;
import java.util.List;


public class PyTypeTest extends PyTestCase {

  @Override
  protected @Nullable LightProjectDescriptor getProjectDescriptor() {
    return ourPy2Descriptor;
  }

  /**
   * Call of union returns union of all callable types in this union
   */
  public void testCallableInUnion() {
    doTest("str",
           """
             import random
             def spam():
                 return "D"
             class Eggs:
                 pass
             class Eggs2:
                 pass
             dd = spam if random.randint != 42 else Eggs2()
             var = dd if random.randint != 42 else dd
             expr = var()""");
  }

  public void testTupleType() {
    doTest("str",
           "t = ('a', 2)\n" +
           "expr = t[0]");
    doTest("List[bool]",
           """
             from typing import List, Literal
             def foo(t: tuple[int, str, List[bool]], i: Literal[2]):
                 expr = t[i]""");
    doTest("Union[int, List[bool]]",
           """
             from typing import List, Literal
             def foo(t: tuple[int, str, List[bool]], i: Literal[0, -1]):
                 expr = t[i]""");
  }

  public void testTupleAssignmentType() {
    doTest("str",
           "t = ('a', 2)\n" +
           "(expr, q) = t");
  }

  public void testBinaryExprType() {
    doTest("int",
           "expr = 1 + 2");
    doTest("str",
           "expr = '1' + '2'");
    doTest("str",
           "expr = '%s' % ('a')");
    doTest("List[int]",
           "expr = [1] + [2]");
  }

  public void testAssignmentChainBinaryExprType() {
    doTest("int",
           """
             class C(object):
                 def __add__(self, other):
                     return -1
             c = C()
             x = c + 'foo'
             expr = x + 'bar'""");
  }

  public void testUnaryExprType() {
    doTest("int",
           "expr = -1");
  }

  public void testTypeFromComment() {
    doTest("str",
           "expr = ''.capitalize()");
  }

  public void testUnionOfTuples() {
    doTest("Union[Tuple[int, str], Tuple[str, int]]",
           """
             def x(b):
               if b:
                 return (1, 'a')
               else:
                 return ('a', 1)
             expr = x()""");
  }

  public void testAugAssignment() {
    doTest("int",
           """
             def x():
                 count = 0
                 count += 1
                 return count
             expr = x()""");
  }

  public void testSetComp() {
    doTest("Set[int]",
           "expr = {i for i in range(3)}");
  }

  public void testSet() {
    doTest("Set[int]",
           "expr = {1, 2, 3}");
  }

  // PY-1425
  public void testNone() {
    doTest("Any",
           """
             class C:
                 def __init__(self): self.foo = None
             expr = C().foo""");
  }

  // PY-1427
  public void testUnicodeLiteral() {  // PY-1427
    doTest("unicode",
           "expr = u'foo'");
  }

  public void testPropertyType() {
    doTest("property",
           """
             class C:
                 x = property(lambda self: 'foo', None, None)
             expr = C.x
             """);
  }

  public void testPropertyInstanceType() {
    doTest("str",
           """
             class C:
                 x = property(lambda self: 'foo', None, None)
             c = C()
             expr = c.x
             """);
  }

  public void testIterationType() {
    doTest("int",
           "for expr in [1, 2, 3]: pass");
  }

  public void testSubscriptType() {
    doTest("int",
           "l = [1, 2, 3]; expr = l[0]");
  }

  public void testListSliceType() {
    doTest("List[int]",
           "l = [1, 2, 3]; expr = l[0:1]");
  }

  public void testTupleSliceType() {
    doTest("tuple",
           "l = (1, 2, 3); expr = l[0:1]");
  }

  // PY-18560
  public void testCustomSliceType() {
    doTest(
      "int",
      """
        class RectangleFactory(object):
            def __getitem__(self, item):
                return 1
        factory = RectangleFactory()
        expr = factory[:]"""
    );
  }

  public void testExceptType() {
    doTest("ImportError",
           """
             try:
                 pass
             except ImportError, expr:
                 pass""");
  }

  public void testTypeAnno() {
    runWithLanguageLevel(
      LanguageLevel.PYTHON34,
      () -> doTest("str",
                   "def foo(x: str) -> list:\n" +
                   "    expr = x")
    );
  }

  public void testReturnTypeAnno() {
    runWithLanguageLevel(
      LanguageLevel.PYTHON34,
      () -> doTest("list",
                   """
                     def foo(x) -> list:
                         return x
                     expr = foo(None)""")
    );
  }

  public void testRestParamType() {
    doTest("int",
           """
             def foo(limit):
               ''':param integer limit: maximum number of stack frames to show'''
               expr = limit""");
  }

  // PY-3849
  public void testRestClassType() {
    doTest("Foo",
           """
             class Foo: pass
             def foo(limit):
               ''':param :class:`Foo` limit: maximum number of stack frames to show'''
               expr = limit""");
  }

  public void testRestIvarType() {
    doTest("str",
           """
             def foo(p):
                 var = p.bar
                 ''':type var: str'''
                 expr = var""");
  }

  public void testUnknownTypeInUnion() {
    doTest("Union[int, Any]",
           """
             def f(c, x):
                 if c:
                     return 1
                 return x
             expr = f(1, g())
             """);
  }

  public void testIsInstance() {
    doTest("str",
           """
             def f(c):
                 def g():
                     '''
                     :rtype: int or str
                     '''
                 x = g()
                 if isinstance(x, str):
                     expr = x""");
  }

  // PY-2140
  public void testNotIsInstance() {
    doTest("list",
           """
             def f(c):
                 def g():
                     '''
                     :rtype: int or str or list
                     '''
                 x = g()
                 if not isinstance(x, (str, long)):
                     expr = x""");
  }

  public void testIsInstance2() {
    doTest("str",
           """
             x = ""
             if isinstance(x, (1, "")):
                 expr = x
             """);
  }

  public void testIfIsInstanceOr1() {
    doTest("Union[int, str]",
           """
               def foo(a):
                   if isinstance(a, int) or isinstance(a, str):
                       expr = a
           """);
  }

  public void testIfIsInstanceOr2() {
    doTest("Union[str, int, A, B]",
           """
           class A:
               pass
           
           class B:
               pass
          
           def f(a: object):
               if isinstance(a, str) or isinstance(a, int) or isinstance(a, A) or isinstance(a, B):
                   expr = a
               else:
                   pass
           """);
  }

  public void testIfIsInstanceAnd1() {
    doTest("A",
           """
             class A:
                 pass
             
             def f(a):
                 if isinstance(a, (str, A)) and isinstance(a, (A, int)):
                     expr = a
             """);
  }

  public void testIfIsInstanceAnd2() {
    doTest("A",
           """
             class A:
                 pass
             
             class B:
                 pass
             
             def f(a):
                 if isinstance(a, (str, A)) and isinstance(a, (A, int)) and isinstance(a, (B, A)):
                     expr = a
             """);
  }

  public void testIfIsInstanceLogicalExpressions() {
    doTest("Union[str, B]",
           """
             class A:
                 pass
             
             class B:
                 pass
             
             def f(a):
                 if isinstance(a, (str, A, int)) and not isinstance(a, (A, int)) or isinstance(a, B):
                     expr = a
             """);
  }

  // PY-4383
  public void testAssertIsInstance() {
    doTest("int",
           """
             from unittest import TestCase

             class Test1(TestCase):
                 def test_1(self, c):
                     x = 1 if c else 'foo'
                     self.assertIsInstance(x, int)
                     expr = x
             """);
  }

  // PY-20679
  public void testIsInstanceViaTrue() {
    doTest("str",
           """
             a = None
             if isinstance(a, str) is True:
                 expr = a
             raise TypeError('Invalid type')""");

    doTest("str",
           """
             a = None
             if True is isinstance(a, str):
                 expr = a
             raise TypeError('Invalid type')""");
  }

  // PY-20679
  public void testIsInstanceViaFalse() {
    doTest("str",
           """
             a = None
             if isinstance(a, str) is not False:
                 expr = a
             raise TypeError('Invalid type')""");

    doTest("str",
           """
             a = None
             if False is not isinstance(a, str):
                 expr = a
             raise TypeError('Invalid type')""");

    doTest("str",
           """
             a = None
             if not isinstance(a, str) is False:
                 expr = a
             raise TypeError('Invalid type')""");

    doTest("str",
           """
             a = None
             if not False is isinstance(a, str):
                 expr = a
             raise TypeError('Invalid type')""");
  }

  // PY-20679
  public void testNotIsInstanceViaTrue() {
    doTest("str",
           """
             a = None
             if not isinstance(a, str) is True:
                 raise TypeError('Invalid type')
             expr = a""");

    doTest("str",
           """
             a = None
             if not True is isinstance(a, str):
                 raise TypeError('Invalid type')
             expr = a""");

    doTest("str",
           """
             a = None
             if isinstance(a, str) is not True:
                 raise TypeError('Invalid type')
             expr = a""");

    doTest("str",
           """
             a = None
             if True is not isinstance(a, str):
                 raise TypeError('Invalid type')
             expr = a""");
  }

  // PY-20679
  public void testNotIsInstanceViaFalse() {
    doTest("str",
           """
             a = None
             if isinstance(a, str) is False:
                 raise TypeError('Invalid type')
             expr = a""");

    doTest("str",
           """
             a = None
             if False is isinstance(a, str):
                 raise TypeError('Invalid type')
             expr = a""");
  }

  public void testIfNotEqOperator() {
    doTest("Literal[\"ab\"]",
           """
             from typing import Literal
             def foo(v: Literal["abba", "ab"]):
                 if (v <> "abba"):
                     expr = v
             """);
  }

  // PY-4279
  public void testFieldReassignment() {
    doTest("C1",
           """
             class C1(object):
                 def m1(self):
                     pass

             class C2(object):
                 def m2(self):
                     pass

             class Test(object):
                 def __init__(self, param1):
                     self.x = param1
                     self.x = C1()
                     expr = self.x
             """);
  }

  public void testSOEOnRecursiveCall() {
    doTest("Any", "def foo(x): return foo(x)\n" +
                  "expr = foo(1)");
  }

  public void testGenericConcrete() {
    doTest("int", """
      def f(x):
          '''
          :type x: T
          :rtype: T
          '''
          return x

      expr = f(1)
      """);
  }

  public void testGenericConcreteMismatch() {
    doTest("int", """
      def f(x, y):
          '''
          :type x: T
          :rtype: T
          '''
          return x

      expr = f(1)
      """);
  }

  // PY-5831
  public void testYieldType() {
    doTest("Any", """
      def f():
          expr = yield 2
      """);
  }

  // PY-9590
  public void testYieldParensType() {
    doTest("Any", """
      def f():
          expr = (yield 2)
      """);
  }

  public void testFunctionAssignment() {
    doTest("int",
           """
             def f():
                 return 1
             g = f
             h = g
             expr = h()
             """);
  }

  public void testPropertyOfUnionType() {
    doTest("int", """
      def f():
          '''
          :rtype: int or slice
          '''
          raise NotImplementedError

      x = f()
      expr = x.bit_length()
      """);
  }

  public void testUndefinedPropertyOfUnionType() {
    doTest("Any", """
      x = 42 if True else 'spam'
      expr = x.foo
      """);
  }

  // PY-7058
  public void testReturnTypeOfTypeForInstance() {
    PyExpression expr = parseExpr("""
                                    class C(object):
                                        pass

                                    x = C()
                                    expr = type(x)
                                    """);
    assertNotNull(expr);
    for (TypeEvalContext context : getTypeEvalContexts(expr)) {
      PyType type = context.getType(expr);
      assertInstanceOf(type, PyClassType.class);
      assertTrue("Got instance type instead of class type", ((PyClassType)type).isDefinition());
    }
  }

  // PY-7058
  public void testReturnTypeOfTypeForClass() {
    doTest("type", """
      class C(object):
          pass

      expr = type(C)
      """);
  }

  // PY-7058
  public void testReturnTypeOfTypeForUnknown() {
    doTest("Any", """
      def f(x):
          expr = type(x)
      """);
  }

  // PY-7040
  public void testInstanceAndClassAttribute() {
    doTest("int",
           """
             class C(object):
                 foo = 'str1'

                 def __init__(self):
                     self.foo = 3
                     expr = self.foo
             """);
  }

  // PY-7215
  public void testFunctionWithNestedGenerator() {
    doTest("List[int]",
           """
             def f():
                 def g():
                     yield 10
                 return list(g())

             expr = f()
             """);
  }

  public void testGeneratorNextType() {
    doTest("int",
           """
             def f():
                 yield 10
             expr = f().next()
             """);
  }

  public void testGeneratorFunctionType() {
    doTest("Generator[str, Any, int]",
           """
             def f():
                 yield 'foo'
                 return 0

             expr = f()
             """);
  }

  // PY-7020
  public void testListComprehensionType() {
    doTest("List[str]", "expr = [str(x) for x in range(10)]\n");
  }

  // PY-7021
  public void testGeneratorComprehensionType() {
    doTest("Generator[str, Any, None]", "expr = (str(x) for x in range(10))\n");
  }

  // PY-7021
  public void testIterOverGeneratorComprehension() {
    doTest("str",
           """
             xs = (str(x) for x in range(10))
             for expr in xs:
                 pass
             """);
  }

  // EA-40207
  public void testRecursion() {
    doTest("List[Any]",
           """
             def f():
                 return [f()]
             expr = f()
             """);
  }

  // PY-5084
  public void testIfIsInstanceElse() {
    doTest("str",
           """
             def test(c):
                 x = 'foo' if c else 42
                 if isinstance(x, int):
                     print(x)
                 else:
                     expr = x
             """);
  }

  // PY-5614
  public void testUnknownReferenceTypeAttribute() {
    doTest("str",
           """
             def f(x):
                 if isinstance(x.foo, str):
                     expr = x.foo
             """);
  }

  // PY-5614
  public void testUnknownTypeAttribute() {
    doTest("str",
           """
             class C(object):
                 def __init__(self, foo):
                     self.foo = foo
                 def f(self):
                     if isinstance(self.foo, str):
                         expr = self.foo
             """);
  }

  // PY-5614
  public void testKnownTypeAttribute() {
    doTest("str",
           """
             class C(object):
                 def __init__(self):
                     self.foo = 42
                 def f(self):
                     if isinstance(self.foo, str):
                         expr = self.foo
             """);
  }

  // PY-5614
  public void testNestedUnknownReferenceTypeAttribute() {
    doTest("str",
           """
             def f(x):
                 if isinstance(x.foo.bar, str):
                     expr = x.foo.bar
             """);

  }

  // PY-7063
  public void testDefaultParameterValue() {
    doTest("int",
           """
             def f(x, y=0):
                 return y
             expr = f(a, b)
             """);
  }

  public void testLogicalAndExpression() {
    doTest("Union[str, int]",
           "expr = 'foo' and 2");
  }

  public void testLogicalNotExpression() {
    doTest("bool",
           "expr = not 'hello'");
  }

  // PY-7063
  public void testDefaultParameterIgnoreNone() {
    doTest("Any", """
      def f(x=None):
          expr = x
      """);
  }

  public void testParameterFromUsages() {
    RecursionManager.assertOnRecursionPrevention(myFixture.getTestRootDisposable());
    Registry.get("python.use.better.control.flow.type.inference").setValue(true);
    final String text = """
      def foo(bar):
          expr = bar
      def use_foo(x):
          foo(x)
          foo(3)
          foo('bar')
      """;
    final PyExpression expr = parseExpr(text);
    assertNotNull(expr);
    doTest("UnsafeUnion[Union[int, str], Any]", expr, TypeEvalContext.codeCompletion(expr.getProject(), expr.getContainingFile()));
  }

  public void testUpperBoundGeneric() {
    doTest("UnsafeUnion[Union[int, str], Any]",
           """
             def foo(x):
                 '''
                 :type x: T <= int or str
                 :rtype: T
                 '''
             def bar(x):
                 expr = foo(x)
             """);
  }

  public void testIterationTypeFromGetItem() {
    doTest("int",
           """
             class C(object):
                 def __getitem__(self, index):
                     return 0
                 def __len__(self):
                     return 10
             for expr in C():
                 pass
             """);
  }

  public void testFunctionTypeAsUnificationArgument() {
    doTest("Union[List[int], str, unicode]",
           """
             def map2(f, xs):
                 '''
                 :type f: (T) -> V | None
                 :type xs: collections.Iterable[T] | str | unicode
                 :rtype: list[V] | str | unicode
                 '''
                 pass

             expr = map2(lambda x: 10, ['1', '2', '3'])
             """);
  }

  public void testFunctionTypeAsUnificationArgumentWithSubscription() {
    doTest("Union[int, str, unicode]",
           """
             def map2(f, xs):
                 '''
                 :type f: (T) -> V | None
                 :type xs: collections.Iterable[T] | str | unicode
                 :rtype: list[V] | str | unicode
                 '''
                 pass

             expr = map2(lambda x: 10, ['1', '2', '3'])[0]
             """);
  }

  public void testFunctionTypeAsUnificationResult() {
    doTest("int",
           """
             def f(x):
                 '''
                 :type x: T
                 :rtype: () -> T
                 '''
                 pass

             g = f(10)
             expr = g()
             """);
  }

  public void testUnionIteration() {
    doTest("Union[Union[int, str], Any]",
           """
             def f(c):
                 if c < 0:
                     return [1, 2, 3]
                 elif c == 0:
                     return 0.0
                 else:
                     return 'foo'

             def g(c):
                 for expr in f(c):
                     pass
             """);
  }

  public void testParameterOfFunctionTypeAndReturnValue() {
    doTest("int",
           """
             def func(f):
                 '''
                 :type f: (unknown) -> str
                 '''
                 return 1

             expr = func(foo)
             """);
  }

  // PY-6584
  public void testClassAttributeTypeInClassDocStringViaClass() {
    doTest("int",
           """
             class C(object):
                 '''
                 :type foo: int
                 '''
                 foo = None

             expr = C.foo
             """);
  }

  // PY-6584
  public void testClassAttributeTypeInClassDocStringViaInstance() {
    doTest("int",
           """
             class C(object):
                 '''
                 :type foo: int
                 '''
                 foo = None

             expr = C().foo
             """);
  }

  // PY-6584
  public void testInstanceAttributeTypeInClassDocString() {
    doTest("int",
           """
             class C(object):
                 '''
                 :type foo: int
                 '''
                 def __init__(self, bar):
                     self.foo = bar

             def f(x):
                 expr = C(x).foo
             """);
  }

  public void testOpenDefault() {
    doTest("BinaryIO",
           "expr = open('foo')\n");
  }

  public void testOpenText() {
    doTest("BinaryIO",
           "expr = open('foo', 'r')\n");
  }

  public void testOpenBinary() {
    doTest("BinaryIO",
           "expr = open('foo', 'rb')\n");
  }

  public void testNoResolveToFunctionsInTypes() {
    doTest("Union[C, Any]",
           """
             class C(object):
                 def bar(self):
                     pass

             def foo(x):
                 '''
                 :type x: C | C.bar | foo
                 '''
                 expr = x
             """);
  }

  public void testIsInstanceExpressionResolvedToTuple() {
    doTest("Union[str, unicode]",
           """
             string_types = str, unicode

             def f(x):
                 if isinstance(x, string_types):
                     expr = x
             """);
  }

  public void testIsInstanceInConditionalExpression() {
    doTest("Union[str, int]",
           """
             def f(x):
                 expr = x if isinstance(x, str) else 10
             """);
  }

  // PY-9334
  public void testIterateOverListOfNestedTuples() {
    doTest("str",
           """
             def f():
                 for i, (expr, v) in [(0, ('foo', []))]:
                     print(expr)
             """);
  }

  // PY-8953
  public void testSelfInDocString() {
    doTest("int",
           """
             class C(object):
                 def foo(self):
                     '''
                     :type self: int
                     '''
                     expr = self
             """);
  }

  // PY-9605
  public void testPropertyReturnsCallable() {
    doTest("() -> int",
           """
             class C(object):
                 @property
                 def foo(self):
                     return lambda: 0

             c = C()
             expr = c.foo
             """);
  }

  public void testIterNext() {
    doTest("int",
           """
             xs = [1, 2, 3]
             expr = iter(xs).next()
             """);
  }

  // PY-10967
  public void testDefaultTupleParameterMember() {
    doTest("int",
           """
             def foo(xs=(1, 2)):
               expr, foo = xs
             """);
  }

  // PY-19826
  public void testListFromTuple() {
    doTest("List[Union[str, int]]",
           "expr = list(('1', 2, 3))");
  }

  public void testDictFromTuple() {
    doTest("Dict[Union[str, int], Union[int, str]]",
           "expr = dict((('1', 1), (2, 2), (3, '3')))");
  }

  public void testSetFromTuple() {
    doTest("Set[Union[str, int]]",
           "expr = set(('1', 2, 3))");
  }

  public void testTupleFromTuple() {
    doTest("Tuple[str, int, int]",
           "expr = tuple(('1', 2, 3))");
  }

  public void testTupleFromList() {
    doTest("Tuple[Union[str, int], ...]",
           "expr = tuple(['1', 2, 3])");
  }

  public void testTupleFromDict() {
    doTest("Tuple[Union[str, int], ...]",
           "expr = tuple({'1': 'a', 2: 'b', 3: 4})");
  }

  public void testTupleFromSet() {
    doTest("Tuple[Union[str, int], ...]",
           "expr = tuple({'1', 2, 3})");
  }

  public void testHomogeneousTupleSubstitution() {
    runWithLanguageLevel(
      LanguageLevel.PYTHON35,
      () -> doTest("Tuple[int, ...]",
                   """
                     from typing import TypeVar, Tuple
                     T = TypeVar('T')
                     def foo(i: T) -> Tuple[T, ...]:
                         pass
                     expr = foo(5)""")
    );
  }

  public void testHeterogeneousTupleSubstitution() {
    doTest("tuple[int, int]",
           """
             def foo(i):
                 ""\"
                 :type i: T
                 :rtype: tuple[T, T]
                 ""\"
                 pass
             expr = foo(5)""");
  }

  public void testUnknownTupleSubstitution() {
    doTest("tuple",
           """
             def foo(i):
                 ""\"
                 :type i: T
                 :rtype: tuple
                 ""\"
                 pass
             expr = foo(5)""");
  }

  public void testTupleIterationType() {
    doTest("Union[int, str]",
           """
             xs = (1, 'a')
             for expr in xs:
                 pass
             """);
  }

  // PY-12801
  public void testTupleConcatenation() {
    doTest("Tuple[int, bool, str]",
           "expr = (1,) + (True, 'spam') + ()");
  }

  public void testTupleMultiplication() {
    doTest("Tuple[int, bool, int, bool]",
           "expr = (1, False) * 2");
  }


  public void testTupleDestructuring() {
    doTest("str",
           "_, expr = (1, 'val') ");
  }

  public void testParensTupleDestructuring() {
    doTest("str",
           "(_, expr) = (1, 'val') ");
  }

  // PY-19825
  public void testSubTupleDestructuring() {
    doTest("str",
           "(a, (_, expr)) = (1, (2,'val')) ");
  }

  // PY-19825
  public void testSubTupleIndirectDestructuring() {
    doTest("str",
           "xs = (2,'val')\n" +
           "(a, (_, expr)) = (1, xs) ");
  }

  // PY-38928
  public void testIterateListOfTuples() {
    doTest(
      "str",
      """
        for ((_, expr)) in [(1, 'foo')]:
            pass
        """
    );
  }

  public void testConstructorUnification() {
    doTest("C[int]",
           """
             class C(object):
                 def __init__(self, x):
                     '''
                     :type x: T
                     :rtype: C[T]
                     '''
                     pass

             expr = C(10)
             """);
  }

  public void testGenericClassMethodUnification() {
    doTest("int",
           """
             class C(object):
                 def __init__(self, x):
                     '''
                     :type x: T
                     :rtype: C[T]
                     '''
                     pass
                 def foo(self):
                     '''
                     :rtype: T
                     '''
                     pass

             expr = C(10).foo()
             """);
  }

  public void testGenericFunctionsUseSameTypeParameter() {
    doTest("int",
           """
             def id(x):
                 ""\"
                 :type x: T
                 :rtype: T
                 ""\"
                 return x

             def f3(x):
                 ""\"
                 :type x: T
                 ""\"
                 return id(x)
             expr = f3(42)""");
  }

  // PY-9439
  public void testNumpyArrayType() {
    doMultiFileTest("ndarray",
                    """
                      import numpy as np
                      expr = np.array([1,2,3])
                      """);
  }

  public void testUnionTypeAttributeOfDifferentTypes() {
    doTest("Union[List[Any], int]",
           """
             class Foo:
                 x = []

             class Bar:
                 x = 42

             def f(c):
                 o = Foo() if c else Bar()
                 expr = o.x
             """);
  }

  // PY-11364
  public void testUnionTypeAttributeCallOfDifferentTypes() {
    doTest("Union[C1, C2]",
           """
             class C1:
                 def foo(self):
                     return self

             class C2:
                 def foo(self):
                     return self

             def f():
                 '''
                 :rtype: C1 | C2
                 '''
                 pass

             expr = f().foo()
             """);
  }

  // PY-12862
  public void testUnionTypeAttributeSubscriptionOfDifferentTypes() {
    doTest("Union[C1, C2]",
           """
             class C1:
                 def __getitem__(self, item):
                     return self

             class C2:
                 def __getitem__(self, item):
                     return self

             def f():
                 '''
                 :rtype: C1 | C2
                 '''
                 pass

             expr = f()[0]
             print(expr)
             """);
  }

  // PY-11541
  public void testIsInstanceBaseStringCheck() {
    doTest("Union[str, unicode]",
           """
             def f(x):
                 if isinstance(x, basestring):
                     expr = x
             """);
  }

  public void testStructuralType() {
    doTest("{foo, bar}",
           """
             def f(x):
                 x.foo + x.bar()
                 expr = x
             """);
  }

  public void testOnlyRelatedNestedAttributes() {
    doTest("{foo}",
           """
             def g(x):
                 x.bar

             def f(x, y):
                 x.foo + g(y)
                 expr = x
             """);
  }

  public void testNoContainsInContainsArgumentForStructuralType() {
    doTest("{foo, __getitem__}",
           """
             def f(x):
                x in []
                x.foo
                x[0]   expr = x
             """);
  }

  public void testStructuralTypeAndIsInstanceChecks() {
    doTest("(x: {foo}) -> None",
           """
             def f(x):
                 if isinstance(x, str):
                     x.lower()
                 x.foo

             expr = f
             """);
  }

  // PY-20832
  public void testStructuralTypeWithDunderIter() {
    doTest("{__iter__}",
           """
             def expand(values1):
                 for a in values1:
                     print(a)
                 expr = values1
             """);
  }

  // PY-20833
  public void testStructuralTypeWithDunderLen() {
    doTest("{__len__}",
           """
             def expand(values1):
                 a = len(values1)
                 expr = values1
             """);
  }

  // PY-16267
  public void testGenericField() {
    doTest("str",
           """
             class D(object):
                 def __init__(self, foo):
                     '''
                     :type foo: T
                     :rtype: D[T]
                     '''
                     self.foo = foo


             def g():
                 '''
                 :rtype: D[str]
                 '''
                 return D('test')


             y = g()
             expr = y.foo
             """);
