// Copyright 2000-2017 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.inspections;

import com.intellij.openapi.util.RecursionManager;
import com.intellij.openapi.util.StackOverflowPreventedException;
import com.jetbrains.python.fixtures.PyInspectionTestCase;
import com.jetbrains.python.psi.LanguageLevel;
import org.jetbrains.annotations.NotNull;

public class Py3TypeCheckerInspectionTest extends PyInspectionTestCase {
  public static final String TEST_DIRECTORY = "inspections/PyTypeCheckerInspection/";

  @NotNull
  @Override
  protected Class<? extends PyInspection> getInspectionClass() {
    return PyTypeCheckerInspection.class;
  }

  @Override
  protected boolean isLowerCaseTestFile() {
    return false;
  }

  @Override
  protected String getTestCaseDirectory() {
    return TEST_DIRECTORY;
  }

  // PY-9289
  public void testWithOpenBinaryPy3() {
    doTest();
  }

  // PY-72232
  public void testWithItemNonContextManager() {
    doTest();
  }

  // PY-10660
  public void testStructUnpackPy3() {
    doMultiFileTest();
  }

  public void testBuiltinsPy3() {
    doTest();
  }

  // PY-16125
  public void testTypingIterableForLoop() {
    doTest();
  }

  // PY-16146
  public void testTypingListSubscriptionExpression() {
    doTest();
  }

  // PY-16855
  public void testTypingTypeVarWithUnresolvedBound() {
    doTest();
  }

  // PY-16898
  public void testAsyncForIterable() {
    doTest();
  }

  // PY-6729
  public void testYieldFromNonIterable() {
    doTest();
  }

  // PY-18275
  public void testStrFormatPy3() {
    doTest();
  }

  // PY-18762
  public void testHomogeneousTuples() {
    doTest();
  }

  // PY-9924
  public void testTupleGetItemWithSlice() {
    doTest();
  }

  // PY-9924
  public void testListGetItemWithSlice() {
    doTest();
  }

  // PY-20460
  public void testStringGetItemWithSlice() {
    doTest();
  }

  // PY-20460
  public void testBytesGetItemWithSlice() {
    doTest();
  }

  // PY-19796
  public void testOrd() {
    doTest();
  }

  // PY-12944
  public void testDelegatedGenerator() {
    doTest();
  }

  // PY-16055
  public void testFunctionReturnTypePy3() {
    doTest();
  }

  public void testFunctionYieldTypePy3() {
    doTest();
  }

  // PY-20770
  public void testAsyncForOverAsyncGenerator() {
    doTest();
  }

  // PY-20770
  public void testForOverAsyncGenerator() {
    doTest();
  }

  // PY-20770
  public void testAsyncComprehensionsOverAsyncGenerator() {
    doTest();
  }

  // PY-20770
  public void testAsyncComprehensionsOverGenerator() {
    doTest();
  }

  // PY-20770
  public void testComprehensionsOverAsyncGenerator() {
    doTest();
  }

  // PY-20769
  public void testPathLikePassedToStdlibFunctions() {
    doTest();
  }

  // PY-21048
  public void testAsyncFunctionReturnType() {
    doTest();
  }

  // PY-20967
  public void testAsyncFunctionAnnotatedToReturnNone() {
    doTest();
  }

  // PY-20709
  public void testGeneratorReturnType() {
    doTest();
  }

  // PY-20657, PY-21916
  public void testGeneratorAnnotatedToReturnIterable() {
    doTest();
  }

  // PY-20657, PY-21916
  public void testAsyncGeneratorAnnotatedToReturnAsyncIterable() {
    doTest();
  }

  // PY-21083
  public void testFloatFromhex() {
    doTest();
  }

  // PY-20073
  public void testMapArgumentsInOppositeOrderPy3() {
    doTest();
  }

  // PY-21350
  public void testBuiltinInputPy3() {
    doTest();
  }

  // PY-200057
  public void testClassObjectType() {
    doTest();
  }

  // PY-20057
  public void testTypeAndClassObjectTypesCompatibility() {
    doTest();
  }

  // PY-20057
  public void testClassObjectTypeWithUnion() {
    doTest();
  }

  // PY-22730
  public void testOptionalOfBoundTypeVarInWarnings() {
    doTest();
  }

