Infer a more precise return type for functools.partial.__call__.
(ctx: mypy.plugin.MethodContext)
| 335 | |
| 336 | |
| 337 | def partial_call_callback(ctx: mypy.plugin.MethodContext) -> Type: |
| 338 | """Infer a more precise return type for functools.partial.__call__.""" |
| 339 | if ( |
| 340 | not isinstance(ctx.api, mypy.checker.TypeChecker) # use internals |
| 341 | or not isinstance(ctx.type, Instance) |
| 342 | or ctx.type.type.fullname != PARTIAL |
| 343 | or not ctx.type.extra_attrs |
| 344 | or "__mypy_partial" not in ctx.type.extra_attrs.attrs |
| 345 | ): |
| 346 | return ctx.default_return_type |
| 347 | |
| 348 | extra_attrs = ctx.type.extra_attrs |
| 349 | partial_type = get_proper_type(extra_attrs.attrs["__mypy_partial"]) |
| 350 | if len(ctx.arg_types) != 2: # *args, **kwargs |
| 351 | return ctx.default_return_type |
| 352 | |
| 353 | # See comments for similar actual to formal code above |
| 354 | actual_args = [] |
| 355 | actual_arg_kinds = [] |
| 356 | actual_arg_names = [] |
| 357 | seen_args = set() |
| 358 | for i, param in enumerate(ctx.args): |
| 359 | for j, a in enumerate(param): |
| 360 | if a in seen_args: |
| 361 | continue |
| 362 | seen_args.add(a) |
| 363 | actual_args.append(a) |
| 364 | actual_arg_kinds.append(ctx.arg_kinds[i][j]) |
| 365 | actual_arg_names.append(ctx.arg_names[i][j]) |
| 366 | |
| 367 | result, _ = ctx.api.expr_checker.check_call( |
| 368 | callee=partial_type, |
| 369 | args=actual_args, |
| 370 | arg_kinds=actual_arg_kinds, |
| 371 | arg_names=actual_arg_names, |
| 372 | context=ctx.context, |
| 373 | ) |
| 374 | if not isinstance(partial_type, CallableType) or partial_type.param_spec() is None: |
| 375 | return result |
| 376 | |
| 377 | args_bound = "__mypy_partial_paramspec_args_bound" in extra_attrs.immutable |
| 378 | kwargs_bound = "__mypy_partial_paramspec_kwargs_bound" in extra_attrs.immutable |
| 379 | |
| 380 | passed_paramspec_parts = [ |
| 381 | arg.node.type |
| 382 | for arg in actual_args |
| 383 | if isinstance(arg, NameExpr) |
| 384 | and isinstance(arg.node, Var) |
| 385 | and isinstance(arg.node.type, ParamSpecType) |
| 386 | ] |
| 387 | # ensure *args: P.args |
| 388 | args_passed = any(part.flavor == ParamSpecFlavor.ARGS for part in passed_paramspec_parts) |
| 389 | if not args_bound and not args_passed: |
| 390 | ctx.api.expr_checker.msg.too_few_arguments(partial_type, ctx.context, actual_arg_names) |
| 391 | elif args_bound and args_passed: |
| 392 | ctx.api.expr_checker.msg.too_many_arguments(partial_type, ctx.context) |
| 393 | |
| 394 | # ensure **kwargs: P.kwargs |
nothing calls this directly
no test coverage detected
searching dependent graphs…