mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-19 04:51:24 +07:00
PY-65385 Retain unbound ParamSpecs as-is during type parameter substitution
The original problem with @contextlib.asynccontextmanager was due to a bug in PyTypeChecker.substitute introduced in the TypeVarTuple support. Namely, we started to substitute unmatched ParamSpec types with null, effectively replacing them in a callable signature with a single parameter of type Any. Then the special logic in PyCallExpressionHelper.mapArguments that treated unmatched ParamSpecs as "catch-all" varargs stopped working, and we started to highlight all extra arguments in the substituted callable invocations. In other words, binding type parameters from decorator targets, e.g. ParamSpecs or function return types, never worked because we can't resolve functions passed as decorator arguments in "de-sugared" expression fragments in the codeAnalysis context, i.e. when we replace ``` @deco def f(x: int, y: str): ... ``` with `deco(f)` and then try to infer its type in PyDecoratedFunctionTypeProvider, but we didn't report it thanks to that special-casing of unmatched ParamSpecs (other type parameters replaced by Any don't trigger such warnings). Ideally, we should start resolving references in arguments of function calls in such virtual expression fragments in some stub-safe manner instead of relying on this fallback logic. In the general case, however, complete stub-safe inference for decorators is a hard problem because arbitrary expressions can affect types of their return type, .e.g. ``` def deco(result: T) -> Callable[[Callable[P, Any]], Callable[P, T]]: ... @deco(arbitrary_call().foo + 42) # how to handle this without unstubbing? def f(x: int, y: str): ... ``` GitOrigin-RevId: adeb625611a3ebb7d5db523df00388d619323545
This commit is contained in:
committed by
intellij-monorepo-bot
parent
07cadf2cf2
commit
7320815ad2
@@ -1106,7 +1106,10 @@ public final class PyTypeChecker {
|
||||
}
|
||||
return substitution;
|
||||
}
|
||||
else if (type instanceof PyParamSpecType paramSpecType) {
|
||||
else if (type instanceof PyParamSpecType paramSpecType && paramSpecType.getParameters() == null) {
|
||||
if (!substitutions.paramSpecs.containsKey(paramSpecType)) {
|
||||
return paramSpecType;
|
||||
}
|
||||
PyParamSpecType substitution = substitutions.paramSpecs.get(paramSpecType);
|
||||
if (substitution != null && !substitution.equals(paramSpecType) && hasGenerics(substitution, context)) {
|
||||
return substitute(substitution, substitutions, context);
|
||||
@@ -1166,9 +1169,9 @@ public final class PyTypeChecker {
|
||||
for (PyCallableParameter parameter : parameters) {
|
||||
final var parameterType = parameter.getType(context);
|
||||
if (parameters.size() == 1 && parameterType instanceof PyParamSpecType) {
|
||||
final var parameterTypeSubst = substitutions.paramSpecs.get(parameterType);
|
||||
if (parameterTypeSubst != null && parameterTypeSubst.getParameters() != null) {
|
||||
substParams = parameterTypeSubst.getParameters();
|
||||
final var substitution = substitute(parameterType, substitutions, context);
|
||||
if (substitution instanceof PyParamSpecType paramSpecSubst && paramSpecSubst.getParameters() != null) {
|
||||
substParams = paramSpecSubst.getParameters();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user