  // PY-22769
  public void testReplaceCalledOnUnionOfStrAndBytesWithStrArguments() {
    doTest();
  }

  // PY-23053
  public void testUnboundTypeVarsMatchClassObjectTypes() {
    doTest();
  }

  // PY-22513
  public void testGenericKwargs() {
    doTest();
  }

  public void testTypingNamedTupleAsParameter() {
    doTest();
  }

  // PY-17962
  public void testTypingCallableCall() {
    doTest();
  }

  // PY-23057
  public void testEllipsisInFunctionWithSpecifiedReturnType() {
    doTest();
  }

  // PY-23239, PY-23253
  public void testInitializingTypingNamedTuple() {
    doTest();
  }

  // PY-21302
  public void testInitializingNewType() {
    doTest();
  }

  // PY-21302
  public void testNewTypeAsParameter() {
    doTest();
  }

  // PY-21302
  public void testNewTypeInheritance() {
    doTest();
  }

  // PY-24287
  public void testPromotingBytearrayToBytes() {
    doTest();
  }

  // PY-25045
  public void testUnionOfIntAndFloatShouldBeConsideredAsDividable() {
    doTest();
  }

  // PY-23289
  // PY-23391
  // PY-24194
  // PY-24789
  public void testTypingSupports() {
    doTest();
  }

  // PY-25994
  public void testUnresolvedReceiverGeneric() {
    doTest();
  }

  public void testMatchingOpenFunctionCallTypesPy3() {
    doMultiFileTest();
  }

  public void testChainedComparisonsGenericMatching() {
    doTest();
  }

  // PY-27398
  public void testInitializingDataclass() {
    doMultiFileTest();
  }

  // PY-28442
  public void testDataclassClsCallType() {
    doMultiFileTest();
  }

  // PY-26354
  public void testInitializingAttrs() {
    runWithAdditionalClassEntryInSdkRoots(
      "packages",
      () -> doTestByText(
        """
          import attr
          import typing
          
          @attr.s
          class Weak1:
              x = attr.ib()
              y = attr.ib(default=0)
              z = attr.ib(default=attr.Factory(list))
             \s
          Weak1(1, <warning descr="Expected type 'int', got 'str' instead">"str"</warning>, <warning descr="Expected type 'list', got 'int' instead">2</warning>)
          
          
          @attr.s
          class Weak2:
              x = attr.ib()
             \s
              @x.default
              def __init_x__(self):
                  return 1
             \s
          Weak2("str")
          
          
          @attr.s
          class Strong:
              x = attr.ib(type=int)
              y = attr.ib(default=0, type=int)
              z = attr.ib(default=attr.Factory(list), type=typing.List[int])
             \s
          Strong(1, <warning descr="Expected type 'int', got 'str' instead">"str"</warning>, <warning descr="Expected type 'list[int]', got 'list[str]' instead">["str"]</warning>)"""
      )
    );
  }

  // PY-28957
  public void testDataclassesReplace() {
    doMultiFileTest();
  }

  // PY-28127 PY-31424
  public void testInitializingTypeVar() {
    doTest();
  }

  // PY-24832
  public void testAssignment() {
    doTest();
  }

  // PY-24832
  public void testReAssignment() {
    doTest();
  }

  // PY-24832
  public void testTypeCommentAssignment() {
    doTest();
  }

  // PY-24832
  public void testTypeDeclarationAndAssignment() {
    doTest();
  }

  // PY-24832
  public void testClassLevelAssignment() {
    doTest();
  }

  // PY-36889
  public void testInstanceAndClassAttributeAssignment() {
    doTestByText("""
                   from typing import ClassVar
                   
                   class ClassAnnotations:
                       attr: int
                       class_attr: ClassVar[int]
                   
                   ClassAnnotations().attr = <warning descr="Expected type 'int', got 'str' instead">"foo"</warning>
                   ClassAnnotations.class_attr = <warning descr="Expected type 'int', got 'str' instead">"foo"</warning>
                   
                   class ClassAnnotationInstanceAssignment:
                       attr: int
                       def __init__(self, x):
                           self.attr = x
                   
                   ClassAnnotationInstanceAssignment(42).attr = <warning descr="Expected type 'int', got 'str' instead">"foo"</warning>
                   
                   class InstanceAnnotationAndAssignment:
                       def __init__(self):
                           self.attr: int = 42
                   
                   InstanceAnnotationAndAssignment().attr = <warning descr="Expected type 'int', got 'str' instead">"foo"</warning>
                   """);
  }

