mirror of
https://github.com/Significant-Gravitas/AutoGPT-Code-Ability.git
synced 2026-11-11 00:21:27 -10:00
This is a large but a much needed change to allow more sophisticated static code analysis to be added. PyRight (used by pylance) will allow us to detect: Misuse of async functions. Misuse of optional type and other annotated types. Wrong usage of pydantic and prisma model. Wrong usage of third-party library. The overhaul is required to allow us retrying while fixing the code as a project (not only as a function) and allow us to keep adding non-LLM fixes to it so that we can avoid wasting the retry. The scope of this PR is still a refactoring and the adding of the PyRight linter, so this PR try to keep the existing behaviour. The enablement and more tuning on the linter will be done in the followup PR. --------- Co-authored-by: majdyz <[email protected]>
166 lines
4.7 KiB
Python
166 lines
4.7 KiB
Python
from typing import List, Tuple
|
|
|
|
from prisma.models import ObjectType
|
|
|
|
OPEN_BRACES = {"{": "Dict", "[": "List", "(": "Tuple"}
|
|
CLOSE_BRACES = {"}": "Dict", "]": "List", ")": "Tuple"}
|
|
|
|
RENAMED_TYPES = {
|
|
"dict": "Dict",
|
|
"list": "List",
|
|
"tuple": "Tuple",
|
|
"set": "Set",
|
|
"frozenset": "FrozenSet",
|
|
"type": "Type",
|
|
}
|
|
|
|
|
|
def unwrap_object_type(type: str) -> Tuple[str, List[str]]:
|
|
"""
|
|
Get the type and children of a composite type.
|
|
Args:
|
|
type (str): The type to parse.
|
|
Returns:
|
|
str: The type.
|
|
[str]: The children types.
|
|
"""
|
|
type = type.replace(" ", "")
|
|
if not type:
|
|
return "", []
|
|
|
|
def split_outer_level(type: str, separator: str) -> List[str]:
|
|
brace_count = 0
|
|
last_index = 0
|
|
splits = []
|
|
|
|
for i, c in enumerate(type):
|
|
if c in OPEN_BRACES:
|
|
brace_count += 1
|
|
elif c in CLOSE_BRACES:
|
|
brace_count -= 1
|
|
elif c == separator and brace_count == 0:
|
|
splits.append(type[last_index:i])
|
|
last_index = i + 1
|
|
|
|
splits.append(type[last_index:])
|
|
return splits
|
|
|
|
# Unwrap primitive union types
|
|
union_split = split_outer_level(type, "|")
|
|
if len(union_split) > 1:
|
|
if len(union_split) == 2 and "None" in union_split:
|
|
return "Optional", [v for v in union_split if v != "None"]
|
|
return "Union", union_split
|
|
|
|
# Unwrap primitive dict/list/tuple types
|
|
if type[0] in OPEN_BRACES and type[-1] in CLOSE_BRACES:
|
|
type_name = OPEN_BRACES[type[0]]
|
|
type_children = split_outer_level(type[1:-1], ",")
|
|
return type_name, type_children
|
|
|
|
brace_pos = type.find("[")
|
|
if brace_pos != -1 and type[-1] == "]":
|
|
# Unwrap normal composite types
|
|
type_name = type[:brace_pos]
|
|
type_children = split_outer_level(type[brace_pos + 1 : -1], ",")
|
|
else:
|
|
# Non-composite types, no need to unwrap
|
|
type_name = type
|
|
type_children = []
|
|
|
|
return RENAMED_TYPES.get(type_name, type_name), type_children
|
|
|
|
|
|
def is_type_equal(type1: str | None, type2: str | None) -> bool:
|
|
"""
|
|
Check if two types are equal.
|
|
This function handle composite types like list, dict, and tuple.
|
|
group similar types like list[str], List[str], and [str] as equal.
|
|
"""
|
|
if type1 is None and type2 is None:
|
|
return True
|
|
if type1 is None or type2 is None:
|
|
return False
|
|
|
|
evaluated_type1, children1 = unwrap_object_type(type1)
|
|
evaluated_type2, children2 = unwrap_object_type(type2)
|
|
|
|
# Compare the class name of the types (ignoring the module)
|
|
# TODO(majdyz): compare the module name as well.
|
|
if evaluated_type1.split(".")[-1] != evaluated_type2.split(".")[-1]:
|
|
return False
|
|
|
|
if len(children1) != len(children2):
|
|
return False
|
|
|
|
if len(children1) == len(children2) == 0:
|
|
return True
|
|
|
|
for c1, c2 in zip(children1, children2):
|
|
if not is_type_equal(c1, c2):
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
def extract_field_type(field_type: str | None) -> set[str]:
|
|
"""
|
|
Extract the field type from a composite type.
|
|
e.g. tuple[str, dict[str, int]] -> {tuple, dict, str, int}
|
|
|
|
Args:
|
|
field_type (str): The field type to parse.
|
|
Returns:
|
|
list[str]: The extracted field types.
|
|
"""
|
|
if field_type is None:
|
|
return set()
|
|
parent_type, children = unwrap_object_type(field_type)
|
|
|
|
result = {parent_type}
|
|
for child in children:
|
|
result |= extract_field_type(child)
|
|
return result
|
|
|
|
|
|
def normalize_type(type: str, renamed_types: dict[str, str] = {}) -> str:
|
|
"""
|
|
Normalize the type to a standard format.
|
|
e.g. list[str] -> List[str], dict[str, int | float] -> Dict[str, Union[int, float]]
|
|
|
|
Args:
|
|
type (str): The type to normalize.
|
|
Returns:
|
|
str: The normalized type.
|
|
"""
|
|
parent_type, children = unwrap_object_type(type)
|
|
|
|
if parent_type in renamed_types:
|
|
parent_type = renamed_types[parent_type]
|
|
|
|
if len(children) == 0:
|
|
return parent_type
|
|
|
|
return f"{parent_type}[{', '.join([normalize_type(c, renamed_types) for c in children])}]"
|
|
|
|
|
|
def get_related_types(
|
|
type: str, available_objects: dict[str, ObjectType]
|
|
) -> list[ObjectType]:
|
|
"""
|
|
Get the related types of a composite type.
|
|
e.g. tuple[Obj1, dict[Obj2, Obj3]] with {Obj1, Obj3} -> [Obj1, Obj3]
|
|
|
|
Args:
|
|
type (str): The type to parse.
|
|
available_objects (dict[str, ObjectType]): The available objects.
|
|
|
|
Returns:
|
|
list[ObjectType]: The related types.
|
|
"""
|
|
return [
|
|
available_objects[related_type]
|
|
for related_type in extract_field_type(type)
|
|
if related_type in available_objects
|
|
]
|