boolean (@NotNull PyVariadicType expected, @Nullable PyType actual, @NotNull MatchContext context) { if (actual == null) { return true; } if (!(actual instanceof PyVariadicType actualVariadic)) { return false; } if (expected instanceof PyUnpackedTupleType expectedUnpackedTupleType) { // The actual type is just a TypeVarTuple if (!(actualVariadic instanceof PyUnpackedTupleType actualUnpackedTupleType)) { return false; } if (expectedUnpackedTupleType.isUnbound()) { PyType repeatedExpectedType = expectedUnpackedTupleType.getElementTypes().get(0); if (actualUnpackedTupleType.isUnbound()) { return match(repeatedExpectedType, actualUnpackedTupleType.getElementTypes().get(0), context).orElse(false); } else { return ContainerUtil.all(actualUnpackedTupleType.getElementTypes(), singleActualType -> match(repeatedExpectedType, singleActualType, context).orElse(false)); } } else { if (actualUnpackedTupleType.isUnbound()) { PyType repeatedActualType = actualUnpackedTupleType.getElementTypes().get(0); return ContainerUtil.all(expectedUnpackedTupleType.getElementTypes(), singleExpectedType -> match(singleExpectedType, repeatedActualType, context).orElse(false)); } else { return matchTypeParameters(expectedUnpackedTupleType.getElementTypes(), actualUnpackedTupleType.getElementTypes(), context); } } } // The expected type is just a TypeVarTuple else { PyVariadicType substitution = context.mySubstitutions.typeVarTuples.get(expected); if (substitution != null && !substitution.equals(PyUnpackedTupleTypeImpl.UNSPECIFIED)) { if (expected.equals(actual) || substitution.equals(expected)) { return true; } return context.reversedSubstitutions ? match(actualVariadic, substitution, context) : match(substitution, actualVariadic, context); } context.mySubstitutions.typeVarTuples.put((PyTypeVarTupleType)expected, actualVariadic); } return true; }