  // PY-36889
  public void testDataclassInstanceAssignment() {
    doTestByText("""
                   from dataclasses import dataclass
                   
                   @dataclass
                   class C:
                       attr: int
                   
                   C().attr = <warning descr="Expected type 'int', got 'str' instead">"foo"</warning>
                   """);
  }

  // PY-24832
  public void testNoTypeMismatchInAssignmentWithoutTypeAnnotation() {
    doTest();
  }

  // PY-35235
  public void testTypingLiteralStrings() {
    doTestByText("""
                   from typing_extensions import Literal
                   
                   a: Literal["abc"] = undefined
                   b: Literal[b"abc"] = undefined
                   
                   def foo1(p1: Literal["abc"]):
                       pass
                   foo1(a)
                   foo1(<warning descr="Expected type 'Literal[\\"abc\\"]', got 'Literal[b\\"abc\\"]' instead">b</warning>)
                   
                   def foo2(p1: Literal[b"abc"]):
                       pass
                   foo2(<warning descr="Expected type 'Literal[b\\"abc\\"]', got 'Literal[\\"abc\\"]' instead">a</warning>)
                   foo2(b)
                   
                   def foo3(p1: str):
                       pass
                   foo3(a)
                   foo3(<warning descr="Expected type 'str', got 'Literal[b\\"abc\\"]' instead">b</warning>)
                   
                   def foo4(p1: bytes):
                       pass
                   foo4(<warning descr="Expected type 'bytes', got 'Literal[\\"abc\\"]' instead">a</warning>)
                   foo4(b)
                   """);
  }

  // PY-46385
  public void testAliasingEnumClassNameInLiteralType() {
    doTestByText("""
                   from enum import Enum
                   from typing import Literal
                   
                   class Colors(Enum):
                       RED = 1
                       GREEN = 1
                       BLUE = 3
                   
                   AliasColors = Colors
                   
                   x: AliasColors = Colors.RED
                   y: Literal[Colors.RED] = <warning descr="Expected type 'Literal[Colors.RED]', got 'Literal[Colors.GREEN]' instead">Colors.GREEN</warning>
                   z: Literal[AliasColors.RED] = Colors.RED""");
  }

  // PY-46385
  public void testAliasingEnumMemberNameInLiteralType() {
    doTestByText("""
                   from enum import Enum
                   from typing import Literal
                   
                   class Colors(Enum):
                       RED = 1
                       GREEN = 2
                       BLUE = 3
                   
                   SpecialColors = Literal[Colors.RED]
                   
                   def special_painter(color: SpecialColors):
                       assert color == Colors.RED
                   
                   special_painter(<warning descr="Expected type 'Literal[Colors.RED]', got 'Literal[Colors.GREEN]' instead">Colors.GREEN</warning>)
                   
                   costs: dict[SpecialColors, int] = <warning descr="Expected type 'dict[Literal[Colors.RED], int]', got 'dict[Literal[Colors.GREEN], Literal[7]]' instead">{Colors.GREEN: 7}</warning>""");
  }

  public void testEnumMemberAlias() {
    doTestByText(
      """
        from enum import Enum
        from typing import Literal
        
        class Color(Enum):
            RED = 1
            R = RED
        
        x: Literal[Color.RED]
        x = Color.R"""
    );
  }

  public void testTypeNarrowingIsOrEquals() {
    doTestByText(
      """
        from enum import Enum
        from typing import Literal
        
        class Color(Enum):
            R = 1
            G = 2
            B = 3
            RED = R
            GREEN = G
            BLUE = B
        
        def foo(v: Color | str) -> None:
            if v is Color.RED:
                r: Literal[Color.R] = v
            elif v == Color.G:
                g: Literal[Color.G] = v
            elif v is Color.B:
                b: Literal[Color.B] = v
            else:
                s: str = v
                c: Color = <warning descr="Expected type 'Color', got 'str' instead">v</warning>
        
            if v is Color.BLUE or isinstance(v, str):
                pass
            else:
                s: str = <warning descr="Expected type 'str', got 'Literal[Color.R, Color.G]' instead">v</warning>
        
        def bar(v: Literal[Color.R, "1"]) -> None:
            if isinstance(v, Color):
                r: Literal[Color.R] = v
            else:
                s: Literal["1"] = v
                c: Color = <warning descr="Expected type 'Color', got 'Literal[\\"1\\"]' instead">v</warning>
        
        def buz(v: Color):
            if v is not Color.B and v != Color.RED:
                g: Literal[Color.G] = v
                s: str = <warning descr="Expected type 'str', got 'Literal[Color.G]' instead">v</warning>
        """
    );
  }

