boolean (@NotNull List<PyCallableParameter> expectedParameters, @NotNull List<PyCallableParameter> actualParameters, @NotNull MatchContext matchContext) { final TypeEvalContext context = matchContext.context; if (expectedParameters.size() == 1) { final var firstExpectedParam = expectedParameters.get(0); final var expectedParamType = firstExpectedParam.getType(context); if (expectedParamType instanceof final PyParamSpecType expectedParamSpecType) { matchContext.mySubstitutions.paramSpecs.put(expectedParamSpecType, expectedParamSpecType.withParameters(actualParameters, context)); return true; } else if (expectedParamType instanceof final PyConcatenateType expectedConcatenateType) { if (actualParameters.isEmpty()) { return true; } final var actualParamType = actualParameters.get(0).getType(context); final var expectedFirstTypes = expectedConcatenateType.getFirstTypes(); if (actualParamType instanceof final PyConcatenateType actualConcatenateType) { final var actualFirstType = actualConcatenateType.getFirstTypes(); if (!match(expectedFirstTypes, actualFirstType, matchContext)) { return false; } } else { final var actualParamRightBound = Math.min(expectedFirstTypes.size(), actualParameters.size()); final var actualFirstParamTypes = ContainerUtil .map(actualParameters.subList(0, actualParamRightBound), it -> it.getType(context)); if (!match(expectedFirstTypes, actualFirstParamTypes, matchContext)) { return false; } if (actualParamRightBound < actualParameters.size()) { final var expectedParamSpecType = expectedConcatenateType.getParamSpec(); final var restActualParameters = actualParameters.subList(actualParamRightBound, actualParameters.size()); final var parametersSubst = expectedParamSpecType.withParameters(restActualParameters, context); matchContext.mySubstitutions.paramSpecs.put(expectedParamSpecType, parametersSubst); return true; } } return true; } } int startIndex = 0; if (!expectedParameters.isEmpty() && !actualParameters.isEmpty()) { var firstExpectedParam = expectedParameters.get(0); var firstActualParam = actualParameters.get(0); if (firstExpectedParam.isSelf() && firstActualParam.isSelf()) { if (!match(firstExpectedParam.getType(context), firstActualParam.getType(context), matchContext).orElse(true)) { return false; } startIndex = 1; } } // TODO Implement proper compatibility check for callable signatures, including positional- and keyword-only arguments, defaults, etc. boolean shouldAcceptUnlimitedPositionalArgs = ContainerUtil.exists(expectedParameters, PyCallableParameter::isPositionalContainer); boolean canAcceptUnlimitedPositionalArgs = ContainerUtil.exists(actualParameters, PyCallableParameter::isPositionalContainer); if (shouldAcceptUnlimitedPositionalArgs && !canAcceptUnlimitedPositionalArgs) return false; boolean shouldAcceptArbitraryKeywordArgs = ContainerUtil.exists(expectedParameters, PyCallableParameter::isKeywordContainer); boolean canAcceptArbitraryKeywordArgs = ContainerUtil.exists(actualParameters, PyCallableParameter::isKeywordContainer); if (shouldAcceptArbitraryKeywordArgs && !canAcceptArbitraryKeywordArgs) return false; List<PyType> expectedElementTypes = ContainerUtil.map(expectedParameters, cp -> { PyType argType = cp.getArgumentType(context); if (cp.isPositionalContainer() && !(argType instanceof PyVariadicType)) { return PyUnpackedTupleTypeImpl.createUnbound(argType); } return argType; }); PyTypeParameterMapping mapping = PyTypeParameterMapping.mapWithParameterList(ContainerUtil.subList(expectedElementTypes, startIndex), ContainerUtil.subList(actualParameters, startIndex), context); if (mapping == null) { return false; } // actual callable type could accept more general parameter type for (Couple<PyType> pair : mapping.getMappedTypes()) { Optional<Boolean> matched = matchContext.reverseSubstitutions().reversedSubstitutions ? match(pair.getSecond(), pair.getFirst(), matchContext.reverseSubstitutions()) : match(pair.getFirst(), pair.getSecond(), matchContext.reverseSubstitutions()); if (!matched.orElse(true)) { return false; } } return true; }