GDB (xrefs)
Loading...
Searching...
No Matches
typecheck.py
Go to the documentation of this file.
1# Copyright 2023 Free Software Foundation, Inc.
2
3# This program is free software; you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation; either version 3 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16# A simple runtime type checker.
17
18import collections.abc
19import functools
20import typing
21
22
23# 'isinstance' won't work in general for type variables, so we
24# implement the subset that is needed by DAP.
25def _check_instance(value, typevar):
26 base = typing.get_origin(typevar)
27 if base is None:
28 return isinstance(value, typevar)
29 arg_types = typing.get_args(typevar)
30 if base == collections.abc.Mapping or base == typing.Mapping:
31 if not isinstance(value, collections.abc.Mapping):
32 return False
33 assert len(arg_types) == 2
34 (keytype, valuetype) = arg_types
35 return all(
36 _check_instance(k, keytype) and _check_instance(v, valuetype)
37 for k, v in value.items()
38 )
39 elif base == collections.abc.Sequence or base == typing.Sequence:
40 # In some places we simply use 'Sequence' without arguments.
41 if not isinstance(value, base):
42 return False
43 if len(arg_types) == 0:
44 return True
45 assert len(arg_types) == 1
46 arg_type = arg_types[0]
47 return all(_check_instance(item, arg_type) for item in value)
48 elif base == typing.Union:
49 return any(_check_instance(value, arg_type) for arg_type in arg_types)
50 raise TypeError("unsupported type variable '" + str(typevar) + "'")
51
52
53def type_check(func):
54 """A decorator that checks FUNC's argument types at runtime."""
55
56 # The type checker relies on 'typing.get_origin', which was added
57 # in Python 3.8. (It also relies on 'typing.get_args', but that
58 # was added at the same time.)
59 if not hasattr(typing, "get_origin"):
60 return func
61
62 hints = typing.get_type_hints(func)
63 # We don't check the return type, but we allow it in case someone
64 # wants to use it on a function definition.
65 if "return" in hints:
66 del hints["return"]
67
68 # Note that keyword-only is fine for our purposes, because this is
69 # only used for DAP requests, and those are always called this
70 # way.
71 @functools.wraps(func)
72 def check_arguments(**kwargs):
73 for key in hints:
74 # The argument might not be passed in; we could type-check
75 # any default value here, but it seems fine to just rely
76 # on the code being correct -- the main goal of this
77 # checking is to verify JSON coming from the client.
78 if key in kwargs and not _check_instance(kwargs[key], hints[key]):
79 raise TypeError(
80 "value for '"
81 + key
82 + "' does not have expected type '"
83 + str(hints[key])
84 + "'"
85 )
86 return func(**kwargs)
87
88 return check_arguments
@ all
void(* func)(remote_target *remote, char *)