Skip to content

Commit 8a1ebf3

Browse files
authored
Add basic mypy type checking for vec types (#20669)
Check that item type is valid, and infer `i64` as the type of `len(<vec>)`. `vec` types only support specific simple item types that are quick to type check at runtime. Related issue: mypyc/mypyc#840
1 parent edc47c6 commit 8a1ebf3

File tree

4 files changed

+121
-0
lines changed

4 files changed

+121
-0
lines changed

mypy/plugins/default.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ def get_function_hook(self, fullname: str) -> Callable[[FunctionContext], Type]
105105
return partial_new_callback
106106
elif fullname == "enum.member":
107107
return enum_member_callback
108+
elif fullname == "builtins.len":
109+
return len_callback
108110
return None
109111

110112
def get_function_signature_hook(
@@ -213,6 +215,18 @@ def get_class_decorator_hook_2(
213215
return None
214216

215217

218+
def len_callback(ctx: FunctionContext) -> Type:
219+
"""Infer a better return type for 'len'."""
220+
if len(ctx.arg_types) == 1:
221+
arg_type = ctx.arg_types[0][0]
222+
arg_type = get_proper_type(arg_type)
223+
if isinstance(arg_type, Instance) and arg_type.type.fullname == "librt.vecs.vec":
224+
# The length of vec is a fixed-width integer, for more
225+
# low-level optimization potential.
226+
return ctx.api.named_generic_type("mypy_extensions.i64", [])
227+
return ctx.default_return_type
228+
229+
216230
def typed_dict_get_signature_callback(ctx: MethodSigContext) -> CallableType:
217231
"""Try to infer a better signature type for TypedDict.get.
218232

mypy/semanal.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@
245245
TypeVarLikeList,
246246
analyze_type_alias,
247247
check_for_explicit_any,
248+
check_vec_type_args,
248249
detect_diverging_alias,
249250
find_self_type,
250251
fix_instance,
@@ -6178,6 +6179,10 @@ def analyze_type_application(self, expr: IndexExpr) -> None:
61786179
expr.analyzed.line = expr.line
61796180
expr.analyzed.column = expr.column
61806181

6182+
if isinstance(base, RefExpr) and base.fullname == "librt.vecs.vec":
6183+
# Apply restrictions specific to vec
6184+
check_vec_type_args(types, expr, self)
6185+
61816186
def analyze_type_application_args(self, expr: IndexExpr) -> list[Type] | None:
61826187
"""Analyze type arguments (index) in a type application.
61836188

mypy/typeanal.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
CONCATENATE_TYPE_NAMES,
6666
FINAL_TYPE_NAMES,
6767
LITERAL_TYPE_NAMES,
68+
MYPYC_NATIVE_INT_NAMES,
6869
NEVER_NAMES,
6970
TUPLE_NAMES,
7071
TYPE_ALIAS_NAMES,
@@ -877,6 +878,11 @@ def analyze_type_with_type_info(
877878
if len(info.type_vars) == 1 and info.has_param_spec_type:
878879
instance.args = tuple(self.pack_paramspec_args(instance.args, empty_tuple_index))
879880

881+
if info.fullname == "librt.vecs.vec" and not check_vec_type_args(
882+
instance.args, ctx, self.api
883+
):
884+
return AnyType(TypeOfAny.from_error)
885+
880886
# Check type argument count.
881887
instance.args = tuple(flatten_nested_tuples(instance.args))
882888
if not (self.defining_alias and self.nesting_level == 0) and not validate_instance(
@@ -2736,3 +2742,54 @@ def visit_unbound_type(self, t: UnboundType) -> Type:
27362742
def visit_type_alias_type(self, t: TypeAliasType) -> Type:
27372743
# TypeAliasTypes are analyzed separately already, just return it
27382744
return t
2745+
2746+
2747+
def check_vec_type_args(
2748+
args: tuple[Type, ...] | list[Type], ctx: Context, api: SemanticAnalyzerCoreInterface
2749+
) -> bool:
2750+
"""Report an error if type args for 'vec' are invalid.
2751+
2752+
Return False on error.
2753+
"""
2754+
ok = True
2755+
if len(args) != 1:
2756+
ok = False
2757+
else:
2758+
arg = get_proper_type(args[0])
2759+
if isinstance(arg, Instance):
2760+
if arg.type.fullname == "builtins.int":
2761+
# A fixed-width integer such as 'i64' must be used instead of plain 'int'
2762+
ok = False
2763+
elif isinstance(arg, UnionType):
2764+
non_optional = None
2765+
items = [get_proper_type(item) for item in arg.items]
2766+
if len(items) != 2:
2767+
ok = False
2768+
elif isinstance(items[0], NoneType):
2769+
if not check_vec_type_args([items[1]], ctx, api):
2770+
# Error has already been reported so it's fine to return
2771+
return False
2772+
non_optional = items[1]
2773+
elif isinstance(items[1], NoneType):
2774+
if not check_vec_type_args([items[0]], ctx, api):
2775+
# Error has already been reported so it's fine to return
2776+
return False
2777+
non_optional = items[0]
2778+
else:
2779+
ok = False
2780+
if isinstance(non_optional, Instance) and (
2781+
non_optional.type.fullname in MYPYC_NATIVE_INT_NAMES
2782+
or non_optional.type.fullname
2783+
in ("builtins.int", "builtins.float", "builtins.bool", "librt.vecs.vec")
2784+
):
2785+
ok = False
2786+
elif isinstance(arg, TypeVarType):
2787+
# Generic vec types aren't supported in type checked Python code, but
2788+
# they can be provided in libraries implemented in C (e.g. append).
2789+
if not api.is_stub_file:
2790+
ok = False
2791+
else:
2792+
ok = False
2793+
if not ok:
2794+
api.fail('Invalid item type for "vec"', ctx)
2795+
return ok

test-data/unit/check-vec.test

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
[case testVecBasics]
2+
# flags: --python-version 3.10
3+
from typing import Optional, Any, TypeVar
4+
5+
from librt.vecs import vec
6+
from mypy_extensions import i64, i32, i16, u8
7+
8+
def f(v: vec[i64]) -> None:
9+
x: i64 = v[0]
10+
x = v[x]
11+
v[x] = x
12+
13+
v = vec[i64]()
14+
reveal_type(v) # N: Revealed type is "librt.vecs.vec[mypy_extensions.i64]"
15+
f(v)
16+
reveal_type(len(v)) # N: Revealed type is "mypy_extensions.i64"
17+
18+
vec_i32: vec[i32]
19+
vec_i16: vec[i16]
20+
vec_u8: vec[u8]
21+
vec_bool: vec[bool]
22+
vec_float: vec[float]
23+
vec_str: vec[str]
24+
vec_str_opt1: vec[str | None]
25+
vec_str_opt2: vec[Optional[str]]
26+
vec_nested1: vec[vec[i32]]
27+
vec_nested2: vec[vec[vec[str | None]]]
28+
vec_list: vec[list[int]]
29+
vec_var_tuple: vec[tuple[int, ...]]
30+
vec_object: vec[object]
31+
32+
vec_bad_int: vec[int] # E: Invalid item type for "vec"
33+
vec_bad_tuple: vec[tuple[int, str]] # E: Invalid item type for "vec"
34+
vec_bad_union: vec[str | ellipsis] # E: Invalid item type for "vec"
35+
vec_bad_any: vec[Any] # E: Invalid item type for "vec"
36+
vec_bad_two_args: vec[i32, i32] # E: Invalid item type for "vec"
37+
vec_bad_optional1: vec[int | None] # E: Invalid item type for "vec"
38+
vec_bad_optional2: vec[i64 | None] # E: Invalid item type for "vec"
39+
vec_bad_optional3: vec[bool | None] # E: Invalid item type for "vec"
40+
vec_bad_optional4: vec[float | None] # E: Invalid item type for "vec"
41+
42+
T = TypeVar("T")
43+
44+
def bad_generic_func(v: vec[T]) -> None: ... # E: Invalid item type for "vec"
45+
[builtins fixtures/len.pyi]

0 commit comments

Comments
 (0)