Files
MetaCrate/tools/generate_api_shims.py

1083 lines
52 KiB
Python

#!/usr/bin/env python3
"""Generate callable, failure-only Rust shims from the authoritative catalog."""
from __future__ import annotations
import argparse
import csv
import json
import re
import subprocess
from collections import Counter
from pathlib import Path
import generate_rust_mapping as mapping
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",
"LibreMetaverse.Imaging.Skia": ROOT / "crates/libremetaverse-imaging-skia/src/generated.rs",
"LibreMetaverse.LslTools": ROOT / "crates/libremetaverse-lsl-tools/src/generated.rs",
"LibreMetaverse.PrimMesher": ROOT / "crates/libremetaverse-prim-mesher/src/generated.rs",
"LibreMetaverse.RLV": ROOT / "crates/libremetaverse-rlv/src/generated.rs",
"LibreMetaverse.Rendering.MeshFoundry": ROOT / "crates/libremetaverse-rendering-mesh-foundry/src/generated.rs",
"LibreMetaverse.Rendering.Simple": ROOT / "crates/libremetaverse-rendering-simple/src/generated.rs",
"LibreMetaverse.Types": ROOT / "crates/libremetaverse-types/src/generated.rs",
"LibreMetaverse.StructuredData": ROOT / "crates/libremetaverse-structured-data/src/generated.rs",
"LibreMetaverse.Utilities": ROOT / "crates/libremetaverse-utilities/src/generated.rs",
"LibreMetaverse.Voice.Vivox": ROOT / "crates/libremetaverse-voice-vivox/src/generated.rs",
"LibreMetaverse.Voice.WebRTC": ROOT / "crates/libremetaverse-voice-webrtc/src/generated.rs",
}
# Types whose failure-only generated shells have been replaced by native Rust
# implementations. The generated module keeps catalog markers and re-exports
# the hand-written type so coverage remains deterministic.
NATIVE_TYPES = {
"T:LibreMetaverse.StructuredData.OSD": "crate::model::OSD",
"T:LibreMetaverse.StructuredData.OSDArray": "crate::model::OSDArray",
"T:LibreMetaverse.StructuredData.OSDBinary": "crate::model::OSDBinary",
"T:LibreMetaverse.StructuredData.OSDBoolean": "crate::model::OSDBoolean",
"T:LibreMetaverse.StructuredData.OSDDate": "crate::model::OSDDate",
"T:LibreMetaverse.StructuredData.OSDException": "crate::model::OSDException",
"T:LibreMetaverse.StructuredData.OSDFormat": "crate::model::OSDFormat",
"T:LibreMetaverse.StructuredData.OSDInteger": "crate::model::OSDInteger",
"T:LibreMetaverse.StructuredData.OSDLlsdXml": "crate::model::OSDLlsdXml",
"T:LibreMetaverse.StructuredData.OSDMap": "crate::model::OSDMap",
"T:LibreMetaverse.StructuredData.OSDReal": "crate::model::OSDReal",
"T:LibreMetaverse.StructuredData.OSDString": "crate::model::OSDString",
"T:LibreMetaverse.StructuredData.OSDType": "crate::model::OSDType",
"T:LibreMetaverse.StructuredData.OSDUUID": "crate::model::OSDUUID",
"T:LibreMetaverse.StructuredData.OSDUri": "crate::model::OSDUri",
"T:LibreMetaverse.CacheDictionary`2": "crate::collections::CacheDictionary",
"T:LibreMetaverse.CacheDictionary`2.CacheDictionaryEnumerator": "crate::collections::CacheDictionaryCacheDictionaryEnumerator",
"T:LibreMetaverse.Color4": "crate::color4::Color4",
"T:LibreMetaverse.CRC32": "crate::crc32::CRC32",
"T:LibreMetaverse.DoubleDictionary`3": "crate::collections::DoubleDictionary",
"T:LibreMetaverse.EmptyRemovalStrategy`1": "crate::collections::EmptyRemovalStrategy",
"T:LibreMetaverse.EnumInfoAttribute": "crate::enum_compat::EnumInfoAttribute",
"T:LibreMetaverse.EnumInfoExtensions": "crate::enum_compat::EnumInfoExtensions",
"T:LibreMetaverse.ExpiringCache`2": "crate::collections::ExpiringCache",
"T:LibreMetaverse.ICacheDictionaryRemovalStrategy`1": "crate::collections::ICacheDictionaryRemovalStrategy",
"T:LibreMetaverse.LruRemovalStrategy`1": "crate::collections::LruRemovalStrategy",
"T:LibreMetaverse.Matrix4": "crate::geometry::Matrix4",
"T:LibreMetaverse.MruRemovalStrategy`1": "crate::collections::MruRemovalStrategy",
"T:LibreMetaverse.MultiValueDictionary`2": "crate::collections::MultiValueDictionary",
"T:LibreMetaverse.Quaternion": "crate::geometry::Quaternion",
"T:LibreMetaverse.Ray": "crate::vectors::Ray",
"T:LibreMetaverse.TokenBucket": "crate::collections::TokenBucket",
"T:LibreMetaverse.UUID": "crate::uuid::UUID",
"T:LibreMetaverse.Vector2": "crate::vectors::Vector2",
"T:LibreMetaverse.Vector3": "crate::vectors::Vector3",
"T:LibreMetaverse.Vector3d": "crate::vectors::Vector3d",
"T:LibreMetaverse.Vector4": "crate::vectors::Vector4",
"T:LibreMetaverse.Utils": "crate::utils::Utils",
}
NATIVE_MEMBER_BODIES = {
"M:LibreMetaverse.StructuredData.OSDParser.BufferCharactersEqual(System.IO.StringReader,System.Char[],System.Int32)":
"crate::notation::buffer_characters_equal(reader, buffer, offset)",
"M:LibreMetaverse.StructuredData.OSDParser.ConsumeBytes(System.IO.Stream,System.Int32)":
"crate::binary::consume_bytes(stream, consume_bytes)",
"M:LibreMetaverse.StructuredData.OSDParser.DeserializeLLSDBinary(System.Byte[])":
"crate::binary::deserialize_bytes(binary_data)",
"M:LibreMetaverse.StructuredData.OSDParser.DeserializeLLSDBinary(System.IO.Stream)":
"crate::binary::deserialize_stream(stream)",
"M:LibreMetaverse.StructuredData.OSDParser.DeserializeLLSDNotation(System.IO.StringReader)":
"crate::notation::deserialize_reader(reader)",
"M:LibreMetaverse.StructuredData.OSDParser.DeserializeLLSDNotation(System.String)":
"crate::notation::deserialize_string(notation_data)",
"M:LibreMetaverse.StructuredData.OSDParser.DeserializeLLSDProtobuf(System.Byte[])":
"crate::protobuf::deserialize_bytes(data)",
"M:LibreMetaverse.StructuredData.OSDParser.DeserializeLLSDProtobuf(System.IO.Stream)":
"crate::protobuf::deserialize_stream(stream)",
"M:LibreMetaverse.StructuredData.OSDParser.DeserializeJson(System.IO.Stream)":
"crate::json_codec::deserialize_stream(json)",
"M:LibreMetaverse.StructuredData.OSDParser.DeserializeJson(System.String)":
"crate::json_codec::deserialize_string(json)",
"M:LibreMetaverse.StructuredData.OSDParser.DeserializeLLSDXml(System.Byte[])":
"crate::xml_codec::deserialize_bytes(xml_data)",
"M:LibreMetaverse.StructuredData.OSDParser.DeserializeLLSDXml(System.IO.Stream)":
"crate::xml_codec::deserialize_stream(xml_stream)",
"M:LibreMetaverse.StructuredData.OSDParser.DeserializeLLSDXml(System.String)":
"crate::xml_codec::deserialize_string(xml_data)",
"M:LibreMetaverse.StructuredData.OSDParser.DeserializeLLSDXml(System.Xml.XmlReader)":
"crate::xml_codec::deserialize_reader(xml_data)",
"M:LibreMetaverse.StructuredData.OSDParser.EscapeCharacter(System.String,System.Char)":
"crate::notation::escape_character(s, c)",
"M:LibreMetaverse.StructuredData.OSDParser.FindByte(System.IO.Stream,System.Byte)":
"crate::binary::find_byte(stream, to_find)",
"M:LibreMetaverse.StructuredData.OSDParser.FindString(System.IO.Stream,System.String)":
"crate::binary::find_string(stream, to_find)",
"M:LibreMetaverse.StructuredData.OSDParser.GetLengthInBrackets(System.IO.StringReader)":
"crate::notation::get_length_in_brackets(reader)",
"M:LibreMetaverse.StructuredData.OSDParser.GetStringDelimitedBy(System.IO.StringReader,System.Char)":
"crate::notation::get_string_delimited_by(reader, delimiter)",
"M:LibreMetaverse.StructuredData.OSDParser.HostToNetworkIntBytes(System.Int32)":
"crate::binary::host_to_network_int_bytes(int_host_end)",
"M:LibreMetaverse.StructuredData.OSDParser.NetworkToHostDouble(System.Byte[])":
"crate::binary::network_to_host_double(binary_net_end)",
"M:LibreMetaverse.StructuredData.OSDParser.NetworkToHostInt(System.Byte[])":
"crate::binary::network_to_host_int(binary_net_end)",
"M:LibreMetaverse.StructuredData.OSDParser.PeekAndSkipWhitespace(System.IO.StringReader)":
"crate::notation::peek_and_skip_whitespace(reader)",
"M:LibreMetaverse.StructuredData.OSDParser.ReadAndSkipWhitespace(System.IO.StringReader)":
"crate::notation::read_and_skip_whitespace(reader)",
"M:LibreMetaverse.StructuredData.OSDParser.SerializeLLSDBinary(LibreMetaverse.StructuredData.OSD)":
"crate::binary::serialize(osd)",
"M:LibreMetaverse.StructuredData.OSDParser.SerializeLLSDBinary(LibreMetaverse.StructuredData.OSD,System.Boolean)":
"crate::binary::serialize_with_header(osd, prepend_header)",
"M:LibreMetaverse.StructuredData.OSDParser.SerializeLLSDBinaryStream(LibreMetaverse.StructuredData.OSD)":
"crate::binary::serialize_stream(data)",
"M:LibreMetaverse.StructuredData.OSDParser.SerializeLLSDBinaryStream(LibreMetaverse.StructuredData.OSD,System.Boolean)":
"crate::binary::serialize_stream_with_header(data, prepend_header)",
"M:LibreMetaverse.StructuredData.OSDParser.SerializeJsonString(LibreMetaverse.StructuredData.OSD,System.Boolean)":
"crate::json_codec::serialize(osd, preserve_defaults)",
"M:LibreMetaverse.StructuredData.OSDParser.SerializeLLSDInnerXmlString(LibreMetaverse.StructuredData.OSD)":
"crate::xml_codec::serialize_inner(data)",
"M:LibreMetaverse.StructuredData.OSDParser.SerializeLLSDNotation(LibreMetaverse.StructuredData.OSD)":
"crate::notation::serialize(osd)",
"M:LibreMetaverse.StructuredData.OSDParser.SerializeLLSDNotationFormatted(LibreMetaverse.StructuredData.OSD)":
"crate::notation::serialize_formatted(osd)",
"M:LibreMetaverse.StructuredData.OSDParser.SerializeLLSDNotationStream(LibreMetaverse.StructuredData.OSD)":
"crate::notation::serialize_stream(osd)",
"M:LibreMetaverse.StructuredData.OSDParser.SerializeLLSDNotationStreamFormatted(LibreMetaverse.StructuredData.OSD)":
"crate::notation::serialize_stream_formatted(osd)",
"M:LibreMetaverse.StructuredData.OSDParser.SerializeLLSDProtobuf(LibreMetaverse.StructuredData.OSD,System.Boolean)":
"crate::protobuf::serialize(osd, prepend_header)",
"M:LibreMetaverse.StructuredData.OSDParser.SerializeLLSDXmlBytes(LibreMetaverse.StructuredData.OSD)":
"crate::xml_codec::serialize_bytes(data)",
"M:LibreMetaverse.StructuredData.OSDParser.SerializeLLSDXmlElement(System.Xml.XmlWriter,LibreMetaverse.StructuredData.OSD)":
"crate::xml_codec::serialize_element(writer, data)",
"M:LibreMetaverse.StructuredData.OSDParser.SerializeLLSDXmlString(LibreMetaverse.StructuredData.OSD)":
"crate::xml_codec::serialize_string(data)",
"M:LibreMetaverse.StructuredData.OSDParser.SkipWhiteSpace(System.IO.Stream)":
"crate::binary::skip_whitespace(stream)",
"M:LibreMetaverse.StructuredData.OSDParser.UnescapeCharacter(System.String,System.Char)":
"crate::notation::unescape_character(s, c)",
"M:LibreMetaverse.StructuredData.OSDParser.Deserialize(System.Byte[])":
"crate::dispatch::deserialize_bytes(data)",
"M:LibreMetaverse.StructuredData.OSDParser.Deserialize(System.IO.Stream)":
"crate::dispatch::deserialize_stream(stream)",
"M:LibreMetaverse.StructuredData.OSDParser.Deserialize(System.String)":
"crate::dispatch::deserialize_string(data)",
"M:LibreMetaverse.Utils.ReadSingleLittleEndian(System.Byte[],System.Int32)":
"crate::byte_order::read_single_little_endian(src, pos)",
"M:LibreMetaverse.Utils.WriteSingleLittleEndian(System.Byte[],System.Int32,System.Single)":
"crate::byte_order::write_single_little_endian(dest, pos, value)",
"M:LibreMetaverse.Utils.ReadDoubleLittleEndian(System.Byte[],System.Int32)":
"crate::byte_order::read_double_little_endian(src, pos)",
"M:LibreMetaverse.Utils.WriteDoubleLittleEndian(System.Byte[],System.Int32,System.Double)":
"crate::byte_order::write_double_little_endian(dest, pos, value)",
}
NATIVE_OWNER_BOUNDS = {
"T:LibreMetaverse.CacheDictionary`2": {
"TKey": "TKey: Clone + PartialEq",
"TValue": "TValue: Clone + PartialEq",
},
"T:LibreMetaverse.CacheDictionary`2.CacheDictionaryEnumerator": {
"TKey": "TKey: Clone",
"TValue": "TValue: Clone",
},
"T:LibreMetaverse.DoubleDictionary`3": {
"TKey1": "TKey1: Clone + PartialEq",
"TKey2": "TKey2: Clone + PartialEq",
"TValue": "TValue: Clone + PartialEq",
},
"T:LibreMetaverse.EmptyRemovalStrategy`1": {"TKey": "TKey: Clone + PartialEq + Send"},
"T:LibreMetaverse.ExpiringCache`2": {
"TKey": "TKey: Clone + PartialEq",
"TValue": "TValue: Clone",
},
"T:LibreMetaverse.LruRemovalStrategy`1": {"TKey": "TKey: Clone + PartialEq + Send"},
"T:LibreMetaverse.MruRemovalStrategy`1": {"TKey": "TKey: Clone + PartialEq + Send"},
"T:LibreMetaverse.MultiValueDictionary`2": {
"TKey": "TKey: Clone + PartialEq",
"TValue": "TValue: Clone + PartialEq",
},
}
MAIN_TARGET = ROOT / "crates/libremetaverse/src/generated.rs"
WIRE_NAMESPACES = (
"LibreMetaverse.Assets",
"LibreMetaverse.Formatters",
"LibreMetaverse.Imaging",
"LibreMetaverse.ImportExport",
"LibreMetaverse.Materials",
"LibreMetaverse.Messages",
"LibreMetaverse.Packets",
"LibreMetaverse.Rendering",
)
WIRE_ROOT_TYPES = {
"T:LibreMetaverse.Animation",
"T:LibreMetaverse.BitPack",
"T:LibreMetaverse.Bumpiness",
"T:LibreMetaverse.FieldType",
"T:LibreMetaverse.GLTFMaterialUpdate",
"T:LibreMetaverse.Grass",
"T:LibreMetaverse.GrassDefinition",
"T:LibreMetaverse.Interfaces.IMessage",
"T:LibreMetaverse.LiveParticle",
"T:LibreMetaverse.MappingType",
"T:LibreMetaverse.MediaControls",
"T:LibreMetaverse.MediaEntry",
"T:LibreMetaverse.MediaPermission",
"T:LibreMetaverse.NameValue",
"T:LibreMetaverse.NameValue.ClassType",
"T:LibreMetaverse.NameValue.SendtoType",
"T:LibreMetaverse.NameValue.ValueType",
"T:LibreMetaverse.PacketFrequency",
"T:LibreMetaverse.PermissionMask",
"T:LibreMetaverse.PermissionWho",
"T:LibreMetaverse.Permissions",
"T:LibreMetaverse.Shininess",
"T:LibreMetaverse.TextureAttributes",
"T:LibreMetaverse.Tree",
"T:LibreMetaverse.TreeDefinition",
}
COMPOSED_BASE_TYPES = {
"T:LibreMetaverse.InventoryAnimation",
"T:LibreMetaverse.InventoryAttachment",
"T:LibreMetaverse.InventoryCallingCard",
"T:LibreMetaverse.InventoryCategory",
"T:LibreMetaverse.InventoryFolder",
"T:LibreMetaverse.InventoryGesture",
"T:LibreMetaverse.InventoryItem",
"T:LibreMetaverse.InventoryLSL",
"T:LibreMetaverse.InventoryLandmark",
"T:LibreMetaverse.InventoryMaterial",
"T:LibreMetaverse.InventoryNotecard",
"T:LibreMetaverse.InventoryObject",
"T:LibreMetaverse.InventorySettings",
"T:LibreMetaverse.InventorySnapshot",
"T:LibreMetaverse.InventorySound",
"T:LibreMetaverse.InventoryTexture",
"T:LibreMetaverse.InventoryWearable",
"T:LibreMetaverse.Messages.Linden.ChatSessionAcceptInvitation",
"T:LibreMetaverse.Messages.Linden.ChatSessionRequestMuteUpdate",
"T:LibreMetaverse.Messages.Linden.ChatSessionRequestStartConference",
"T:LibreMetaverse.Messages.Linden.MapLayerReplyVariant",
"T:LibreMetaverse.Packets.DirPlacesReplyPacket",
"T:LibreMetaverse.Packets.TestMessagePacket",
"T:LibreMetaverse.Rendering.Joint",
}
OPEN_ENUM_IDS = {"T:LibreMetaverse.BakeType", "T:LibreMetaverse.HoleType"}
TRAIT_SUPERTRAITS = {
"T:LibreMetaverse.IBakingTextureProvider": "std::any::Any",
"T:LibreMetaverse.Appearance.ICurrentOutfitPolicy": "Send + Sync",
}
CORE_NAMESPACES = (
"LibreMetaverse.Http",
"LibreMetaverse.Stats",
"LibreMetaverse.Threading",
)
CORE_ROOT_TYPES = {
"T:LibreMetaverse.CapabilitiesReceivedEventArgs",
"T:LibreMetaverse.Caps",
"T:LibreMetaverse.Caps.EventQueueCallback",
"T:LibreMetaverse.CapsCategory",
"T:LibreMetaverse.CapsEventDictionary",
"T:LibreMetaverse.CapsRateLimiter",
"T:LibreMetaverse.CapsRateLimiterOptions",
"T:LibreMetaverse.ConnectionSettings",
"T:LibreMetaverse.DisconnectedEventArgs",
"T:LibreMetaverse.DisposalHelper.DisposalGuard",
"T:LibreMetaverse.DisposalHelper",
"T:LibreMetaverse.EventQueueRunningEventArgs",
"T:LibreMetaverse.EventSubscription`1",
"T:LibreMetaverse.EventSubscriptionHelper",
"T:LibreMetaverse.FileHelper",
"T:LibreMetaverse.DictionaryChangeCallback",
"T:LibreMetaverse.DictionaryEventAction",
"T:LibreMetaverse.GenericStreamingMessageEventArgs",
"T:LibreMetaverse.GenericStreamingMethod",
"T:LibreMetaverse.GridClient",
"T:LibreMetaverse.GridClientBakingTextureProvider",
"T:LibreMetaverse.GridClientServiceCollectionExtensions",
"T:LibreMetaverse.Helpers",
"T:LibreMetaverse.HomeInfo",
"T:LibreMetaverse.HttpCapsClient",
"T:LibreMetaverse.IBakingTextureProvider",
"T:LibreMetaverse.IGridClient",
"T:LibreMetaverse.ImageCodec",
"T:LibreMetaverse.ImageDownload",
"T:LibreMetaverse.ImageReceiveProgressEventArgs",
"T:LibreMetaverse.ImageRequest",
"T:LibreMetaverse.ImageType",
"T:LibreMetaverse.IncomingPacketIDCollection",
"T:LibreMetaverse.Interfaces.IByteBufferPool",
"T:LibreMetaverse.Logger",
"T:LibreMetaverse.Logger.LogCallback",
"T:LibreMetaverse.LoggingSettings",
"T:LibreMetaverse.LoginCredential",
"T:LibreMetaverse.LoginException",
"T:LibreMetaverse.LoginParams",
"T:LibreMetaverse.LoginProgressEventArgs",
"T:LibreMetaverse.LoginResponseData",
"T:LibreMetaverse.LoginResponseData.ParseMessage",
"T:LibreMetaverse.LoginResponseData.ParseMessageLevel",
"T:LibreMetaverse.LoginResponseData.ParseResult",
"T:LibreMetaverse.LoginState",
"T:LibreMetaverse.LoginStatus",
"T:LibreMetaverse.LoggedOutEventArgs",
"T:LibreMetaverse.NetworkManager",
"T:LibreMetaverse.NetworkManager.DisconnectType",
"T:LibreMetaverse.NetworkManager.IncomingPacket",
"T:LibreMetaverse.NetworkManager.LoginResponseCallback",
"T:LibreMetaverse.NetworkManager.OutgoingPacket",
"T:LibreMetaverse.LastExecStatus",
"T:LibreMetaverse.ObservableDictionary`2",
"T:LibreMetaverse.PacketEventDictionary",
"T:LibreMetaverse.PacketReceivedEventArgs",
"T:LibreMetaverse.PacketSentEventArgs",
"T:LibreMetaverse.PacketSettings",
"T:LibreMetaverse.HttpCapsClient.ProgressReport",
"T:LibreMetaverse.ProtocolManager",
"T:LibreMetaverse.Repeat",
"T:LibreMetaverse.Settings",
"T:LibreMetaverse.SimChangedEventArgs",
"T:LibreMetaverse.SimConnectedEventArgs",
"T:LibreMetaverse.SimConnectingEventArgs",
"T:LibreMetaverse.SimDisconnectedEventArgs",
"T:LibreMetaverse.Simulator",
"T:LibreMetaverse.Simulator.SimStats",
"T:LibreMetaverse.SimulatorDataPool",
"T:LibreMetaverse.SimulatorFeatures",
"T:LibreMetaverse.SourceType",
"T:LibreMetaverse.StatusCode",
"T:LibreMetaverse.TargetType",
"T:LibreMetaverse.TexturePipeline",
"T:LibreMetaverse.TexturePipelineSettings",
"T:LibreMetaverse.TextureRequestState",
"T:LibreMetaverse.TimingSettings",
"T:LibreMetaverse.Transfer",
"T:LibreMetaverse.TransferError",
"T:LibreMetaverse.UDPBase",
"T:LibreMetaverse.UDPPacketBuffer",
"T:LibreMetaverse.XferDownload",
"T:LibreMetaverse.XferReceivedEventArgs",
}
AVATAR_NAMESPACES = (
"LibreMetaverse.Animesh",
"LibreMetaverse.Appearance",
)
AVATAR_TYPE_PREFIXES = (
"T:LibreMetaverse.Agent",
"T:LibreMetaverse.Animations",
"T:LibreMetaverse.AnimeshSkinning",
"T:LibreMetaverse.Appearance",
"T:LibreMetaverse.ArchetypeParam",
"T:LibreMetaverse.Asset",
"T:LibreMetaverse.Attention",
"T:LibreMetaverse.Avatar",
"T:LibreMetaverse.Inventory",
)
AVATAR_ROOT_TYPES = {
"T:LibreMetaverse.AISResponseMeta",
"T:LibreMetaverse.AccountLevelBenefits",
"T:LibreMetaverse.AlertMessageEventArgs",
"T:LibreMetaverse.BakeType",
"T:LibreMetaverse.BalanceEventArgs",
"T:LibreMetaverse.BinBVHAnimationReader",
"T:LibreMetaverse.BorderCrossingDirection",
"T:LibreMetaverse.CallingCardOfferedEventArgs",
"T:LibreMetaverse.CameraConstraintEventArgs",
"T:LibreMetaverse.ChannelType",
"T:LibreMetaverse.ChatAudibleLevel",
"T:LibreMetaverse.ChatEventArgs",
"T:LibreMetaverse.ChatSessionMember",
"T:LibreMetaverse.ChatSessionMemberAddedEventArgs",
"T:LibreMetaverse.ChatSessionMemberLeftEventArgs",
"T:LibreMetaverse.ChatSourceType",
"T:LibreMetaverse.ChatType",
"T:LibreMetaverse.ClassifiedAd",
"T:LibreMetaverse.ClassifiedInfoReplyEventArgs",
"T:LibreMetaverse.DeRezDestination",
"T:LibreMetaverse.DisplayNameUpdateEventArgs",
"T:LibreMetaverse.DrivenParamInfo",
"T:LibreMetaverse.EffectType",
"T:LibreMetaverse.ExperienceInfoEventArgs",
"T:LibreMetaverse.ExperiencePreferencesEventArgs",
"T:LibreMetaverse.FindObjectByPathReplyEventArgs",
"T:LibreMetaverse.FolderUpdatedEventArgs",
"T:LibreMetaverse.Genepool",
"T:LibreMetaverse.GenepoolArchetype",
"T:LibreMetaverse.GroupChatJoinedEventArgs",
"T:LibreMetaverse.HandPose",
"T:LibreMetaverse.InitiateDownloadEventArgs",
"T:LibreMetaverse.InstantMessage",
"T:LibreMetaverse.InstantMessageDialog",
"T:LibreMetaverse.InstantMessageEventArgs",
"T:LibreMetaverse.InstantMessageOnline",
"T:LibreMetaverse.ItemReceivedEventArgs",
"T:LibreMetaverse.LindenAttentions",
"T:LibreMetaverse.LoadUrlEventArgs",
"T:LibreMetaverse.LookAtType",
"T:LibreMetaverse.MeanCollisionEventArgs",
"T:LibreMetaverse.MeanCollisionType",
"T:LibreMetaverse.MoneyBalanceReplyEventArgs",
"T:LibreMetaverse.MoneyTransactionType",
"T:LibreMetaverse.MuteEntry",
"T:LibreMetaverse.MuteFlags",
"T:LibreMetaverse.MuteType",
"T:LibreMetaverse.NavMeshStatusUpdateEventArgs",
"T:LibreMetaverse.PayPriceReplyEventArgs",
"T:LibreMetaverse.PayPriceType",
"T:LibreMetaverse.PickInfoReplyEventArgs",
"T:LibreMetaverse.PointAtType",
"T:LibreMetaverse.ProductInfoEventArgs",
"T:LibreMetaverse.ProfileFlags",
"T:LibreMetaverse.ProfilePick",
"T:LibreMetaverse.RebakeAvatarTexturesEventArgs",
"T:LibreMetaverse.RegionCrossedEventArgs",
"T:LibreMetaverse.RegionCrossingPredictionEventArgs",
"T:LibreMetaverse.RegionExperiencesEventArgs",
"T:LibreMetaverse.SaveAssetToInventoryEventArgs",
"T:LibreMetaverse.ScriptControlChange",
"T:LibreMetaverse.ScriptControlEventArgs",
"T:LibreMetaverse.ScriptDialogEventArgs",
"T:LibreMetaverse.ScriptPermission",
"T:LibreMetaverse.ScriptQuestionEventArgs",
"T:LibreMetaverse.ScriptRunningReplyEventArgs",
"T:LibreMetaverse.ScriptSensorReplyEventArgs",
"T:LibreMetaverse.ScriptSensorTypeFlags",
"T:LibreMetaverse.SetDisplayNameReplyEventArgs",
"T:LibreMetaverse.SkeletalBoneInfo",
"T:LibreMetaverse.TaskInventoryReplyEventArgs",
"T:LibreMetaverse.TaskItemReceivedEventArgs",
"T:LibreMetaverse.TeleportEventArgs",
"T:LibreMetaverse.TeleportFlags",
"T:LibreMetaverse.TeleportLureFlags",
"T:LibreMetaverse.TeleportStatus",
"T:LibreMetaverse.TransactionFlags",
"T:LibreMetaverse.TransactionInfo",
"T:LibreMetaverse.UUIDNameReplyEventArgs",
"T:LibreMetaverse.ViewerBenefitsEventArgs",
"T:LibreMetaverse.ViewerEffectEventArgs",
"T:LibreMetaverse.ViewerEffectLookAtEventArgs",
"T:LibreMetaverse.ViewerEffectPointAtEventArgs",
"T:LibreMetaverse.VisualAlphaParam",
"T:LibreMetaverse.VisualColorOperation",
"T:LibreMetaverse.VisualColorParam",
"T:LibreMetaverse.VisualParam",
"T:LibreMetaverse.VisualParams",
"T:LibreMetaverse.VolumeMorphInfo",
"T:LibreMetaverse.binBVHJoint",
"T:LibreMetaverse.binBVHJointKey",
}
UNDERLYING = {
"System.SByte": "i8", "System.Byte": "u8", "System.Int16": "i16", "System.UInt16": "u16",
"System.Int32": "i32", "System.UInt32": "u32", "System.Int64": "i64", "System.UInt64": "u64",
}
PRIVATE_LAYOUTS = {
"T:LibreMetaverse.CacheDictionary`2": [("entries", "std::collections::HashMap<TKey, TValue>")],
"T:LibreMetaverse.DoubleDictionary`3": [
("by_first", "std::collections::HashMap<TKey1, TValue>"),
("by_second", "std::collections::HashMap<TKey2, TValue>"),
],
"T:LibreMetaverse.EmptyRemovalStrategy`1": [("keys", "Vec<TKey>")],
"T:LibreMetaverse.ExpiringCache`2": [("entries", "std::collections::HashMap<TKey, TValue>")],
"T:LibreMetaverse.LruRemovalStrategy`1": [("keys", "Vec<TKey>")],
"T:LibreMetaverse.MruRemovalStrategy`1": [("keys", "Vec<TKey>")],
"T:LibreMetaverse.MultiValueDictionary`2": [("entries", "std::collections::HashMap<TKey, Vec<TValue>>")],
"T:LibreMetaverse.TokenBucket": [("content", "i32"), ("max_burst", "i32"), ("drip_rate", "i32")],
"T:LibreMetaverse.UUID": [("bytes", "[u8; 16]")],
"T:LibreMetaverse.StructuredData.OSDArray": [("values", "Vec<OSD>")],
"T:LibreMetaverse.StructuredData.OSDBinary": [("value", "Vec<u8>")],
"T:LibreMetaverse.StructuredData.OSDBoolean": [("value", "bool")],
"T:LibreMetaverse.StructuredData.OSDDate": [("value", "std::time::SystemTime")],
"T:LibreMetaverse.StructuredData.OSDException": [("message", "String")],
"T:LibreMetaverse.StructuredData.OSDInteger": [("value", "i32")],
"T:LibreMetaverse.StructuredData.OSDMap": [("values", "std::collections::HashMap<String, OSD>")],
"T:LibreMetaverse.StructuredData.OSDReal": [("value", "f64")],
"T:LibreMetaverse.StructuredData.OSDString": [("value", "String")],
"T:LibreMetaverse.StructuredData.OSDUUID": [("value", "libremetaverse_types::UUID")],
"T:LibreMetaverse.StructuredData.OSDUri": [("value", "libremetaverse_types::compat::Uri")],
"T:LibreMetaverse.Imaging.ManagedImage": [("data", "Vec<u8>")],
}
VALUE_DERIVES = {
"T:LibreMetaverse.Matrix4": "Clone, Copy, Debug",
"T:LibreMetaverse.Quaternion": "Clone, Copy, Debug",
"T:LibreMetaverse.UUID": "Clone, Copy, Debug, Default, Eq, Hash, PartialEq",
"T:LibreMetaverse.Vector3": "Clone, Copy, Debug",
"T:LibreMetaverse.Vector3d": "Clone, Copy, Debug",
}
def generic_names(item: dict) -> list[str]:
return [parameter["name"] for parameter in item.get("generic_parameters", [])]
def format_rust(source: str) -> str:
result = subprocess.run(
["rustfmt", "--edition", "2024", "--emit", "stdout"],
input=source,
text=True,
capture_output=True,
check=False,
)
if result.returncode:
raise ValueError(result.stderr.strip())
return result.stdout
def type_name(type_row: dict) -> str:
return type_row["rust_path"].rsplit("::", 1)[-1]
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)
owner_bounds = NATIVE_OWNER_BOUNDS.get(item["doc_id"], {})
parameters = [
*(owner_bounds.get(name, name) for name in owner_names),
*generic_declarations(member, mapper, owner_names),
]
if member["doc_id"] == "M:LibreMetaverse.Utils.EnumTryParse``1(System.String,``0@)":
parameters = [
*owner_names,
"T: libremetaverse_types::EnumValueMetadata",
]
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", []))
def duplicate_enum_values(item: dict) -> bool:
values = [member["value"]["value"] for member in item["members"] if member["kind"] == "enum_value"]
return len(values) != len(set(values))
def render_enum(item: dict, rust_name: str) -> list[str]:
underlying = UNDERLYING[item["enum_underlying_type"]]
values = [member for member in item["members"] if member["kind"] == "enum_value"]
lines = [f"/// C# type: `{item['doc_id']}`."]
if item["doc_id"] in OPEN_ENUM_IDS:
lines += [
"#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]",
"#[repr(transparent)]",
f"pub struct {rust_name}(pub {underlying});",
"#[allow(non_upper_case_globals)]",
f"impl {rust_name} {{",
]
for member in values:
lines += [
f" /// C# member: `{member['doc_id']}`.",
f" pub const {mapping.pascal(member['name'])}: Self = Self({member['value']['value']});",
]
lines.append("}")
return lines
if flags_enum(item) or duplicate_enum_values(item):
lines += [
"#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]",
"#[repr(transparent)]",
f"pub struct {rust_name}(pub {underlying});",
f"impl {rust_name} {{",
]
for member in values:
name = mapping.snake(member["name"]).upper()
value = member["value"]["value"]
lines += [f" /// C# member: `{member['doc_id']}`.", f" pub const {name}: Self = Self({value});"]
lines.append("}")
return lines
lines += [
"#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]",
f"#[repr({underlying})]",
f"pub enum {rust_name} {{",
]
for member in values:
lines += [f" /// C# member: `{member['doc_id']}`.", f" {mapping.pascal(member['name'])} = {member['value']['value']},"]
lines.append("}")
return lines
def render_body(signature: str, member_id: str, error_model: str, asyncness: str, trait: bool) -> str:
if trait:
signature = signature.removeprefix("pub ")
if member_id in NATIVE_MEMBER_BODIES:
return f" {signature} {{ {NATIVE_MEMBER_BODIES[member_id]} }}"
failure = (
f"libremetaverse_types::not_implemented({json.dumps(member_id)})"
if error_model.startswith("Result")
else f"libremetaverse_types::unimplemented_api!({json.dumps(member_id)})"
)
if asyncness == "boxed_future" or "std::pin::Pin<Box<dyn std::future::Future" in signature:
failure = f"Box::pin(async {{ {failure} }})"
return f" {signature} {{ {failure} }}"
def rust_constant_value(item: dict) -> str:
value = item["value"]["value"]
if item["type"] == "System.String":
return json.dumps(value)
if item["type"] == "System.Boolean":
return value.lower()
if item["type"] in {"System.Single", "System.Double"} and not any(
marker in value.lower() for marker in (".", "e")
):
return value + ".0"
return value
def render_type(item: dict, type_row: dict, member_rows: dict[str, dict[str, str]], mapper: mapping.Mapper) -> str:
rust_name = type_name(type_row)
if native_path := NATIVE_TYPES.get(item["doc_id"]):
lines = [f"/// C# type: `{item['doc_id']}`."]
lines.extend(f"/// C# member: `{member['doc_id']}`." for member in item["members"])
lines.append(f"pub use {native_path} as {rust_name};")
return "\n".join(lines)
if item["kind"] == "enum":
return "\n".join(render_enum(item, rust_name))
names = generic_names(item)
suffix = generic_suffix(names)
trait = item["kind"] == "interface"
lines = [f"/// C# type: `{item['doc_id']}`."]
if trait:
supertrait = TRAIT_SUPERTRAITS.get(item["doc_id"])
inheritance = f": {supertrait}" if supertrait else ""
lines.append(f"pub trait {rust_name}{suffix}{inheritance} {{")
else:
fields = [member for member in item["members"] if member["kind"] == "field" and not member.get("static")]
private_fields = PRIVATE_LAYOUTS.get(item["doc_id"], [])
base_type = item.get("base_type") if item["doc_id"] in COMPOSED_BASE_TYPES else None
if derives := VALUE_DERIVES.get(item["doc_id"]):
lines.append(f"#[derive({derives})]")
if item["doc_id"] == "T:LibreMetaverse.StructuredData.OSD":
lines += [
"#[non_exhaustive]",
"pub enum OSD {",
" Undefined, Boolean(bool), Integer(i32), Real(f64), String(String),",
" UUID(libremetaverse_types::UUID), Date(std::time::SystemTime),",
" Uri(libremetaverse_types::compat::Uri), Binary(Vec<u8>),",
" Array(Vec<OSD>), Map(std::collections::HashMap<String, OSD>), LlsdXml(String),",
"}",
]
elif not fields and not names and not private_fields and not base_type:
lines.append(f"pub struct {rust_name};")
else:
lines.append(f"pub struct {rust_name}{suffix} {{")
if base_type:
lines += [
f" /// Rust composition for C# base type `{base_type}`.",
f" pub base: {mapper.type(base_type, None, set(names))},",
]
for field in fields:
field_signature = member_rows[field["doc_id"]]["rust_signature"]
field_name_match = re.fullmatch(r"pub ([^:]+): (.+)", field_signature)
if not field_name_match:
raise ValueError(f"invalid mapped field signature: {field_signature}")
field_name = field_name_match.group(1)
field_type = field_name_match.group(2)
lines += [f" /// C# member: `{field['doc_id']}`.", f" pub {field_name}: {field_type},"]
for field_name, field_type in private_fields:
lines.append(f" {field_name}: {field_type},")
if names:
tuple_type = "(" + ", ".join(names) + ("," if len(names) == 1 else "") + ")"
lines.append(f" _marker: std::marker::PhantomData<{tuple_type}>,")
lines.append("}")
lines += [f"impl{suffix} {rust_name}{suffix} {{"]
for member in item["members"]:
if member["kind"] in {"enum_value"} or (member["kind"] == "field" and not member.get("static")):
continue
row = member_rows[member["doc_id"]]
lines.append(f" /// C# member: `{member['doc_id']}`.")
if member["kind"] == "constant":
lines.append(f" {row['rust_signature']} = {rust_constant_value(member)};")
continue
signatures = row["rust_signature"].split(" ; ")
for index, signature in enumerate(signatures):
if index:
lines.append(f" /// Setter for C# member: `{member['doc_id']}`.")
lines.append(
render_body(
signature,
member["doc_id"],
row["error_model"],
row["asyncness"],
trait,
)
)
lines.append("}")
if not trait:
for interface in item.get("interfaces", []):
base, arguments, _ = mapping.type_parts(interface)
if base not in mapper.interfaces or not base.startswith("LibreMetaverse."):
continue
mapped_arguments = [mapper.type(argument, None, set(names)) for argument in arguments]
trait_path = mapper.resolved[base] + generic_suffix(mapped_arguments)
lines.append(f"impl{suffix} {trait_path} for {rust_name}{suffix} {{}}")
return "\n".join(lines)
def render_pending_type(item: dict, type_row: dict) -> str:
rust_name = type_name(type_row)
names = generic_names(item)
suffix = generic_suffix(names)
lines = [f"/// Pending catalog type `{item['doc_id']}`; completed by a later milestone issue."]
if item["kind"] == "interface":
lines.append(f"pub trait {rust_name}{suffix} {{}}")
elif names:
tuple_type = "(" + ", ".join(names) + ("," if len(names) == 1 else "") + ")"
lines += [f"pub struct {rust_name}{suffix} {{", f" _marker: std::marker::PhantomData<{tuple_type}>,", "}"]
else:
lines.append(f"pub struct {rust_name};")
return "\n".join(lines)
def wire_type(item: dict) -> bool:
return (
item["doc_id"] in WIRE_ROOT_TYPES
or item["doc_id"].startswith("T:LibreMetaverse.Primitive")
or item["namespace"].startswith(WIRE_NAMESPACES)
)
def core_type(item: dict) -> bool:
return item["doc_id"] in CORE_ROOT_TYPES or item["namespace"].startswith(CORE_NAMESPACES)
def avatar_type(item: dict) -> bool:
return (
item["doc_id"] in AVATAR_ROOT_TYPES
or item["doc_id"].startswith(AVATAR_TYPE_PREFIXES)
or item["namespace"].startswith(AVATAR_NAMESPACES)
)
def world_type(item: dict) -> bool:
return not wire_type(item) and not core_type(item) and not avatar_type(item)
def render_modules(items: list[tuple[dict, dict, bool]], member_rows: dict[str, dict[str, str]], mapper: mapping.Mapper) -> str:
tree: dict = {"items": [], "children": {}}
for item, type_row, complete in items:
node = tree
for segment in filter(None, type_row["rust_module"].split("::")):
node = node["children"].setdefault(segment, {"items": [], "children": {}})
node["items"].append((item, type_row, complete))
def render_node(node: dict) -> list[str]:
lines = []
for item, type_row, complete in node["items"]:
rendered = render_type(item, type_row, member_rows, mapper) if complete else render_pending_type(item, type_row)
lines += [rendered, ""]
for name, child in sorted(node["children"].items()):
body = render_node(child)
lines.append(f"pub mod {name} {{")
lines += [(" " + line) if line else "" for line in body]
lines += ["}", ""]
return lines
return "\n".join(render_node(tree))
def generate_sources(catalog: dict) -> tuple[dict[Path, str], dict[str, tuple[int, int, bool]]]:
type_rows, resolved = mapping.build_type_rows(catalog)
type_by_id = {row["csharp_type_id"]: row for row in type_rows}
member_by_id = {
member["doc_id"]: member
for assembly in catalog["assemblies"]
for item in assembly["types"]
for member in item["members"]
}
configured_main_ids = WIRE_ROOT_TYPES | CORE_ROOT_TYPES | AVATAR_ROOT_TYPES
missing_configured_ids = configured_main_ids - type_by_id.keys()
if missing_configured_ids:
raise ValueError(f"unknown configured main-slice IDs: {sorted(missing_configured_ids)}")
missing_boxed_fields = mapping.BOXED_FIELD_IDS - member_by_id.keys()
if missing_boxed_fields:
raise ValueError(f"unknown configured boxed fields: {sorted(missing_boxed_fields)}")
if invalid_boxed_fields := [
member_id
for member_id in mapping.BOXED_FIELD_IDS
if member_by_id[member_id]["kind"] != "field"
]:
raise ValueError(f"configured boxed members are not fields: {sorted(invalid_boxed_fields)}")
interfaces = {
*{mapping.type_parts(item["signature"])[0] for assembly in catalog["assemblies"] for item in assembly["types"] if item["kind"] == "interface"},
*{
mapping.type_parts(item["signature"])[0]
for item in catalog["external_types"]
if item["kind"] == "interface" and not item["assembly"]["name"].startswith("System")
},
}
mapper = mapping.Mapper(resolved, interfaces)
member_rows = {row["csharp_id"]: row for row in csv.DictReader(LEDGER.open(), delimiter="\t")}
outputs = {}
coverage = {}
for assembly in catalog["assemblies"]:
name = assembly["identity"]["name"]
if name not in TARGETS and name != "LibreMetaverse":
continue
chunks = [
"// @generated by tools/generate_api_shims.py; do not edit by hand.",
"#![allow(clippy::missing_errors_doc)]",
"#![allow(clippy::must_use_candidate)]",
"#![allow(clippy::all, clippy::pedantic, dead_code, unused_variables)]",
"#![allow(non_camel_case_types)]",
"#![allow(non_snake_case)]",
"",
]
selected = (
assembly["types"]
if name in TARGETS
else [
item
for item in assembly["types"]
if wire_type(item) or core_type(item) or avatar_type(item) or world_type(item)
]
)
selected_ids = {item["doc_id"] for item in selected}
rendered_items = [
(item, type_by_id[item["doc_id"]], item["doc_id"] in selected_ids)
for item in assembly["types"]
]
chunks.append(render_modules(rendered_items, member_rows, mapper))
source = format_rust("\n".join(chunks).rstrip() + "\n")
expected_types = selected_ids
expected_members = {member["doc_id"] for item in selected for member in item["members"]}
emitted_types = set(re.findall(r"C# type: `(.*)`\.", source))
emitted_members = set(re.findall(r"C# member: `(.*)`\.", source))
if emitted_types != expected_types or emitted_members != expected_members:
raise ValueError(
f"generated catalog ID mismatch for {name}: "
f"types missing/stale={len(expected_types - emitted_types)}/{len(emitted_types - expected_types)}, "
f"members missing/stale={len(expected_members - emitted_members)}/{len(emitted_members - expected_members)}"
)
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
def coverage_report(catalog: dict, coverage: dict[str, tuple[int, int, bool]]) -> str:
lines = [
"# Callable shim coverage", "", "Generated by `python3 tools/generate_api_shims.py`; do not edit by hand.", "",
"| Assembly | Types | Members | Status |", "|---|---:|---:|---|",
]
total_types = total_members = 0
for assembly in catalog["assemblies"]:
name = assembly["identity"]["name"]
types = len(assembly["types"])
members = sum(len(item["members"]) for item in assembly["types"])
if name in coverage:
got_types, got_members, complete = coverage[name]
if complete and (types, members) != (got_types, got_members):
raise ValueError(f"incomplete generated slice for {name}")
native_type_ids = {
item["doc_id"]
for item in assembly["types"]
if item["doc_id"] in NATIVE_TYPES
or (name == "LibreMetaverse.Types" and item["kind"] == "enum")
}
native_member_ids = {
member["doc_id"]
for item in assembly["types"]
for member in item["members"]
if item["doc_id"] in native_type_ids or member["doc_id"] in NATIVE_MEMBER_BODIES
}
if native_type_ids or native_member_ids:
if len(native_member_ids) == members:
status = (
f"native implementation: {len(native_type_ids):,} types / "
f"{len(native_member_ids):,} members; no generated shims remain"
)
else:
status = (
f"native implementation: {len(native_type_ids):,} types / "
f"{len(native_member_ids):,} members; remaining surface is callable failure-only shims"
)
else:
status = "callable failure-only shim" if complete else f"partial callable shim ({got_types:,} types / {got_members:,} members)"
total_types += got_types
total_members += got_members
else:
status = "pending milestone issue"
lines.append(f"| `{name}` | {types:,} | {members:,} | {status} |")
if "LibreMetaverse" in coverage:
main_types = next(
assembly["types"]
for assembly in catalog["assemblies"]
if assembly["identity"]["name"] == "LibreMetaverse"
)
wire_types = [item for item in main_types if wire_type(item)]
core_types = [item for item in main_types if core_type(item) and not wire_type(item)]
avatar_types = [
item
for item in main_types
if avatar_type(item) and not wire_type(item) and not core_type(item)
]
world_types = [item for item in main_types if world_type(item)]
complete = coverage["LibreMetaverse"][2]
lines += [
"",
(
"The `LibreMetaverse` assembly is complete across issue #7 wire/data, issue #8 core runtime/networking,"
if complete
else "The partial `LibreMetaverse` row contains the completed issue #7 wire/data, issue #8 core runtime/networking,"
),
(
"issue #9 avatar-facing manager, and issue #10 world/social/service manager slices."
if complete
else "and issue #9 avatar-facing manager slices. World/social/service manager types remain pending for issue #10."
),
"",
"| Main assembly slice | Types | Members |",
"|---|---:|---:|",
f"| Issue #7 wire/data | {len(wire_types):,} | {sum(len(item['members']) for item in wire_types):,} |",
f"| Issue #8 core runtime/networking | {len(core_types):,} | {sum(len(item['members']) for item in core_types):,} |",
f"| Issue #9 avatar-facing managers | {len(avatar_types):,} | {sum(len(item['members']) for item in avatar_types):,} |",
f"| Issue #10 world/social/service managers | {len(world_types):,} | {sum(len(item['members']) for item in world_types):,} |",
]
lines += ["", f"Current callable coverage: **{total_types:,} types / {total_members:,} members**.", ""]
return "\n".join(lines)
def generate() -> dict[Path, str]:
catalog = json.loads(CATALOG.read_text())
sources, coverage = generate_sources(catalog)
sources[COVERAGE] = coverage_report(catalog, coverage)
return sources
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--check", action="store_true")
args = parser.parse_args()
outputs = generate()
if args.check:
stale = [str(path.relative_to(ROOT)) for path, text in outputs.items() if not path.exists() or path.read_text() != text]
if stale:
raise SystemExit("stale generated API shims: " + ", ".join(stale))
else:
for path, text in outputs.items():
path.write_text(text)
print("generated " + ", ".join(str(path.relative_to(ROOT)) for path in outputs))
if __name__ == "__main__":
main()