mypy: 引数の型が`dict[str|None,str]`である関数に`dict[str,str]`型の値を渡しても、`"Dict" is invariant `というnoteメッセージは出力されない

# 環境
* Python 3.12.4
* mypy 1.13.0



# 内容
dictを受け取る2つの関数があります。それぞれ`dict[str,str]`型の値を受け取りますが、keyとvalueの型が少し異なります。

* `foo1_dict`関数: dictのkeyは`Union`型
* `foo2_dict`関数: dictのvalueは`Union`型

以下のPythonファイルに対してmypyを実行しました。


```python:sample1.py
data = {"a": "b"}
def foo1_dict(arg: dict[str, str|None]) -> None: pass
# errorあり, noteあり
foo1_dict(data)

def foo2_dict(arg: dict[str|None, str]) -> None: pass
# errorあり, noteなし
foo2_dict(data)
```


```
$ mypy sample1.py
sample1.py:4: error: Argument 1 to "foo1_dict" has incompatible type "dict[str, str]"; expected "dict[str, str | None]"  [arg-type]
sample1.py:4: note: "Dict" is invariant -- see https://mypy.readthedocs.io/en/stable/common_issues.html#variance
sample1.py:4: note: Consider using "Mapping" instead, which is covariant in the value type
sample1.py:8: error: Argument 1 to "foo2_dict" has incompatible type "dict[str, str]"; expected "dict[str | None, str]"  [arg-type]
Found 2 errors in 1 file (checked 1 source file)
```

`foo1_dict`関数と`foo2_dict`関数の呼び出しで、`arg-type`のerrorメッセージが出力されました。
`foo1_dict`関数では、`"Dict" is invariant ～`というnoteメッセージが出力されましたが、`foo2_dict`では出力されませんでした。


## `dict`型から`collections.abc.Mapping`型に変更する
mypyのエラーを解決するために、引数の型を`dict`型から`collections.abc.Mapping`型に変更しました。

* `foo1_mapping`関数: dictのkeyは`Union`型
* `foo2_mapping`関数: dictのvalueは`Union`型


```python:sample2.py
from collections.abc import Mapping

data = {"a": "b"}
def foo1_mapping(arg: Mapping[str, str|None]) -> None: pass
# errorなし
foo1_mapping(data)

def foo2_mapping(arg: Mapping[str|None, str]) -> None: pass
# errorあり, noteなし
foo2_mapping(data)
```


```
$ mypy sample2.py
sample2.py:10: error: Argument 1 to "foo2_mapping" has incompatible type "dict[str, str]"; expected "Mapping[str | None, str]"  [arg-type]
Found 1 error in 1 file (checked 1 source file)
```

`foo1_mapping`関数の呼び出しのerrorは解決されましたが、`foo2_mapping`関数の呼び出しのerrorは残ってままでした。

## `Mapping[K,V]`の`K`はinvariant
`Mapping[K,V]`の`V`はcovariantですが、`Mapping[K,V]`の`K`はinvariantです。
`Mapping[K,V]`の`K`がinvariantであるのは、以下のサンプルコードで理解できるかもしれません。

https://github.com/python/typing/pull/273#issuecomment-243864718

私は完全には理解できていません。。。

なお、過去`Mapping[K,V]`の`K`をcovariantにすることを検討していたようです。

https://github.com/python/typing/issues/445


# mypyでの判定処理
以下は、mypyのnoteメッセージを出力するかどうかを判定しているコードです。dictのkey（`args[0]`）は`is_same_type`、dictのvalue（`args[1]`）は`is_subtype`で型を判定していました。

```python:mypy/mypy/messages.py
    elif (
        arg_type.type.fullname == "builtins.dict"
        and expected_type.type.fullname == "builtins.dict"
        and is_same_type(arg_type.args[0], expected_type.args[0])
        and is_subtype(arg_type.args[1], expected_type.args[1])
    ):
        invariant_type = "Dict"
        covariant_suggestion = (
            'Consider using "Mapping" instead, which is covariant in the value type'
        )
```        

https://github.com/python/mypy/blob/40730c9e6d8a576b8374527056a3672ab80f7d65/mypy/messages.py#L3202-L3207




# 補足

## 記事を書いたきっかけ
[loguruの以下のサンプルコード](https://loguru.readthedocs.io/en/stable/api/logger.html#loguru._logger.Logger)で、mypyのエラーが出たので調査しました。

```python:sample3.py
level_per_module = {
    "": "DEBUG",
    "third.lib": "WARNING",
    "anotherlib": False
}
logger.add(lambda m: print(m, end=""), filter=level_per_module, level=0)
```

```
$ mypy sample3.py
sample3.py:7: error: Argument "filter" to "add" of "Logger" has incompatible type "dict[str, object]"; expected "str | Callable[[Record], bool] | dict[str | None, str | int | bool] | None"
  [arg-type]
Found 1 error in 1 file (checked 1 source file)
```






## 参考にしたサイト
https://mypy.readthedocs.io/en/stable/common_issues.html#variance

https://qiita.com/moken20/items/a0c3ae366b5be87c3dd5

https://yng87.page/blog/2023/type-system-python/

https://github.com/python/mypy/issues/14610