  // PY-79164
  public void testTypeNarrowingIn() {
    doTestByText("""
                   from typing import Literal
                   
                   def expects_bad_status(status: Literal["MALFORMED", "ABORTED"]): ...
                   
                   def expects_pending_status(status: Literal["PENDING"]): ...
                   
                   def parse_status(status: str) -> None:
                       if status in ("MALFORMED", "ABORTED"):
                           return expects_bad_status(status)
                   
                       if status == "PENDING":
                           expects_pending_status(status)
                   """);
    doTestByText("""
                   from typing import Literal
                   from enum import Enum
                   
                   class Color(Enum):
                       R = 1
                       G = 2
                       B = 3
                       RED = R
                       BLUE = B
                   
                   def expects_red_or_blue(v: Literal[Color.RED, Color.B]): ...
                   
                   def expects_green(v: Literal[Color.G]): ...
                   
                   def foo(v: Color):
                       if v in (Color.R, Color.BLUE):
                           expects_red_or_blue(v)
                       else:
                           expects_green(v)
                   
                       if v not in (Color.R, Color.BLUE):
                           expects_green(v)
                       else:
                           expects_red_or_blue(v)
                   """);

    doTestByText("""
                   from enum import Enum
                   
                   class MyEnum(Enum):
                       A = 1
                       B = 2
                       C = 3
                       D = 4
                   
                   def foo(v: MyEnum):
                       if v == MyEnum.A:
                           pass
                       elif v in (MyEnum.B, MyEnum.C):
                           b_or_c: Literal[MyEnum.B, MyEnum.C] = v
                           s: int = <warning descr="Expected type 'int', got 'Literal[MyEnum.B, MyEnum.C]' instead">v</warning>
                       else:
                           d: Literal[MyEnum.D] = v
                           s: int = <warning descr="Expected type 'int', got 'Literal[MyEnum.D]' instead">v</warning>
                   """);
  }

  // PY-77937
  public void testListOfEnumMembers() {
    doTestByText(
      """
        from enum import Enum
        
        class Direction(Enum):
            NORTH = "N"
            SOUTH = "S"
            EAST = "E"
            WEST = "W"
            LEFT = "L"
            RIGHT = "R"
            FORWARD = "F"
        
        CARTESIAN = [Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST]
        
        def index(d: Direction) -> None:
            print(CARTESIAN.index(d))
        """
    );
  }

  // PY-80195
  public void testMultiValueEnum() {
    doMultiFileTest();
  }

  // PY-42418
  public void testParametrizedBuiltinCollectionsAndTheirTypingAliasesAreEquivalent() {
    doTest();
  }

  // PY-42418
  public void testParametrizedBuiltinTypeAndTypingTypeAreEquivalent() {
    doTest();
  }

  // PY-30747
  public void testPathlibPathMatchingOsPathLike() {
    doTestByText(
      """
        import pathlib
        import os
        
        def foo(p: pathlib.Path):
            with open(p) as file:
                pass
        
        p1: pathlib.Path
        p2: os.PathLike[bytes] = <warning descr="Expected type 'PathLike[bytes]', got 'Path' instead">p1</warning>
        p3: os.PathLike[str] = p1"""
    );
  }

  // PY-41847
  public void testTypingAnnotatedType() {
    doTestByText("""
                   from typing import Annotated
                   A = Annotated[bool, 'Some constraint']
                   a: A = <warning descr="Expected type 'bool', got 'str' instead">'str'</warning>
                   b: A = True
                   c: Annotated[bool, 'Some constraint'] = <warning descr="Expected type 'bool', got 'str' instead">'str'</warning>
                   d: Annotated[str, 'Some constraint'] = 'str'
                   """);
  }

