Add complete downstream API gate
Some checks failed
Rust API gates / api-gates (push) Has been cancelled
Some checks failed
Rust API gates / api-gates (push) Has been cancelled
This commit is contained in:
@@ -17,6 +17,7 @@ ROOT = Path(__file__).resolve().parents[1]
|
||||
CATALOG = ROOT / "api/public-api.json"
|
||||
LEDGER = ROOT / "api/RUST-MAPPING.tsv"
|
||||
COVERAGE = ROOT / "api/SHIM-COVERAGE.md"
|
||||
COMPILE_FIXTURE = ROOT / "tests/api-compile/src/lib.rs"
|
||||
|
||||
TARGETS = {
|
||||
"LibreMetaverse.Imaging.Abstractions": ROOT / "crates/libremetaverse-imaging/src/generated.rs",
|
||||
@@ -327,6 +328,124 @@ def generic_suffix(names: list[str]) -> str:
|
||||
return "<" + ", ".join(names) + ">" if names else ""
|
||||
|
||||
|
||||
def generic_declarations(item: dict, mapper: mapping.Mapper, owner_names: list[str]) -> list[str]:
|
||||
generics = set(owner_names) | set(generic_names(item))
|
||||
declarations = []
|
||||
for parameter in item.get("generic_parameters", []):
|
||||
bounds = [mapper.constraint(constraint, generics) for constraint in parameter.get("constraints", [])]
|
||||
if parameter.get("default_constructor"):
|
||||
bounds.append("Default")
|
||||
declarations.append(parameter["name"] + ((": " + " + ".join(bounds)) if bounds else ""))
|
||||
return declarations
|
||||
|
||||
|
||||
def function_name(signature: str) -> str:
|
||||
match = re.search(r"\bfn ([A-Za-z_][A-Za-z0-9_]*)", signature)
|
||||
if not match:
|
||||
raise ValueError(f"invalid mapped function signature: {signature}")
|
||||
return match.group(1)
|
||||
|
||||
|
||||
def render_compile_fixture(
|
||||
catalog: dict,
|
||||
type_by_id: dict[str, dict[str, str]],
|
||||
member_rows: dict[str, dict[str, str]],
|
||||
mapper: mapping.Mapper,
|
||||
) -> str:
|
||||
lines = [
|
||||
"// @generated by tools/generate_api_shims.py; do not edit by hand.",
|
||||
"#![allow(clippy::all, clippy::pedantic, dead_code, unused_variables)]",
|
||||
"#![allow(non_camel_case_types, non_snake_case)]",
|
||||
"",
|
||||
'fn argument<T>() -> T { panic!("compile-only API fixture") }',
|
||||
"",
|
||||
]
|
||||
type_index = member_index = 0
|
||||
for assembly in catalog["assemblies"]:
|
||||
for item in assembly["types"]:
|
||||
type_index += 1
|
||||
type_row = type_by_id[item["doc_id"]]
|
||||
owner_names = generic_names(item)
|
||||
owner_path = type_row["rust_path"]
|
||||
owner_type = owner_path + generic_suffix(owner_names)
|
||||
owner_expression = owner_path + (("::<" + ", ".join(owner_names) + ">") if owner_names else "")
|
||||
trait = item["kind"] == "interface"
|
||||
type_parameters = [*owner_names]
|
||||
if trait:
|
||||
type_parameters.append(f"__Probe: {owner_type}")
|
||||
type_argument = "&__Probe"
|
||||
else:
|
||||
type_argument = f"&{owner_type}"
|
||||
declarations = generic_suffix(type_parameters)
|
||||
lines += [
|
||||
f"// C# type: `{item['doc_id']}`.",
|
||||
f"fn type_{type_index:04}{declarations}(_: {type_argument}) {{}}",
|
||||
"",
|
||||
]
|
||||
|
||||
for member in item["members"]:
|
||||
member_index += 1
|
||||
row = member_rows[member["doc_id"]]
|
||||
method_names = generic_names(member)
|
||||
parameters = [*owner_names, *generic_declarations(member, mapper, owner_names)]
|
||||
if trait:
|
||||
parameters.append(f"__Probe: {owner_type}")
|
||||
declaration = generic_suffix(parameters)
|
||||
instance = not member.get("static") and member["kind"] not in {
|
||||
"constructor",
|
||||
"constant",
|
||||
"enum_value",
|
||||
}
|
||||
value_parameter = (
|
||||
f"value: &mut {'__Probe' if trait else owner_type}" if instance else ""
|
||||
)
|
||||
body = []
|
||||
if member["kind"] == "field" and not member.get("static"):
|
||||
field = re.fullmatch(r"pub ([^:]+): .+", row["rust_signature"])
|
||||
if not field:
|
||||
raise ValueError(f"invalid mapped field signature: {row['rust_signature']}")
|
||||
body.append(f"let _ = &value.{field.group(1)};")
|
||||
elif member["kind"] in {"constant", "enum_value"}:
|
||||
pattern = r"pub const ([A-Za-z_][A-Za-z0-9_]*)" if member["kind"] == "constant" else r"([A-Za-z_][A-Za-z0-9_]*):"
|
||||
name = re.match(pattern, row["rust_signature"])
|
||||
if not name:
|
||||
raise ValueError(f"invalid mapped value signature: {row['rust_signature']}")
|
||||
body.append(f"let _ = {owner_expression}::{name.group(1)};")
|
||||
else:
|
||||
signatures = row["rust_signature"].split(" ; ")
|
||||
property_argument_counts = []
|
||||
if member["kind"] in {"property", "indexer"}:
|
||||
accessors = {accessor["kind"] for accessor in member.get("accessors", [])}
|
||||
if "get" in accessors:
|
||||
property_argument_counts.append(len(member.get("parameters", [])))
|
||||
if "set" in accessors:
|
||||
property_argument_counts.append(len(member.get("parameters", [])) + 1)
|
||||
for signature_index, signature in enumerate(signatures):
|
||||
name = function_name(signature)
|
||||
generic_call = (("::<" + ", ".join(method_names) + ">") if method_names else "")
|
||||
if instance:
|
||||
target = f"value.{name}{generic_call}"
|
||||
elif trait:
|
||||
target = f"<__Probe as {owner_type}>::{name}{generic_call}"
|
||||
else:
|
||||
target = f"{owner_expression}::{name}{generic_call}"
|
||||
argument_count = len(member.get("parameters", []))
|
||||
if member["kind"] == "event":
|
||||
argument_count = 1
|
||||
elif property_argument_counts:
|
||||
argument_count = property_argument_counts[signature_index]
|
||||
arguments = ", ".join("argument()" for _ in range(argument_count))
|
||||
body.append(f"let _ = {target}({arguments});")
|
||||
lines += [
|
||||
f"// C# member: `{member['doc_id']}`.",
|
||||
f"fn member_{member_index:05}{declaration}({value_parameter}) {{",
|
||||
*[f" {line}" for line in body],
|
||||
"}",
|
||||
"",
|
||||
]
|
||||
return format_rust("\n".join(lines).rstrip() + "\n")
|
||||
|
||||
|
||||
def flags_enum(item: dict) -> bool:
|
||||
return any(attribute["type"] == "System.FlagsAttribute" for attribute in item.get("attributes", []))
|
||||
|
||||
@@ -603,6 +722,7 @@ def generate_sources(catalog: dict) -> tuple[dict[Path, str], dict[str, tuple[in
|
||||
)
|
||||
outputs[TARGETS.get(name, MAIN_TARGET)] = source
|
||||
coverage[name] = (len(selected), sum(len(item["members"]) for item in selected), len(selected) == len(assembly["types"]))
|
||||
outputs[COMPILE_FIXTURE] = render_compile_fixture(catalog, type_by_id, member_rows, mapper)
|
||||
return outputs, coverage
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user