TYP: Improve clip() return and argument typing. 
State: closed

- [x] closes #40829 
- [x] Ensure all linting tests pass, see [here](https://pandas.pydata.org/pandas-docs/dev/development/contributing.html#code-standards) for how to run them


Labels:
name: Typing
description: type annotations, mypy/pyright type checking

Comments:
> Hi @DriesSchaumont
> 
> This is off to a good start!
> 
> You have overloads for:
> 
> * white_check_mark `inplace: Literal[False] = ...`
> * white_check_mark `inplace: bool = ...`
> * white_check_mark `inplace: Literal[True]`:
>   
>   * when `lower`, `upper`, and `axis` are passed
>   * when `lower` and `upper`, but not `axis`, are passed
> 
> You're missing:
> 
> * `inplace: Literal[True]`:
>   
>   * when `lower` and `axis`, but not `upper`, are passed
>   * when `upper` and `axis`, but not `lower`, are passed
>   * when `lower`, but not `axis` or `upper`, is passed
>   * when `upper`, but not `axis` or `lower`, is passed
>   * when `axis`, but not `lower` or `upper`, is passed
>   * when none of `axis`, `lower`, or `upper` are passed

Thank you for this nice overview! I will try to add these cases. I am a bit confused on how to handle the syntax for: "when `upper`, but not `axis` or `lower`, is passed." Is this:

``def clip(self: FrameOrSeries, lower=None, upper=..., *, inplace: Literal[True], **kwargs) -> None:`` correct? How do I indicate that lower is not specified?
can you try
```python
    @overload
    def clip(
        self: FrameOrSeries,
        *
        upper,
        inplace: Literal[True],
        *args,
        **kwargs,
    ) -> None:
        ...
```
?
> can you try
> 
> ```python
>     @overload
>     def clip(
>         self: FrameOrSeries,
>         *
>         upper,
>         inplace: Literal[True],
>         *args,
>         **kwargs,
>     ) -> None:
>         ...
> ```
> 
> ?

Thanks! I think I got them all now. Could you check if I did everything right? I am quite new to type overloading.
> Thanks! I think I got them all now. Could you check if I did everything right? I am quite new to type overloading.

Congrats for having braved this not-so-simple issue then!

To check if you've done it correctly, could you make a new file `t.py` (which you won't stage/commit, it'll just be there locally to check) in which you do something like what I did in https://github.com/pandas-dev/pandas/issues/40737#issue-848710021 ? You'll need to replace `reset_index` with `clip`, and you'll need to try with the various options of arguments which precede `inplace` being preset or absent, and then you should check that running `mypy t.py` produces the types which you were expecting

If you could do that, and paste here the content of `t.py` as the output from running `mypy t.py`, then if it all looks right then this'll be good to go
I think the last one are correct - if `inplace` is just `bool`, then we don't know if the return type is `DataFrame` or `None`, so their union seems correct

If you repeat the first block but put `inplace=False`, then I'd expect you'll get all `DataFrame` only
Yes, just as I left my last comment (which I then deleted), I realized my mistake. Here is the file and the output! 
```
import pandas as pd

inplace: bool

reveal_type(pd.DataFrame([1,2,3]).clip(lower=0, upper=4, axis=1, inplace=True))
reveal_type(pd.DataFrame([1,2,3]).clip(upper=4, axis=1, inplace=True))
reveal_type(pd.DataFrame([1,2,3]).clip(axis=1, inplace=True))
reveal_type(pd.DataFrame([1,2,3]).clip(inplace=True))
reveal_type(pd.DataFrame([1,2,3]).clip(lower=0, axis=1, inplace=True))
reveal_type(pd.DataFrame([1,2,3]).clip(lower=0, inplace=True))
reveal_type(pd.DataFrame([1,2,3]).clip(upper=0, inplace=True))
reveal_type(pd.DataFrame([1,2,3]).clip(upper=4, axis=1, inplace=True))
reveal_type(pd.DataFrame([1,2,3]).clip(lower=0, upper=1, inplace=True))


reveal_type(pd.DataFrame([1,2,3]).clip(lower=0, upper=4, axis=1, inplace=False))
reveal_type(pd.DataFrame([1,2,3]).clip(upper=4, axis=1, inplace=False))
reveal_type(pd.DataFrame([1,2,3]).clip(axis=1, inplace=False))
reveal_type(pd.DataFrame([1,2,3]).clip(inplace=False))
reveal_type(pd.DataFrame([1,2,3]).clip(lower=0, axis=1, inplace=False))
reveal_type(pd.DataFrame([1,2,3]).clip(lower=0, inplace=False))
reveal_type(pd.DataFrame([1,2,3]).clip(upper=0, inplace=False))
reveal_type(pd.DataFrame([1,2,3]).clip(upper=4, axis=1, inplace=False))
reveal_type(pd.DataFrame([1,2,3]).clip(lower=0, upper=1, inplace=False))

reveal_type(pd.DataFrame([1,2,3]).clip(lower=0, upper=4, axis=1, inplace=inplace))
reveal_type(pd.DataFrame([1,2,3]).clip(upper=4, axis=1, inplace=inplace))
reveal_type(pd.DataFrame([1,2,3]).clip(axis=1, inplace=inplace))
reveal_type(pd.DataFrame([1,2,3]).clip(inplace=inplace))
reveal_type(pd.DataFrame([1,2,3]).clip(lower=0, axis=1, inplace=inplace))
reveal_type(pd.DataFrame([1,2,3]).clip(lower=0, inplace=inplace))
reveal_type(pd.DataFrame([1,2,3]).clip(upper=0, inplace=inplace))
reveal_type(pd.DataFrame([1,2,3]).clip(upper=4, axis=1, inplace=inplace))
reveal_type(pd.DataFrame([1,2,3]).clip(lower=0, upper=1, inplace=inplace))
```
```
t.py:6: note: Revealed type is 'None'
t.py:7: note: Revealed type is 'None'
t.py:8: note: Revealed type is 'None'
t.py:9: note: Revealed type is 'None'
t.py:10: note: Revealed type is 'None'
t.py:11: note: Revealed type is 'None'
t.py:12: note: Revealed type is 'None'
t.py:13: note: Revealed type is 'None'
t.py:14: note: Revealed type is 'None'
t.py:17: note: Revealed type is 'pandas.core.frame.DataFrame*'
t.py:18: note: Revealed type is 'pandas.core.frame.DataFrame*'
t.py:19: note: Revealed type is 'pandas.core.frame.DataFrame*'
t.py:20: note: Revealed type is 'pandas.core.frame.DataFrame*'
t.py:21: note: Revealed type is 'pandas.core.frame.DataFrame*'
t.py:22: note: Revealed type is 'pandas.core.frame.DataFrame*'
t.py:23: note: Revealed type is 'pandas.core.frame.DataFrame*'
t.py:24: note: Revealed type is 'pandas.core.frame.DataFrame*'
t.py:25: note: Revealed type is 'pandas.core.frame.DataFrame*'
t.py:27: note: Revealed type is 'Union[pandas.core.frame.DataFrame*, None]'
t.py:28: note: Revealed type is 'Union[pandas.core.frame.DataFrame*, None]'
t.py:29: note: Revealed type is 'Union[pandas.core.frame.DataFrame*, None]'
t.py:30: note: Revealed type is 'Union[pandas.core.frame.DataFrame*, None]'
t.py:31: note: Revealed type is 'Union[pandas.core.frame.DataFrame*, None]'
t.py:32: note: Revealed type is 'Union[pandas.core.frame.DataFrame*, None]'
t.py:33: note: Revealed type is 'Union[pandas.core.frame.DataFrame*, None]'
t.py:34: note: Revealed type is 'Union[pandas.core.frame.DataFrame*, None]'
t.py:35: note: Revealed type is 'Union[pandas.core.frame.DataFrame*, None]'
```
@MarcoGorelli thank you for the guidance and the explanations!