  // PY-41847
  public void testTypingAnnotatedTypeMultiFile() {
    doMultiFileTest();
  }

  // PY-43838
  public void testParameterizedClassAgainstType() {
    doTestByText("""
                   from typing import Type, Any, List
                   
                   def my_function(param: Type[Any]):
                       pass
                   
                   my_function(List[str])""");
  }

  // PY-43838
  public void testUnionAgainstType() {
    doTestByText("""
                   from typing import Type, Any, Union
                   
                   def my_function(param: Type[Any]):
                       pass
                   
                   my_function(Union[int, str])""");
  }

  // PY-44575
  public void testArgsCallableAgainstOneParameterCallable() {
    doTestByText("""
                   from typing import Any, Callable, Iterable, TypeVar
                   _T1 = TypeVar("_T1")
                   def mymap(c: Callable[[_T1], Any], i: Iterable[_T1]) -> Iterable[_T1]:
                     pass
                   def myfoo(*args: int) -> int:
                     pass
                   mymap(myfoo, [1, 2, 3])
                   """);
  }

  public void testCallableWithTypeGuards() {
    doTestByText("""
                   from typing import Any, Callable
                   from typing_extensions import TypeIs
                   
                   def foo(c: Callable[[Any], TypeIs[int]]):
                      ...
                   
                   def is_str(x: Any) -> TypeIs[str]:
                      ...
                   
                   foo(<warning descr="Expected type '(Any) -> TypeIs[int]', got '(x: Any) -> TypeIs[str]' instead">is_str</warning>)
                   """);
  }

  public void testCallableWithTypeGuards2() {
    doTestByText("""
                   from typing import Any, Callable
                   from typing_extensions import TypeIs
                   
                   def foo(c: Callable[[Any], TypeIs[str]]):
                      ...
                   
                   def is_str(x: Any) -> TypeIs[str]:
                      ...
                   
                   foo(is_str)
                   """);
  }

  public void testCallableWithTypeGuards3() {
    doTestByText("""
                   from typing import Any, Callable
                   from typing_extensions import TypeIs
                   
                   class B:
                      ...
                   
                   class D(B):
                      ...
                   
                   def foo(c: Callable[[Any], TypeIs[D]]):
                      ...
                   
                   def is_str(x: Any) -> TypeIs[B]:
                      ...
                   
                   foo(<warning descr="Expected type '(Any) -> TypeIs[D]', got '(x: Any) -> TypeIs[B]' instead">is_str</warning>)
                   """);
  }

  public void testCallableWithTypeGuards4() {
    doTestByText("""
                   from typing import Any, Callable
                   from typing_extensions import TypeIs
                   
                   class B:
                      ...
                   
                   class D(B):
                      ...
                   
                   def foo(c: Callable[[Any], TypeIs[B]]):
                      ...
                   
                   def is_str(x: Any) -> TypeIs[D]:
                      ...
                   foo(<warning descr="Expected type '(Any) -> TypeIs[B]', got '(x: Any) -> TypeIs[D]' instead">is_str</warning>)
                   """);
  }

  public void testTypeIsAndTypeGuardAreNotAssignableToEachOther() {
    doTestByText("""
                   from typing import Callable, TypeGuard, TypeIs
                   
                   def takes_typeguard(f: Callable[[object], TypeGuard[int]]) -> None:
                       pass
                   
                   def takes_typeis(f: Callable[[object], TypeIs[int]]) -> None:
                       pass
                   
                   def is_int_typeis(val: object) -> TypeIs[int]:
                       return isinstance(val, int)
                   
                   def is_int_typeguard(val: object) -> TypeGuard[int]:
                       return isinstance(val, int)
                   
                   takes_typeguard(is_int_typeguard)
                   takes_typeguard(<warning descr="Expected type '(object) -> TypeGuard[int]', got '(val: object) -> TypeIs[int]' instead">is_int_typeis</warning>)
                   takes_typeis(<warning descr="Expected type '(object) -> TypeIs[int]', got '(val: object) -> TypeGuard[int]' instead">is_int_typeguard</warning>)
                   takes_typeis(is_int_typeis)
                   """);
  }

