Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 8010414

Browse files
TH3CHARLieilevkivskyi
authored andcommitted
Support Negative Int Literal (#7878)
This PR aims to support negative int Literal, resolves #7844 Note that this also required adding `type_context` to plugin API, but this may be helpful also for other plugins.
1 parent 3d680e0 commit 8010414

5 files changed

Lines changed: 67 additions & 1 deletion

File tree

mypy/checker.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,10 @@ def __init__(self, errors: Errors, modules: Dict[str, MypyFile], options: Option
251251
# argument through various `checker` and `checkmember` functions.
252252
self._is_final_def = False
253253

254+
@property
255+
def type_context(self) -> List[Optional[Type]]:
256+
return self.expr_checker.type_context
257+
254258
def reset(self) -> None:
255259
"""Cleanup stale state that might be left over from a typechecking run.
256260

mypy/plugin.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,12 @@ class CheckerPluginInterface:
216216
options = None # type: Options
217217
path = None # type: str
218218

219+
# Type context for type inference
220+
@abstractproperty
221+
def type_context(self) -> List[Optional[Type]]:
222+
"""Return the type context of the plugin"""
223+
raise NotImplementedError
224+
219225
@abstractmethod
220226
def fail(self, msg: str, ctx: Context, *, code: Optional[ErrorCode] = None) -> None:
221227
"""Emit an error message at given location."""

mypy/plugins/default.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@
1010
from mypy.plugins.common import try_getting_str_literals
1111
from mypy.types import (
1212
Type, Instance, AnyType, TypeOfAny, CallableType, NoneType, TypedDictType,
13-
TypeVarType, TPDICT_FB_NAMES, get_proper_type
13+
TypeVarType, TPDICT_FB_NAMES, get_proper_type, LiteralType
1414
)
1515
from mypy.subtypes import is_subtype
1616
from mypy.typeops import make_simplified_union
17+
from mypy.checkexpr import is_literal_type_like
1718

1819

1920
class DefaultPlugin(Plugin):
@@ -57,6 +58,8 @@ def get_method_hook(self, fullname: str
5758
return typed_dict_get_callback
5859
elif fullname == 'builtins.int.__pow__':
5960
return int_pow_callback
61+
elif fullname == 'builtins.int.__neg__':
62+
return int_neg_callback
6063
elif fullname in set(n + '.setdefault' for n in TPDICT_FB_NAMES):
6164
return typed_dict_setdefault_callback
6265
elif fullname in set(n + '.pop' for n in TPDICT_FB_NAMES):
@@ -417,3 +420,30 @@ def int_pow_callback(ctx: MethodContext) -> Type:
417420
else:
418421
return ctx.api.named_generic_type('builtins.float', [])
419422
return ctx.default_return_type
423+
424+
425+
def int_neg_callback(ctx: MethodContext) -> Type:
426+
"""Infer a more precise return type for int.__neg__.
427+
428+
This is mainly used to infer the return type as LiteralType
429+
if the original underlying object is a LiteralType object
430+
"""
431+
if isinstance(ctx.type, Instance) and ctx.type.last_known_value is not None:
432+
value = ctx.type.last_known_value.value
433+
fallback = ctx.type.last_known_value.fallback
434+
if isinstance(value, int):
435+
if is_literal_type_like(ctx.api.type_context[-1]):
436+
return LiteralType(value=-value, fallback=fallback)
437+
else:
438+
return ctx.type.copy_modified(last_known_value=LiteralType(
439+
value=-value,
440+
fallback=ctx.type,
441+
line=ctx.type.line,
442+
column=ctx.type.column,
443+
))
444+
elif isinstance(ctx.type, LiteralType):
445+
value = ctx.type.value
446+
fallback = ctx.type.fallback
447+
if isinstance(value, int):
448+
return LiteralType(value=-value, fallback=fallback)
449+
return ctx.default_return_type

test-data/unit/check-literal.test

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3093,3 +3093,28 @@ def test() -> None:
30933093
if x == 2: # OK
30943094
...
30953095
[builtins fixtures/bool.pyi]
3096+
3097+
[case testNegativeIntLiteral]
3098+
from typing_extensions import Literal
3099+
3100+
a: Literal[-2] = -2
3101+
b: Literal[-1] = -1
3102+
c: Literal[0] = 0
3103+
d: Literal[1] = 1
3104+
e: Literal[2] = 2
3105+
[out]
3106+
[builtins fixtures/float.pyi]
3107+
3108+
[case testNegativeIntLiteralWithFinal]
3109+
from typing_extensions import Literal, Final
3110+
3111+
ONE: Final = 1
3112+
x: Literal[-1] = -ONE
3113+
3114+
TWO: Final = 2
3115+
THREE: Final = 3
3116+
3117+
err_code = -TWO
3118+
if bool():
3119+
err_code = -THREE
3120+
[builtins fixtures/float.pyi]

test-data/unit/fixtures/float.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class int:
2626
def __float__(self) -> float: ...
2727
def __int__(self) -> int: ...
2828
def __mul__(self, x: int) -> int: ...
29+
def __neg__(self) -> int: ...
2930
def __rmul__(self, x: int) -> int: ...
3031

3132
class float:

0 commit comments

Comments
 (0)