# gir.py # # Copyright 2021 James Westman # # This file is free software; you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 3 of the # License, or (at your option) any later version. # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this program. If not, see . # # SPDX-License-Identifier: LGPL-3.0-or-later import os import sys import typing as T from functools import cached_property import gi # type: ignore try: gi.require_version("GIRepository", "3.0") from gi.repository import GIRepository # type: ignore _repo = GIRepository.Repository() except ValueError: # We can remove this once we can bump the minimum dependencies # to glib 2.80 and pygobject 3.52 # dependency('glib-2.0', version: '>= 2.80.0') # dependency('girepository-2.0', version: '>= 2.80.0') gi.require_version("GIRepository", "2.0") from gi.repository import GIRepository # type: ignore _repo = GIRepository.Repository from . import typelib, xml_reader from .errors import CompileError, CompilerBugError from .lsp_utils import CodeAction _namespace_cache: T.Dict[str, "Namespace"] = {} _xml_cache = {} _user_search_paths = [] def add_typelib_search_path(path: str): _user_search_paths.append(path) def get_namespace(namespace: str, version: str) -> "Namespace": search_paths = [*_repo.get_search_path(), *_user_search_paths] filename = f"{namespace}-{version}.typelib" if filename not in _namespace_cache: for search_path in search_paths: path = os.path.join(search_path, filename) if os.path.exists(path) and os.path.isfile(path): tl = typelib.load_typelib(path) repository = Repository(tl) _namespace_cache[filename] = repository.namespace break if filename not in _namespace_cache: raise CompileError( f"Namespace {namespace}-{version} could not be found", hints=["search path: " + os.pathsep.join(search_paths)], ) return _namespace_cache[filename] _available_namespaces: list[tuple[str, str]] = [] def get_available_namespaces() -> T.List[T.Tuple[str, str]]: if len(_available_namespaces): return _available_namespaces search_paths: list[str] = [ *_repo.get_search_path(), *_user_search_paths, ] for search_path in search_paths: try: filenames = os.listdir(search_path) except FileNotFoundError: continue for filename in filenames: if filename.endswith(".typelib"): namespace, version = filename.removesuffix(".typelib").rsplit("-", 1) _available_namespaces.append((namespace, version)) return _available_namespaces def get_xml(namespace: str, version: str): search_paths = [] if data_paths := os.environ.get("XDG_DATA_DIRS"): search_paths += [ os.path.join(path, "gir-1.0") for path in data_paths.split(os.pathsep) ] filename = f"{namespace}-{version}.gir" if filename not in _xml_cache: for search_path in search_paths: path = os.path.join(search_path, filename) if os.path.exists(path) and os.path.isfile(path): _xml_cache[filename] = xml_reader.parse(path) break if filename not in _xml_cache: raise CompileError( f"GObject introspection file '{namespace}-{version}.gir' could not be found", hints=["search path: " + os.pathsep.join(search_paths)], ) return _xml_cache[filename] ONLINE_DOCS = { "Adw-1": "https://gnome.pages.gitlab.gnome.org/libadwaita/doc/1-latest/", "Gdk-4.0": "https://docs.gtk.org/gdk4/", "GdkPixbuf-2.0": "https://docs.gtk.org/gdk-pixbuf/", "Gio-2.0": "https://docs.gtk.org/gio/", "GLib-2.0": "https://docs.gtk.org/glib/", "GModule-2.0": "https://docs.gtk.org/gmodule/", "GObject-2.0": "https://docs.gtk.org/gobject/", "Gsk-4.0": "https://docs.gtk.org/gsk4/", "Gtk-4.0": "https://docs.gtk.org/gtk4/", "GtkSource-5": "https://gnome.pages.gitlab.gnome.org/gtksourceview/gtksourceview5", "Pango-1.0": "https://docs.gtk.org/Pango/", "Shumate-1.0": "https://gnome.pages.gitlab.gnome.org/libshumate/", "WebKit2-4.1": "https://webkitgtk.org/reference/webkit2gtk/stable/", } class GirType: @property def doc(self) -> T.Optional[str]: return None def assignable_to(self, other: "GirType") -> bool: raise NotImplementedError() @property def name(self) -> str: """The GIR name of the type, not including the namespace""" raise NotImplementedError() @property def full_name(self) -> str: """The GIR name of the type to use in diagnostics""" raise NotImplementedError() @property def glib_type_name(self) -> str: """The name of the type in the GObject type system, suitable to pass to `g_type_from_name()`.""" raise NotImplementedError() @property def incomplete(self) -> bool: return False @property def deprecated(self) -> bool: return False @property def deprecated_doc(self) -> T.Optional[str]: return None class ExternType(GirType): def __init__(self, name: str) -> None: super().__init__() self._name = name def assignable_to(self, other: GirType) -> bool: return True @property def full_name(self) -> str: return self._name @property def glib_type_name(self) -> str: return self._name @property def incomplete(self) -> bool: return True class ArrayType(GirType): def __init__(self, inner: GirType) -> None: self._inner = inner def assignable_to(self, other: GirType) -> bool: return isinstance(other, ArrayType) and self._inner.assignable_to(other._inner) @property def inner(self) -> GirType: return self._inner @property def name(self) -> str: return self._inner.name + "[]" @property def full_name(self) -> str: return self._inner.full_name + "[]" class BasicType(GirType): name: str = "unknown type" @property def full_name(self) -> str: return self.name class BoolType(BasicType): name = "bool" glib_type_name: str = "gboolean" def assignable_to(self, other: GirType) -> bool: return isinstance(other, BoolType) class IntType(BasicType): name = "int" glib_type_name: str = "gint" def assignable_to(self, other: GirType) -> bool: return ( isinstance(other, IntType) or isinstance(other, UIntType) or isinstance(other, FloatType) ) class UIntType(BasicType): name = "uint" glib_type_name: str = "guint" def assignable_to(self, other: GirType) -> bool: return ( isinstance(other, IntType) or isinstance(other, UIntType) or isinstance(other, FloatType) ) class FloatType(BasicType): name = "float" glib_type_name: str = "gfloat" def assignable_to(self, other: GirType) -> bool: return isinstance(other, FloatType) class StringType(BasicType): name = "string" glib_type_name: str = "gchararray" def assignable_to(self, other: GirType) -> bool: return isinstance(other, StringType) class TypeType(BasicType): name = "GType" glib_type_name: str = "GType" def assignable_to(self, other: GirType) -> bool: return isinstance(other, TypeType) _BASIC_TYPES = { "bool": BoolType, "string": StringType, "int": IntType, "uint": UIntType, "float": FloatType, "double": FloatType, "type": TypeType, } TNode = T.TypeVar("TNode", bound="GirNode") class GirNode: xml_tag: str def __init__(self, container: T.Optional["GirNode"], tl: typelib.Typelib) -> None: self.container = container self.tl = tl def get_containing(self, container_type: T.Type[TNode]) -> TNode: if self.container is None: raise CompilerBugError() elif isinstance(self.container, container_type): return self.container else: return self.container.get_containing(container_type) @cached_property def xml(self): for el in self.container.xml.children: if el.attrs.get("name") == self.name: if el.tag == self.xml_tag: return el @cached_property def glib_type_name(self) -> str: return self.tl.OBJ_GTYPE_NAME @cached_property def full_name(self) -> str: if self.container is None: return self.name else: return f"{self.container.name}.{self.name}" @cached_property def name(self) -> str: return self.tl.BLOB_NAME @cached_property def cname(self) -> str: return self.tl.OBJ_GTYPE_NAME @cached_property def available_in(self) -> str: return self.xml.get("version") @cached_property def detail(self) -> T.Optional[str]: try: el = self.xml.get_elements("doc") if len(el) == 1: return el[0].cdata.strip().partition("\n")[0] else: return None except: return None @cached_property def doc(self) -> T.Optional[str]: sections = [] if self.signature: sections.append("```\n" + self.signature + "\n```") try: el = self.xml.get_elements("doc") if len(el) == 1: sections.append(el[0].cdata.strip()) except: # Not a huge deal, but if you want docs in the language server you # should ensure .gir files are installed sections.append("Documentation is not installed") if self.online_docs: sections.append(f"[Online documentation]({self.online_docs})") return "\n\n---\n\n".join(sections) @property def online_docs(self) -> T.Optional[str]: return None @property def signature(self) -> T.Optional[str]: return None @property def type(self) -> GirType: raise NotImplementedError() @property def deprecated_doc(self) -> T.Optional[str]: try: return self.xml.get_elements("doc-deprecated")[0].cdata.strip() except: return None class Property(GirNode): xml_tag = "property" def __init__(self, klass: T.Union["Class", "Interface"], tl: typelib.Typelib): super().__init__(klass, tl) @cached_property def name(self) -> str: return self.tl.PROP_NAME @cached_property def type(self): return self.get_containing(Repository)._resolve_type_id(self.tl.PROP_TYPE) @cached_property def signature(self): return f"{self.type.full_name} {self.container.name}:{self.name}" @property def writable(self) -> bool: return self.tl.PROP_WRITABLE == 1 @property def construct_only(self) -> bool: return self.tl.PROP_CONSTRUCT_ONLY == 1 @property def online_docs(self) -> T.Optional[str]: if ns := self.get_containing(Namespace).online_docs: assert self.container is not None return f"{ns}property.{self.container.name}.{self.name}.html" else: return None @property def deprecated(self) -> bool: return self.tl.PROP_DEPRECATED == 1 class Argument(GirNode): def __init__(self, container: GirNode, tl: typelib.Typelib) -> None: super().__init__(container, tl) @cached_property def name(self) -> str: return self.tl.ARG_NAME @cached_property def type(self) -> GirType: return self.get_containing(Repository)._resolve_type_id(self.tl.ARG_TYPE) class Signature(GirNode): def __init__(self, container: GirNode, tl: typelib.Typelib) -> None: super().__init__(container, tl) @cached_property def args(self) -> T.List[Argument]: n_arguments = self.tl.SIGNATURE_N_ARGUMENTS blob_size = self.tl.header.HEADER_ARG_BLOB_SIZE result = [] for i in range(n_arguments): entry = self.tl.SIGNATURE_ARGUMENTS[i * blob_size] result.append(Argument(self, entry)) return result @cached_property def return_type(self) -> T.Optional[GirType]: if self.tl.SIGNATURE_RETURN_TYPE == 0: return None else: return self.get_containing(Repository)._resolve_type_id( self.tl.SIGNATURE_RETURN_TYPE ) class Signal(GirNode): xml_tag = "glib:signal" def __init__( self, klass: T.Union["Class", "Interface"], tl: typelib.Typelib ) -> None: super().__init__(klass, tl) @cached_property def gir_signature(self) -> Signature: return Signature(self, self.tl.SIGNAL_SIGNATURE) @property def signature(self): args = ", ".join( [f"{a.type.full_name} {a.name}" for a in self.gir_signature.args] ) result = f"signal {self.container.full_name}::{self.name} ({args})" if self.gir_signature.return_type is not None: result += f" -> {self.gir_signature.return_type.full_name}" return result @property def online_docs(self) -> T.Optional[str]: if ns := self.get_containing(Namespace).online_docs: assert self.container is not None return f"{ns}signal.{self.container.name}.{self.name}.html" else: return None @property def deprecated(self) -> bool: return self.tl.SIGNAL_DEPRECATED == 1 class Interface(GirNode, GirType): xml_tag = "interface" def __init__(self, ns: "Namespace", tl: typelib.Typelib): super().__init__(ns, tl) @cached_property def properties(self) -> T.Mapping[str, Property]: n_prerequisites = self.tl.INTERFACE_N_PREREQUISITES offset = self.tl.header.HEADER_INTERFACE_BLOB_SIZE offset += (n_prerequisites + n_prerequisites % 2) * 2 n_properties = self.tl.INTERFACE_N_PROPERTIES property_size = self.tl.header.HEADER_PROPERTY_BLOB_SIZE result = {} for i in range(n_properties): property = Property(self, self.tl[offset + i * property_size]) result[property.name] = property return result @cached_property def signals(self) -> T.Mapping[str, Signal]: n_prerequisites = self.tl.INTERFACE_N_PREREQUISITES offset = self.tl.header.HEADER_INTERFACE_BLOB_SIZE offset += (n_prerequisites + n_prerequisites % 2) * 2 offset += ( self.tl.INTERFACE_N_PROPERTIES * self.tl.header.HEADER_PROPERTY_BLOB_SIZE ) offset += self.tl.INTERFACE_N_METHODS * self.tl.header.HEADER_FUNCTION_BLOB_SIZE n_signals = self.tl.INTERFACE_N_SIGNALS property_size = self.tl.header.HEADER_SIGNAL_BLOB_SIZE result = {} for i in range(n_signals): signal = Signal(self, self.tl[offset + i * property_size]) result[signal.name] = signal return result @cached_property def prerequisites(self) -> T.List["Interface"]: n_prerequisites = self.tl.INTERFACE_N_PREREQUISITES result = [] for i in range(n_prerequisites): entry = self.tl.INTERFACE_PREREQUISITES[i * 2].AS_DIR_ENTRY result.append(self.get_containing(Repository)._resolve_dir_entry(entry)) return result def assignable_to(self, other: GirType) -> bool: if self == other: return True for pre in self.prerequisites: if pre.assignable_to(other): return True return False @property def online_docs(self) -> T.Optional[str]: if ns := self.get_containing(Namespace).online_docs: return f"{ns}iface.{self.name}.html" else: return None @property def deprecated(self) -> bool: return self.tl.INTERFACE_DEPRECATED == 1 class Class(GirNode, GirType): xml_tag = "class" def __init__(self, ns: "Namespace", tl: typelib.Typelib) -> None: super().__init__(ns, tl) @property def abstract(self) -> bool: return self.tl.OBJ_ABSTRACT == 1 @cached_property def implements(self) -> T.List[Interface]: n_interfaces = self.tl.OBJ_N_INTERFACES result = [] for i in range(n_interfaces): entry = self.tl[self.tl.header.HEADER_OBJECT_BLOB_SIZE + i * 2].AS_DIR_ENTRY result.append(self.get_containing(Repository)._resolve_dir_entry(entry)) return result @cached_property def own_properties(self) -> T.Mapping[str, Property]: n_interfaces = self.tl.OBJ_N_INTERFACES offset = self.tl.header.HEADER_OBJECT_BLOB_SIZE offset += (n_interfaces + n_interfaces % 2) * 2 offset += self.tl.OBJ_N_FIELDS * self.tl.header.HEADER_FIELD_BLOB_SIZE offset += ( self.tl.OBJ_N_FIELD_CALLBACKS * self.tl.header.HEADER_CALLBACK_BLOB_SIZE ) n_properties = self.tl.OBJ_N_PROPERTIES property_size = self.tl.header.HEADER_PROPERTY_BLOB_SIZE result = {} for i in range(n_properties): property = Property(self, self.tl[offset + i * property_size]) result[property.name] = property return result @cached_property def own_signals(self) -> T.Mapping[str, Signal]: n_interfaces = self.tl.OBJ_N_INTERFACES offset = self.tl.header.HEADER_OBJECT_BLOB_SIZE offset += (n_interfaces + n_interfaces % 2) * 2 offset += self.tl.OBJ_N_FIELDS * self.tl.header.HEADER_FIELD_BLOB_SIZE offset += ( self.tl.OBJ_N_FIELD_CALLBACKS * self.tl.header.HEADER_CALLBACK_BLOB_SIZE ) offset += self.tl.OBJ_N_PROPERTIES * self.tl.header.HEADER_PROPERTY_BLOB_SIZE offset += self.tl.OBJ_N_METHODS * self.tl.header.HEADER_FUNCTION_BLOB_SIZE n_signals = self.tl.OBJ_N_SIGNALS signal_size = self.tl.header.HEADER_SIGNAL_BLOB_SIZE result = {} for i in range(n_signals): signal = Signal(self, self.tl[offset][i * signal_size]) result[signal.name] = signal return result @cached_property def parent(self) -> T.Optional["Class"]: if entry := self.tl.OBJ_PARENT: return self.get_containing(Repository)._resolve_dir_entry(entry) else: return None @cached_property def signature(self) -> str: assert self.container is not None result = f"class {self.container.name}.{self.name}" if self.parent is not None: assert self.parent.container is not None result += f" : {self.parent.container.name}.{self.parent.name}" if len(self.implements): result += " implements " + ", ".join( [impl.full_name for impl in self.implements] ) return result @cached_property def properties(self) -> T.Mapping[str, Property]: return {p.name: p for p in self._enum_properties()} @cached_property def signals(self) -> T.Mapping[str, Signal]: return {s.name: s for s in self._enum_signals()} def assignable_to(self, other: GirType) -> bool: if self == other: return True elif self.parent and self.parent.assignable_to(other): return True else: for iface in self.implements: if iface.assignable_to(other): return True return False def _enum_properties(self) -> T.Iterable[Property]: yield from self.own_properties.values() if self.parent is not None: yield from self.parent.properties.values() for impl in self.implements: yield from impl.properties.values() def _enum_signals(self) -> T.Iterable[Signal]: yield from self.own_signals.values() if self.parent is not None: yield from self.parent.signals.values() for impl in self.implements: yield from impl.signals.values() @property def online_docs(self) -> T.Optional[str]: if ns := self.get_containing(Namespace).online_docs: return f"{ns}class.{self.name}.html" else: return None @property def deprecated(self) -> bool: return self.tl.OBJ_DEPRECATED == 1 class TemplateType(GirType): def __init__(self, name: str, parent: T.Optional[GirType]): self._name = name self.parent = parent @property def name(self) -> str: return self._name @property def full_name(self) -> str: return self._name @property def glib_type_name(self) -> str: return self._name @cached_property def properties(self) -> T.Mapping[str, Property]: if not (isinstance(self.parent, Class) or isinstance(self.parent, Interface)): return {} else: return self.parent.properties @cached_property def signals(self) -> T.Mapping[str, Signal]: if not (isinstance(self.parent, Class) or isinstance(self.parent, Interface)): return {} else: return self.parent.signals def assignable_to(self, other: "GirType") -> bool: if self == other: return True elif isinstance(other, Interface): # we don't know the template type's interfaces, assume yes return True elif self.parent is None or isinstance(self.parent, ExternType): return isinstance(other, Class) or isinstance(other, ExternType) else: return self.parent.assignable_to(other) @cached_property def signature(self) -> str: if self.parent is None: return f"template {self.name}" else: return f"template {self.name} : {self.parent.full_name}" @property def incomplete(self) -> bool: return True class EnumMember(GirNode): xml_tag = "member" def __init__(self, enum: "Enumeration", tl: typelib.Typelib) -> None: super().__init__(enum, tl) @property def value(self) -> int: return self.tl.VALUE_VALUE @cached_property def name(self) -> str: return self.tl.VALUE_NAME @cached_property def nick(self) -> str: return self.name.replace("_", "-") @property def c_ident(self) -> str: return self.tl.attr("c:identifier") @property def signature(self) -> str: return f"enum member {self.full_name} = {self.value}" class Enumeration(GirNode, GirType): xml_tag = "enumeration" def __init__(self, ns: "Namespace", tl: typelib.Typelib) -> None: super().__init__(ns, tl) @cached_property def members(self) -> T.Dict[str, EnumMember]: members = {} n_values = self.tl.ENUM_N_VALUES values = self.tl.ENUM_VALUES value_size = self.tl.header.HEADER_VALUE_BLOB_SIZE for i in range(n_values): member = EnumMember(self, values[i * value_size]) members[member.name] = member return members @property def signature(self) -> str: return f"enum {self.full_name}" def assignable_to(self, type: GirType) -> bool: return type == self @property def online_docs(self) -> T.Optional[str]: if ns := self.get_containing(Namespace).online_docs: return f"{ns}enum.{self.name}.html" else: return None @property def deprecated(self) -> bool: return self.tl.ENUM_DEPRECATED == 1 class Boxed(GirNode, GirType): xml_tag = "glib:boxed" def __init__(self, ns: "Namespace", tl: typelib.Typelib) -> None: super().__init__(ns, tl) @property def signature(self) -> str: return f"boxed {self.full_name}" def assignable_to(self, type) -> bool: return type == self @property def online_docs(self) -> T.Optional[str]: if ns := self.get_containing(Namespace).online_docs: return f"{ns}boxed.{self.name}.html" else: return None @property def deprecated(self) -> bool: return self.tl.STRUCT_DEPRECATED == 1 class Bitfield(Enumeration): xml_tag = "bitfield" def __init__(self, ns: "Namespace", tl: typelib.Typelib) -> None: super().__init__(ns, tl) class Namespace(GirNode): def __init__(self, repo: "Repository", tl: typelib.Typelib) -> None: super().__init__(repo, tl) @cached_property def entries(self) -> T.Mapping[str, GirType]: entries: dict[str, GirType] = {} n_local_entries: int = self.tl.HEADER_N_ENTRIES directory: typelib.Typelib = self.tl.HEADER_DIRECTORY blob_size: int = self.tl.header.HEADER_ENTRY_BLOB_SIZE for i in range(n_local_entries): entry = directory[i * blob_size] entry_name: str = entry.DIR_ENTRY_NAME entry_type: int = entry.DIR_ENTRY_BLOB_TYPE entry_blob: typelib.Typelib = entry.DIR_ENTRY_OFFSET if entry_type == typelib.BLOB_TYPE_ENUM: entries[entry_name] = Enumeration(self, entry_blob) elif entry_type == typelib.BLOB_TYPE_FLAGS: entries[entry_name] = Bitfield(self, entry_blob) elif entry_type == typelib.BLOB_TYPE_OBJECT: entries[entry_name] = Class(self, entry_blob) elif entry_type == typelib.BLOB_TYPE_INTERFACE: entries[entry_name] = Interface(self, entry_blob) elif ( entry_type == typelib.BLOB_TYPE_BOXED or entry_type == typelib.BLOB_TYPE_STRUCT ): entries[entry_name] = Boxed(self, entry_blob) return entries @cached_property def xml(self): return get_xml(self.name, self.version).get_elements("namespace")[0] @cached_property def name(self) -> str: return self.tl.HEADER_NAMESPACE @cached_property def version(self) -> str: return self.tl.HEADER_NSVERSION @property def signature(self) -> str: return f"namespace {self.name} {self.version}" @cached_property def classes(self) -> T.Mapping[str, Class]: return { name: entry for name, entry in self.entries.items() if isinstance(entry, Class) } def get_type(self, name) -> T.Optional[GirType]: """Gets a type (class, interface, enum, etc.) from this namespace.""" return self.entries.get(name) def get_type_by_cname(self, cname: str) -> T.Optional[GirType]: """Gets a type from this namespace by its C name.""" for basic in _BASIC_TYPES.values(): if basic.glib_type_name == cname: return basic() for item in self.entries.values(): if ( hasattr(item, "cname") and item.cname is not None and item.cname == cname ): return item return None def lookup_type(self, type_name: str) -> T.Optional[GirType]: """Looks up a type in the scope of this namespace (including in the namespace's dependencies).""" ns, name = type_name.split(".", 1) return self.get_containing(Repository).get_type(name, ns) @property def online_docs(self) -> T.Optional[str]: return ONLINE_DOCS.get(f"{self.name}-{self.version}") class Repository(GirNode): def __init__(self, tl: typelib.Typelib) -> None: super().__init__(None, tl) self.namespace = Namespace(self, tl) if dependencies := tl[0x24].string: deps = [tuple(dep.split("-", 1)) for dep in dependencies.split("|")] try: self.includes = { name: get_namespace(name, version) for name, version in deps } except: # pragma: no cover raise CompilerBugError(f"Failed to load dependencies.") else: self.includes = {} def get_type(self, name: str, ns: str) -> T.Optional[GirType]: return self.lookup_namespace(ns).get_type(name) def lookup_namespace(self, ns: str): """Finds a namespace among this namespace's dependencies.""" if ns == self.namespace.name: return self.namespace else: for include in self.includes.values(): if namespace := include.get_containing(Repository).lookup_namespace(ns): return namespace def _resolve_dir_entry(self, dir_entry: typelib.Typelib): if dir_entry.DIR_ENTRY_LOCAL: return self.namespace.get_type(dir_entry.DIR_ENTRY_NAME) else: ns = dir_entry.DIR_ENTRY_NAMESPACE return self.lookup_namespace(ns).get_type(dir_entry.DIR_ENTRY_NAME) def _resolve_type_id(self, type_id: int) -> GirType: if type_id & (0xFFFFFF if sys.byteorder == "little" else 0xFFFFFF00) == 0: type_id = ((type_id >> 27) if sys.byteorder == "little" else type_id) & 0x1F # simple type if type_id == typelib.TYPE_BOOLEAN: return BoolType() elif type_id in [typelib.TYPE_FLOAT, typelib.TYPE_DOUBLE]: return FloatType() elif type_id in [ typelib.TYPE_INT8, typelib.TYPE_INT16, typelib.TYPE_INT32, typelib.TYPE_INT64, ]: return IntType() elif type_id in [ typelib.TYPE_UINT8, typelib.TYPE_UINT16, typelib.TYPE_UINT32, typelib.TYPE_UINT64, ]: return UIntType() elif type_id == typelib.TYPE_UTF8: return StringType() elif type_id == typelib.TYPE_GTYPE: return TypeType() else: raise CompilerBugError("Unknown type ID", type_id) else: blob = self.tl.header[type_id] if blob.TYPE_BLOB_TAG == typelib.TYPE_INTERFACE: return self._resolve_dir_entry( self.tl.header[type_id].TYPE_BLOB_INTERFACE ) elif blob.TYPE_BLOB_TAG == typelib.TYPE_ARRAY: return ArrayType(self._resolve_type_id(blob.TYPE_BLOB_ARRAY_INNER)) else: raise CompilerBugError(f"{blob.TYPE_BLOB_TAG}") class GirContext: def __init__(self): self.namespaces = {} self.not_found_namespaces: T.Set[str] = set() def add_namespace(self, namespace: Namespace): other = self.namespaces.get(namespace.name) if other is not None and other.version != namespace.version: raise CompileError( f"Namespace {namespace.name}-{namespace.version} can't be imported because version {other.version} was imported earlier" ) self.namespaces[namespace.name] = namespace def get_type_by_cname(self, name: str) -> T.Optional[GirType]: for ns in self.namespaces.values(): if type := ns.get_type_by_cname(name): return type return None def get_type(self, name: str, ns: str) -> T.Optional[GirType]: if ns is None and name in _BASIC_TYPES: return _BASIC_TYPES[name]() ns = ns or "Gtk" if ns not in self.namespaces: return None return self.namespaces[ns].get_type(name) def get_class(self, name: str, ns: str) -> T.Optional[Class]: type = self.get_type(name, ns) if isinstance(type, Class): return type else: return None def validate_ns(self, ns: str) -> None: """Raises an exception if there is a problem looking up the given namespace.""" ns = ns or "Gtk" if ns not in self.namespaces and ns not in self.not_found_namespaces: all_available = list(set(ns for ns, _version in get_available_namespaces())) raise CompileError( f"Namespace {ns} was not imported", did_you_mean=(ns, all_available), ) def validate_type(self, name: str, ns: str) -> None: """Raises an exception if there is a problem looking up the given type.""" self.validate_ns(ns) type = self.get_type(name, ns) ns = ns or "Gtk" if type is None: raise CompileError( f"Namespace {ns} does not contain a type called {name}", did_you_mean=(name, self.namespaces[ns].classes.keys()), )