  // test is broken, type variable in TypeIs is invariant, so TypeIs[B] and TypeIs[D] are not consistent
  public void testCallableBoolean() {
    doTestByText("""
                   from typing import Any, Callable
                   from typing_extensions import TypeIs
                   
                   def foo(c: bool):
                      ...
                   
                   def is_int(x: Any) -> TypeIs[int]:
                      ...
                   
                   foo(is_int(1))
                   """);
  }

  public void testCallableStr() {
    doTestByText("""
                   from typing import Any, Callable
                   from typing_extensions import TypeIs
                   
                   def foo(c: str):
                      ...
                   
                   def is_int(x: Any) -> TypeIs[int]:
                      ...
                   
                   foo(<warning descr="Expected type 'str', got 'TypeIs[int]' instead">is_int(1)</warning>)
                   """);
  }

  public void testCallableInt() {
    doTestByText("""
                   from typing import Any, Callable
                   from typing_extensions import TypeIs
                   
                   def foo(c: int):
                      ...
                   
                   def is_int(x: Any) -> TypeIs[int]:
                      ...
                   
                   foo(is_int(1))
                   """);
  }

  // PY-44974
  public void testBitwiseOrUnionNoneIntStrAssignList() {
    doTestByText("bar: None | int | str = <warning descr=\"Expected type 'None | int | str', got 'list[int]' instead\">[42]</warning>");
  }

  // PY-44974
  public void testParenthesizedBitwiseOrUnionOfUnionsAssignNone() {
    doTestByText(
      "bar: int | ((list | dict) | (float | str)) = <warning descr=\"Expected type 'int | list | dict | float | str', got 'None' instead\">None</warning>");
  }

  // PY-44974
  public void testTypingAndTypesBitwiseOrUnionDifference() {
    doTestByText("""
                   from typing import Type
                   def foo(x: Type[int | str]):
                       pass
                   foo(<warning descr="Expected type 'type[int | str]', got 'UnionType | type[int] | type[str]' instead">int | str</warning>)""");
  }

  // PY-44974
  public void testBitwiseOrUnionsAndOldStyleUnionsAreEquivalent() {
    doTest();
  }

  // PY-49935
  public void testParamSpecExample() {
    doTestByText("""
                   from typing import Callable, ParamSpec
                   
                   P = ParamSpec("P")
                   
                   
                   def changes_return_type_to_str(x: Callable[P, int]) -> Callable[P, str]: ...
                   
                   
                   def returns_int(a: str, b: bool) -> int:
                       return 42
                   
                   
                   changes_return_type_to_str(returns_int)("42", <warning descr="Expected type 'bool', got 'int' instead">42</warning>)""");
  }

  // PY-49935
  public void testParamSpecUserGenericClassMethod() {
    doTestByText("""
                   from typing import TypeVar, Generic, Callable, ParamSpec
                   
                   U = TypeVar("U")
                   P = ParamSpec("P")
                   
                   
                   class Y(Generic[U, P]):
                       f: Callable[P, U]
                       attr: U
                   
                       def __init__(self, f: Callable[P, U], attr: U) -> None:
                           self.f = f
                           self.attr = attr
                   
                   
                   def a(q: int) -> str: ...
                   
                   
                   expr = Y(a, '1').f(<warning descr="Expected type 'int', got 'str' instead">"42"</warning>)
                   """);
  }

  // PY-49935
  public void testParamSpecUserGenericClassMethodSeveralParameters() {
    doTestByText("""
                   from typing import TypeVar, Generic, Callable, ParamSpec
                   
                   U = TypeVar("U")
                   P = ParamSpec("P")
                   
                   
                   class Y(Generic[U, P]):
                       f: Callable[P, U]
                       attr: U
                   
                       def __init__(self, f: Callable[P, U], attr: U) -> None:
                           self.f = f
                           self.attr = attr
                   
                   
                   def a(q: int, s: str) -> str: ...
                   
                   
                   expr = Y(a, '1').f(42, <warning descr="Expected type 'str', got 'int' instead">42</warning>)
                   """);
  }

