Extract type information in makePyArgsString

One unexpected type of '...' required adding a new transformation
that modifies both the name and the type to just '*args', so added
a preferred method `FixWxPrefix.parseNameAndType` which processes
both strings at once.

Also fixes cleanType to recursively call cleanType on sub-types
(was improperly calling cleanName).

With this, method and function signatures now have type annotations
which are mostly correct (100% correct in the "it compiles" sense).
Thankfully, the incorrect type-hints don't cause errors due to using
stringized annotations (by importing annotations from __future__).

Importantly, the overload signatures now have been fully sanitized.
Before this, there was one instance of a variable named `is`, and another
named `/Transfer/` - both invalid identifiers. I stopped looking after
those. Since theses signatures are valid Python code, this opens up the
opportunity to use `typing.overload` to fully expose those.

Edge-cases in type-hints will be addressed in later commits.
This commit is contained in:
lojack5
2023-10-17 17:11:26 -06:00
parent a28de82bbb
commit fa2bde419f
2 changed files with 48 additions and 16 deletions

View File

@@ -462,9 +462,6 @@ class FunctionDef(BaseDef, FixWxPrefix):
"""
Create a pythonized version of the argsString in function and method
items that can be used as part of the docstring.
TODO: Maybe (optionally) use this syntax to document arg types?
http://www.python.org/dev/peps/pep-3107/
"""
params = list()
returns = list()
@@ -477,6 +474,7 @@ class FunctionDef(BaseDef, FixWxPrefix):
'wxString()': '""',
'wxArrayString()' : '[]',
'wxArrayInt()' : '[]',
'wxEmptyString': "''", # Makes signatures much shorter
}
if isinstance(self, CppMethodDef):
# rip apart the argsString instead of using the (empty) list of parameters
@@ -495,7 +493,14 @@ class FunctionDef(BaseDef, FixWxPrefix):
else:
default = self.fixWxPrefix(default, True)
# now grab just the last word, it should be the variable name
arg = arg.split()[-1]
# The rest will be the type information
arg_type, arg = arg.rsplit(None, 1)
arg, arg_type = self.parseNameAndType(arg, arg_type)
if arg_type:
if default == 'None':
arg = f'{arg}: {arg_type} | None'
else:
arg = f'{arg}: {arg_type}'
if default:
arg += '=' + default
params.append(arg)
@@ -506,25 +511,33 @@ class FunctionDef(BaseDef, FixWxPrefix):
continue
if param.arraySize:
continue
s = param.pyName or param.name
s, param_type = self.parseNameAndType(param.pyName or param.name, param.type)
if param.out:
returns.append(s)
if param_type:
returns.append(param_type)
else:
if param.inOut:
returns.append(s)
if param_type:
returns.append(param_type)
if param_type:
s = f'{s}: {param_type}'
if param.default:
default = param.default
if default in defValueMap:
default = defValueMap.get(default)
if param_type and default == 'None':
s = f'{s} | None'
default = '|'.join([self.cleanName(x, True) for x in default.split('|')])
s = f'{s}={default}'
params.append(s)
self.pyArgsString = '(' + ', '.join(params) + ')'
if len(returns) == 1:
self.pyArgsString += ' -> ' + returns[0]
if len(returns) > 1:
self.pyArgsString += ' -> (' + ', '.join(returns) + ')'
self.pyArgsString = f"({', '.join(params)})"
if not returns:
self.pyArgsString = f'{self.pyArgsString} -> None'
elif len(returns) == 1:
self.pyArgsString = f'{self.pyArgsString} -> {returns[0]}'
elif len(returns) > 1:
self.pyArgsString = f"{self.pyArgsString} -> tuple[{', '.join(returns)}]"
def collectPySignatures(self):

View File

@@ -169,11 +169,12 @@ class FixWxPrefix(object):
type_map = {
# Some types are guesses, marked with TODO to verify automatic
# conversion actually happens. Also, these are the type-names
# after processing by cleanName (so spaces are removed)
# after processing by cleanName (so spaces are removed), or
# after potentially lopping off an 'Array' prefix.
# --String types
'String': 'str',
'Char': 'str',
'char':' str',
'char': 'str',
'FileName': 'str', # TODO: check conversion
# --Int types
'byte': 'int',
@@ -196,20 +197,38 @@ class FixWxPrefix(object):
# --Others
'PyObject': 'Any',
'WindowID': 'int', # defined in wx/defs.h
# A few instances, for example in LogInfo:
}
type_name = self.cleanName(type_name)
# Special handling of Vector<type> types -
if type_name.startswith('Vector<') and type_name.endswith('>'):
# Special handling for 'Vector<type>' types
type_name = self.cleanName(type_name[7:-1])
type_name = self.cleanType(type_name[7:-1])
return f'list[{type_name}]'
if type_name.startswith('Array'):
type_name = self.cleanName(type_name[5:])
type_name = self.cleanType(type_name[5:])
if type_name:
return f'list[{type_name}]'
else:
return 'list'
return type_map.get(type_name, type_name)
def parseNameAndType(self, name_string: str, type_string: str | None) -> tuple[str, str | None]:
"""Given an identifier name and an optional type annotation, process
these per cleanName and cleanType. Further performs transforms on the
identifier name that may be required due to the type annotation.
Ex. The transformation "any_identifier : ..." -> "*args" requires
modifying both the identifier name and the annotation.
"""
name_string = self.cleanName(name_string)
if type_string:
type_string = self.cleanType(type_string)
if type_string == '...':
name_string = '*args'
type_string = None
if not type_string:
type_string = None
return name_string, type_string
def ignoreAssignmentOperators(node):