  // PY-49935
  public void testParamSpecUserGenericClassMethodConcatenate() {
    doTestByText("""
                   from typing import TypeVar, Generic, Callable, ParamSpec, Concatenate
                   
                   U = TypeVar("U")
                   P = ParamSpec("P")
                   
                   
                   class Y(Generic[U, P]):
                       f: Callable[Concatenate[int, P], U]
                       attr: U
                   
                       def __init__(self, f: Callable[Concatenate[int, P], U], attr: U) -> None:
                           self.f = f
                           self.attr = attr
                   
                   
                   def a(q: int, s: str, b: bool) -> str: ...
                   
                   
                   expr = Y(a, '1').f(42, <warning descr="Expected type 'str', got 'int' instead">42</warning>, <warning descr="Expected type 'bool', got 'int' instead">42</warning>)
                   """);
  }

  // PY-49935
  public void testParamSpecConcatenateAddThirdParameter() {
    doTestByText("""
                   from typing import Callable, Concatenate, ParamSpec
                   
                   P = ParamSpec("P")
                   
                   
                   def bar(x: int, *args: bool) -> int: ...
                   
                   
                   def add(x: Callable[P, int]) -> Callable[Concatenate[str, P], bool]: ...
                   
                   
                   add(bar)("42", 42, <warning descr="Expected type 'bool', got 'int' instead">42</warning>)""");
  }

  // PY-49935
  public void testParamSpecConcatenateAddSecondParameter() {
    doTestByText("""
                   from typing import Callable,  Concatenate, ParamSpec
                   
                   P = ParamSpec("P")
                   
                   
                   def bar(x: int, *args: bool) -> int: ...
                   
                   
                   def add(x: Callable[P, int]) -> Callable[Concatenate[str, P], bool]: ...
                   
                   
                   add(bar)("42", <warning descr="Expected type 'int', got 'str' instead">"42"</warning>, True)""");
  }

  // PY-49935
  public void testParamSpecConcatenateAddFirstParameter() {
    doTestByText("""
                   from typing import Callable,  Concatenate, ParamSpec
                   
                   P = ParamSpec("P")
                   
                   
                   def bar(x: int, *args: bool) -> int: ...
                   
                   
                   def add(x: Callable[P, int]) -> Callable[Concatenate[str, P], bool]: ...
                   
                   
                   add(bar)(<warning descr="Expected type 'str', got 'int' instead">42</warning>, 42, True)""");
  }

  // PY-49935
  public void testParamSpecConcatenateAddFirstSeveralParameters() {
    doTestByText("""
                   from typing import Callable,  Concatenate, ParamSpec
                   
                   P = ParamSpec("P")
                   
                   
                   def bar(x: int, *args: bool) -> int: ...
                   
                   
                   def add(x: Callable[P, int]) -> Callable[Concatenate[str, list[str], P], bool]: ...
                   
                   
                   add(bar)(<warning descr="Expected type 'str', got 'int' instead">42</warning>, <warning descr="Expected type 'list[str]', got 'list[int]' instead">[42]</warning>, 3, True)""");
  }

  // PY-49935
  public void testParamSpecConcatenateAddOk() {
    doTestByText("""
                   from typing import Callable,  Concatenate, ParamSpec
                   
                   P = ParamSpec("P")
                   
                   
                   def bar(x: int, *args: bool) -> int: ...
                   
                   
                   def add(x: Callable[P, int]) -> Callable[Concatenate[str, P], bool]: ...
                   
                   
                   add(bar)("42", 42, True, True, True)""");
  }

  // PY-49935
  public void testParamSpecConcatenateRemove() {
    doTestByText("""
                   from typing import Callable,  Concatenate, ParamSpec
                   
                   P = ParamSpec("P")
                   
                   
                   def bar(x: int, *args: bool) -> int: ...
                   
                   
                   def remove(x: Callable[Concatenate[int, P], int]) -> Callable[P, bool]: ...
                   
                   
                   remove(bar)(<warning descr="Expected type 'bool', got 'int' instead">42</warning>)""");
  }

  // PY-49935
  public void testParamSpecConcatenateRemoveOkOneBool() {
    doTestByText("""
                   from typing import Callable,  Concatenate, ParamSpec
                   
                   P = ParamSpec("P")
                   
                   
                   def bar(x: int, *args: bool) -> int: ...
                   
                   
                   def remove(x: Callable[Concatenate[int, P], int]) -> Callable[P, bool]: ...
                   
                   
                   remove(bar)(True)""");
  }

  // PY-49935
  public void testParamSpecConcatenateRemoveOkTwoBools() {
    doTestByText("""
