pax_global_header00006660000000000000000000000064147543415210014520gustar00rootroot0000000000000052 comment=2b05eaddce95ba2ef7cf123e2aa95f8c7feafd77 gir-0.20.5/000077500000000000000000000000001475434152100123655ustar00rootroot00000000000000gir-0.20.5/.gitattributes000066400000000000000000000000351475434152100152560ustar00rootroot00000000000000Cargo.* eol=lf *.sh eol=lf gir-0.20.5/.github/000077500000000000000000000000001475434152100137255ustar00rootroot00000000000000gir-0.20.5/.github/FUNDING.yml000066400000000000000000000000301475434152100155330ustar00rootroot00000000000000open_collective: gtk-rs gir-0.20.5/.github/dependabot.yml000066400000000000000000000005301475434152100165530ustar00rootroot00000000000000# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: "github-actions" directory: "/" # Location of package manifests schedule: interval: "weekly" - package-ecosystem: "cargo" directory: "/" schedule: interval: "weekly" gir-0.20.5/.github/workflows/000077500000000000000000000000001475434152100157625ustar00rootroot00000000000000gir-0.20.5/.github/workflows/book.yml000066400000000000000000000020121475434152100174320ustar00rootroot00000000000000name: book on: pull_request: push: branches: - "main" workflow_dispatch: jobs: build-deploy: runs-on: ubuntu-latest name: build steps: - uses: actions/checkout@v4 - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable override: true - name: Setup mdBook uses: peaceiris/actions-mdbook@v2 - run: mdbook build -d public working-directory: book - name: Deploy uses: peaceiris/actions-gh-pages@v4 if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./book/public keep_files: false destination_dir: book codespell: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: codespell-project/actions-codespell@master with: check_filenames: true path: book ignore_words_list: "crate,gir" gir-0.20.5/.github/workflows/docs.yml000066400000000000000000000013241475434152100174350ustar00rootroot00000000000000name: docs on: pull_request: push: branches: - "main" workflow_dispatch: jobs: build-deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable override: true - uses: actions-rs/cargo@v1 with: command: doc args: --no-deps - name: Deploy uses: peaceiris/actions-gh-pages@v4 if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./target/doc keep_files: false destination_dir: docs gir-0.20.5/.github/workflows/main.yaml000066400000000000000000000124041475434152100175730ustar00rootroot00000000000000name: "gir" on: pull_request: branches: - "*" push: branches: - "main" jobs: hygiene: name: Hygiene runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-latest] toolchain: [stable] defaults: run: shell: bash steps: - name: Acquire source code uses: actions/checkout@v4 - name: Acquire Rust toolchain uses: actions-rs/toolchain@v1 with: toolchain: ${{ matrix.toolchain }} override: true profile: minimal components: rustfmt, clippy id: toolchain - name: Cache cargo registry uses: actions/cache@v4 with: path: ~/.cargo/registry key: checks-${{ runner.os }}-cargo-registry-trimmed - name: Cache cargo git trees uses: actions/cache@v4 with: path: ~/.cargo/git key: checks-${{ runner.os }}-cargo-gits-trimmed - name: Cache cargo build uses: actions/cache@v4 with: path: target key: checks-${{ runner.os }}-cargo-target-dir-${{ steps.toolchain.outputs.rustc_hash }} - name: "Run clippy" uses: actions-rs/cargo@v1 with: command: clippy args: --release --tests -- -D warnings if: matrix.os == 'ubuntu-latest' - name: "Run formatting check" uses: actions-rs/cargo@v1 with: command: fmt args: -- --check if: matrix.os == 'ubuntu-latest' build: name: "Build/Test" runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-latest] toolchain: [nightly, stable] # include: # - os: windows-latest # toolchain: stable-i686-pc-windows-gnu # bits: 32 # arch: i686 # - os: windows-latest # toolchain: stable-x86_64-pc-windows-gnu # bits: 64 # arch: x86_64 defaults: run: shell: bash steps: - name: Acquire source code uses: actions/checkout@v4 - name: Acquire Rust toolchain uses: actions-rs/toolchain@v1 with: toolchain: ${{ matrix.toolchain }} override: true profile: minimal id: toolchain - name: Install MSYS2 uses: numworks/setup-msys2@v1 if: matrix.os == 'windows-latest' - name: Set up MSYS2 path and other windows env run: | echo "::add-path::$RUNNER_TEMP\\msys\\msys64\\usr\\bin" echo "::add-path::$RUNNER_TEMP\\msys\\msys64\\mingw${{ matrix.bits }}\\bin" echo "::set-env name=LIBGIT2_SYS_USE_PKG_CONFIG::1" if: matrix.os == 'windows-latest' - name: Cache cargo registry uses: actions/cache@v4 with: path: ~/.cargo/registry key: build-${{ runner.os }}-cargo-registry-trimmed - name: Cache cargo git trees uses: actions/cache@v4 with: path: ~/.cargo/git key: build-${{ runner.os }}-cargo-gits-trimmed - name: Cache cargo build uses: actions/cache@v4 with: path: target key: build-${{ runner.os }}-cargo-target-dir-${{ steps.toolchain.outputs.rustc_hash }} - name: Install packages from apt run: | sudo apt update sudo apt install libgtk-4-dev libssh2-1-dev libglib2.0-dev libgraphene-1.0-dev libcairo-gobject2 libcairo2-dev if: matrix.os == 'ubuntu-latest' - name: Install toolchain packages with pacman run: pacman --noconfirm -S base-devel mingw-w64-${{ matrix.arch }}-toolchain if: matrix.os == 'windows-latest' - name: Install library devel packages with pacman run: pacman --noconfirm -S mingw-w64-${{ matrix.arch }}-libgit2 if: matrix.os == 'windows-latest' - name: "Acquire gir-files" uses: actions/checkout@v4 with: repository: gtk-rs/gir-files ref: main path: tests/gir-files - name: "Build (release)" uses: actions-rs/cargo@v1 with: command: build args: --release - name: "Test (release)" uses: actions-rs/cargo@v1 with: command: test args: --release - name: "Acquire gtk4-rs" uses: actions/checkout@v4 with: repository: gtk-rs/gtk4-rs ref: main path: gtk4-rs if: matrix.os == 'ubuntu-latest' - name: "Symlink `gir` in gtk4-rs" run: | rmdir gtk4-rs/gir ln -sf .. gtk4-rs/gir - name: "Attempt to rebuild gtk4-rs gir" run: cd gtk4-rs && python3 generator.py --no-fmt --gir-path ../target/release/gir --gir-files-directories ../tests/gir-files/ && rm ../Cargo.* && cargo build if: matrix.os == 'ubuntu-latest' - name: "Acquire gtk-rs-core" uses: actions/checkout@v4 with: repository: gtk-rs/gtk-rs-core ref: main path: gtk-rs-core if: matrix.os == 'ubuntu-latest' - name: "Symlink `gir` in gtk-rs-core" run: | rmdir gtk-rs-core/gir ln -sf .. gtk-rs-core/gir - name: "Rebuild gio" run: cd gtk-rs-core/gio && cargo test if: matrix.os == 'ubuntu-latest' gir-0.20.5/.github/workflows/typos.yaml000066400000000000000000000004451475434152100200270ustar00rootroot00000000000000name: CI on: pull_request: push: branches: - "main" jobs: typos: name: Spell Check with Typos runs-on: ubuntu-latest steps: - name: Checkout Actions Repository uses: actions/checkout@v4 - name: Check spelling uses: crate-ci/typos@master gir-0.20.5/.gitignore000066400000000000000000000000601475434152100143510ustar00rootroot00000000000000target /src/gir_version.rs /.vscode **.DS_Store gir-0.20.5/.typos.toml000066400000000000000000000002001475434152100145060ustar00rootroot00000000000000[files] extend-exclude = ["*.svg"] [default.extend-words] # Ignore false-positives gir = "gir" inout = "inout" serde = "serde" gir-0.20.5/Cargo.lock000066400000000000000000000163721475434152100143030ustar00rootroot00000000000000# This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "bitflags" version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" [[package]] name = "clock_ticks" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c49a90f58e73ac5f41ed0ac249861ceb5f0976db35fabc2b9c2c856916042d63" [[package]] name = "env_filter" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" dependencies = [ "log 0.4.25", ] [[package]] name = "env_logger" version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" dependencies = [ "env_filter", "log 0.4.25", ] [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "fix-getters-rules" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6af7c515020a47f109ddbb4ae0ea662e202c361e5d8570caaca2f1d9037d1bc" dependencies = [ "once_cell", ] [[package]] name = "getopts" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" dependencies = [ "unicode-width", ] [[package]] name = "gir" version = "0.0.1" dependencies = [ "bitflags", "env_logger", "fix-getters-rules", "getopts", "hprof", "log 0.4.25", "regex", "rustdoc-stripper", "toml", "xml-rs", ] [[package]] name = "hashbrown" version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] name = "hprof" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17b42e67c01ef27237e424783538a0bc45721ecd53438fab5c3f8bbf5dfd8516" dependencies = [ "clock_ticks", "log 0.3.9", ] [[package]] name = "indexmap" version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" dependencies = [ "equivalent", "hashbrown", ] [[package]] name = "log" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" dependencies = [ "log 0.4.25", ] [[package]] name = "log" version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "once_cell" version = "1.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" [[package]] name = "proc-macro2" version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] [[package]] name = "regex" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rustdoc-stripper" version = "0.1.19" source = "git+https://github.com/GuillaumeGomez/rustdoc-stripper#1170265c0f2a3728c086c717aa0603ccaad18a81" [[package]] name = "serde" version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_spanned" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ "serde", ] [[package]] name = "syn" version = "2.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "toml" version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", "toml_edit", ] [[package]] name = "toml_datetime" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ "serde", ] [[package]] name = "toml_edit" version = "0.22.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", "winnow", ] [[package]] name = "unicode-ident" version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" [[package]] name = "unicode-width" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "winnow" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59690dea168f2198d1a3b0cac23b8063efcd11012f10ae4698f284808c8ef603" dependencies = [ "memchr", ] [[package]] name = "xml-rs" version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5b940ebc25896e71dd073bad2dbaa2abfe97b0a391415e22ad1326d9c54e3c4" gir-0.20.5/Cargo.toml000066400000000000000000000014431475434152100143170ustar00rootroot00000000000000[package] name = "gir" version = "0.0.1" authors = [ "Evgenii Pashkin ", "Gleb Kozyrev ", "Guillaume Gomez ", ] build = "build.rs" exclude = ["Gir*.toml", "tests/**/*", "*.md", "generator.py"] edition = "2021" [dependencies] bitflags = "2.6" getopts = "0.2.21" getter_rules = { package = "fix-getters-rules", version = "0.3.0", default-features = false } xml-rs = "0.8" toml = { version = "0.8" , features = ["preserve_order"] } env_logger = { version = "0.11", default-features = false } log = "0.4" regex = "1.10" hprof = "0.1" rustdoc-stripper = { git = "https://github.com/GuillaumeGomez/rustdoc-stripper" } [profile.release] codegen-units = 4 [[bin]] name = "gir" path = "src/main.rs" [lib] name = "libgir" path = "src/lib.rs" gir-0.20.5/LICENSE000066400000000000000000000020701475434152100133710ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2015 Gleb Kozyrev Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. gir-0.20.5/README.md000066400000000000000000000010341475434152100136420ustar00rootroot00000000000000# GIR `GIR` is a project that helps for generating safe Rust bindings for GObject based libraries. The generated bindings consists of two parts: FFI (the unsafe 1:1 C API calls from Rust) and the safe high-level safe Rust API. ## How to use A work in progress book to help with learning how to use `gir` along with a tutorial are available at . If you intend to contribute to `gir` or make use of `libgir`, the docs are available at / . gir-0.20.5/book/000077500000000000000000000000001475434152100133175ustar00rootroot00000000000000gir-0.20.5/book/.gitignore000066400000000000000000000000051475434152100153020ustar00rootroot00000000000000book gir-0.20.5/book/book.toml000066400000000000000000000004721475434152100151510ustar00rootroot00000000000000[book] authors = ["The gtk-rs Project Developers"] language = "en" multilingual = false src = "src" title = "Generate Rust bindings for GIR based libraries" [output.html] git-repository-url = "https://github.com/gtk-rs/gir/tree/main/book" edit-url-template = "https://github.com/gtk-rs/gir/edit/main/book/{path}" gir-0.20.5/book/src/000077500000000000000000000000001475434152100141065ustar00rootroot00000000000000gir-0.20.5/book/src/SUMMARY.md000066400000000000000000000011151475434152100155630ustar00rootroot00000000000000# Summary - [Introduction](introduction.md) - [Tutorial](tutorial/introduction.md) - [Preparation](tutorial/preparation.md) - [Finding .gir files](tutorial/finding_gir_files.md) - [Generating the FFI library](tutorial/sys_library.md) - [Generating the Rust API](tutorial/high_level_rust_api.md) - [Handling generation errors](tutorial/handling_errors.md) - [Generating documentation](tutorial/generate_docs.md) - [Configuration files](config/introduction.md) - [FFI Options](config/ffi.md) - [API Options](config/api.md) - [Crate name override](config/name_override.md) gir-0.20.5/book/src/config/000077500000000000000000000000001475434152100153535ustar00rootroot00000000000000gir-0.20.5/book/src/config/api.md000066400000000000000000000563211475434152100164550ustar00rootroot00000000000000# API Options This mode requires you to write another TOML file. [gtk/Gir.toml](https://github.com/gtk-rs/gtk/blob/main/Gir.toml) is a good example. ```toml [options] girs_directories = ["gir-files"] library = "Gtk" version = "3.0" min_cfg_version = "3.4" target_path = "." # Path where objects generated (defaults to /src/auto) # auto_path = "src/auto" work_mode = "normal" # Whether the library uses https://gitlab.gnome.org/GNOME/gi-docgen for its documentation use_gi_docgen = false generate_safety_asserts = true deprecate_by_min_version = true # With this option enabled, versions for gir and gir-files saved only to one file to minimize noise, # can also take path to the directory for saving "versions.txt" or filename with extension. # Relative to target_path single_version_file = true # Trust the nullability information about return values. If this is disabled # then any pointer return type is assumed to be nullable unless there is an # explicit override for it. # This has to be used carefully as many libraries are missing nullable # annotations for return values, which then will cause a panic once an # unexpected NULL is returned. trust_return_value_nullability = false # Disable running `cargo fmt` on generated files # (defaults to false) disable_format = true # Always generate a Builder if possible. This is mostly a convenient setter as most of the # time you might want the Builder to be generated. Ignoring none-desired ones can still be done with per object `generate_builder` configuration. # (defaults to false) generate_builder = true ``` This mode generates only the specified objects. You can either add the object's fullname to the `generate` array or add it to the `manual` array (but in this case, it won't be generated, just used in other functions/methods instead of generating an "ignored" argument). Example: ```toml generate = ["Gtk.Widget", "Gtk.Window"] manual = ["Gtk.Button"] ``` So in here, both `GtkWidget` and `GtkWindow` will be fully generated and functions/methods using `GtkButton` will be uncommented. To generate code for all global functions, add `Gtk.*` to the `generate` array. To also generate a `Builder` struct for a widget, it needs to be set with the `generate_builder` flag in object configuration: ```toml [[object]] name = "Gtk.TreeView" status = "generate" generate_builder = true ``` > If the object doesn't already have a `Default` implementation through a constructor method without arguments, generating a `Builder` struct will add a `Default` implementation for the object. If you want to remove warning messages about the not bound `Builders` during the generation you don't want to be generated, you can ignore them with the `generate_builder` flag in object configuration: ```toml [[object]] name = "Gtk.TreeView" status = "generate" generate_builder = false ``` If there is some work which has to be done post-construction before the builder's `build` method returns, you can set the `builder_postprocess` value in the object configuration: ```toml [[object]] name = "Gtk.Application" status = "generate" generate_builder = true builder_postprocess = "Application::register_startup_hook(&ret);" ``` For the duration of the code in `builder_postprocess` the binding `ret` will be the value to be returned from the `build` method. Sometimes Gir understands the object definition incorrectly or the `.gir` file contains an incomplete or wrong definition, to fix it, you can use the full object configuration: ```toml [[object]] # object's fullname name = "Gtk.SomeClass" # can be also "manual" and "ignore" but it's simpler to just put the object in the same array status = "generate" # replace the parameter name for the child in child properties (instead "child") child_name = "item" # mark object as final type, i.e. one without any further subclasses. this # will not generate trait SomeClassExt for this object, but implement all # functions in impl SomeClass final_type = true # mark the object as a fundamental type in case the GIR file lacks the annotation # note that fundamental types don't make use of IsA/Cast traits and you should # implement something similar manually # gir is only capable for generating the type definitions along with their functions fundamental_type = false # mark the enum as exhaustive. This must only be done if it is impossible for # the C library to add new variants to the enum at a later time but allows # for more optimal code to be generated. exhaustive = false # allow rename result file module_name = "soome_class" # override starting version version = "3.12" # prefixed object in mod.rs with #[cfg(mycond)] cfg_condition = "mycond" # if you want to generate builder with name SomeClassBuilder generate_builder = true # trust return value nullability annotations for this specific type. # See above for details and use with care trust_return_value_nullability = false # Tweak the visibility of the type visibility = "pub" # or 'crate' / 'private' / 'super' # The default value to used for the `Default` implementation. It only # works for flags and enums. You have to pass the "GIR" member name. default_value = "fill" # Change the name of the generated trait to e.g avoid naming conflicts trait_name = "TraitnameExt" # In case you don't want to generate the documentation for this type. generate_doc = false # define overrides for function [[object.function]] # filter functions from object name = "set_website_label" # alternative way to apply override for many functions. Will be used with '^' and '$' on both sides # can be used instead of `name` almost anywhere # pattern = "[gs]et_value" # don't generate function ignore = true # override starting version version = "3.12" # prefixed function with #[cfg(mycond)] cfg_condition = "mycond" # prefixed function with #[doc(hidden)] doc_hidden = true # define a list of function parameters to be ignored when the documentation is generated doc_ignore_parameters = ["some_user_data_param"] # disable length_of autodetection disable_length_detect = true # write function docs to trait other than default "xxxExt", # also works in [object.signal] and [object.property] doc_trait_name = "SocketListenerExtManual" # disable generation of future for async function no_future = true # to rename the generated function rename = "something_else" # to override the default safety assertions: "none", "skip", # "not-initialized", "in-main-thread" assertion = "in-main-thread" # Tweak the visibility of the function visibility = "pub" # or 'crate' / 'private' / 'super' # In case you don't want to generate the documentation for this method. generate_doc = false # override for parameter [[object.function.parameter]] # filter by name name = "website_label" # allow to remove/add Option<> nullable = true # Take the parameter by value instead of by ref move = true # allow to make parameter immutable const = true # parameter is calculated as length of string or array and removed from function declaration # (for length of return value use "return") length_of = "str" # change string type. Variants: "utf8", "filename", "os_string" string_type = "os_string" # make function unsafe to call (emits `fn unsafe`) unsafe = true # override for return value [object.function.return] # allow to remove/add Option<> to return value nullable = true # convert bool return types to Result<(), glib::BoolError> with # the given error message on failure bool_return_is_error = "Function failed doing what it is supposed to do" # convert Option return types to Result with # the given error message on failure nullable_return_is_error = "Function failed doing what it is supposed to do" # always include the return value of throwing functions in the returned Result<...>, # without this option bool and guint return values are assumed to indicate success or error, # and are not included in the returned Result<...> use_return_for_result = true # change string type. Variants: "utf8", "filename", "os_string" string_type = "os_string" # overwrite type type = "Gtk.Widget" # Override callback's parameter [[object.function.parameter.callback_parameter]] name = "name_of_the_callback_parameter" nullable = true # virtual methods support the same configuration for parameters and return types as functions # note that they are not used for code generation yet. [[object.virtual_method]] # filter virtual method from object name = "set_website_label" # alternative way to apply override for many functions. Will be used with '^' and '$' on both sides # can be used instead of `name` almost anywhere # pattern = "[gs]et_value" # don't generate function ignore = true # override starting version version = "3.12" # prefixed function with #[cfg(mycond)] cfg_condition = "mycond" # prefixed function with #[doc(hidden)] doc_hidden = true # define a list of function parameters to be ignored when the documentation is generated doc_ignore_parameters = ["some_user_data_param"] # write function docs to trait other than default "xxxExt", # also works in [object.signal] and [object.property] doc_trait_name = "SocketListenerExtManual" # to rename the generated function rename = "something_else" # In case you don't want to generate the documentation for this method. generate_doc = false [[object.signal]] name = "activate-link" # replace trampoline bool return type with `Inhibit` inhibit = true ignore = true version = "3.10" doc_hidden = true # In case you don't want to generate the documentation for this signal. generate_doc = false [[object.signal.parameter]] name = "path_string" # allow to use different names in closure new_name = "path" # can be also "borrow" and "none": Add some transformation between ffi trampoline parameters and rust closure transformation = "treepath" nullable = true [object.signal.return] nullable = true # override for properties [[object.property]] name = "baseline-position" version = "3.10" ignore = true # In case you don't want to generate the documentation for this property. generate_doc = false [[object.property]] name = "events" # generate only `connect_property_events_notify`, without `get_property_events` and `set_property_events` # supported values: "get", "set", "notify" generate = ["notify"] ``` Since there are no child properties in `.gir` files, it needs to be added for classes manually: ```toml [[object]] name = "Gtk.SomeClassWithChildProperties" status = "generate" # replace parameter name for child in child properties (instead of "child") child_name = "item" # define concrete child type (instead of "Widget") child_type = "Gtk.MenuItem" [[object.child_prop]] name = "position" type = "gint" doc_hidden = true ``` For enumerations and bitflags, you can configure the members and mark the type as `#[must_use]`: ```toml [[object]] name = "Gdk.EventType" status = "generate" # generates #[must_use] attribute for the type must_use = true # override starting version version = "3.12" [[object.member]] name = "2button_press" # allows to skip elements with bad names, other members with same value used instead ignore = true # Allow to add a cfg condition cfg_condition = "target_os = \"linux\"" # In case you don't want to generate the documentation for this member. generate_doc = false [[object.member]] name = "touchpad_pinch" # define starting version when member added version = "3.18" ``` For enumerations, bitflags and boxed types / records, you can configure the `#[derive()]` clauses, optionally conditioned to a `cfg`. Providing `#[derive()]` clauses behaves differently depending on the type they are added to: * for boxed types / records (except `inline_boxed` types), the provided `#[derive()]` clauses replace all the derives that are normally added by default (i.e. `#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]`); if the default derives are needed, they must be explicitly listed. * for `inline_boxed` types, only the `Debug` trait can be specified (and thus added); providing any other trait will cause gir to fail with an error. * for enumerations, the provided `#[derive()]` clauses replace all the ones that are normally derived by default (i.e. `#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]`) with the exception of `Copy` and `Clone` that are always derived; if the default derives are needed, they must be explicitly listed. * for bitflags, the provided `#[derive()]` clauses are simply added to the derives implemented by default. ```toml [[object]] name = "Gst.Format" status = "generate" [[object.derive]] name = "Serialize, Deserialize" cfg_condition = "feature = \"serde\"" ``` For global functions, the members can be configured by configuring the `Gtk.*` object: ```toml [[object]] name = "Gtk.*" status = "generate" [[object.function]] name = "stock_list_ids" # allows to ignore global functions ignore = true # allows to define if the function was moved to a trait doc_trait_name = "StockExt" # allows to define if the function was moved to a struct doc_struct_name = "Stock" # In case you don't want to generate the documentation for this function. generate_doc = false ``` Which will prevent gir from generating `stock_list_ids`. If you want to specify that a function will be manually implemented, you can use: ```toml [[object]] name = "Gtk.Entry" status = "generate" [[object.function]] name = "get_invisible_char" manual = true ``` This will prevent gir from generating `get_invisible_char` and it won't generate `get_property_invisible_char` which would have been generated if we had used "ignore = true". Note that you must not place `Gtk.*` into the `generate` array and additionally configure its members. You can control the generation of constants in a similar fashion: ```toml [[object]] name = "Gtk.*" status = "generate" [[object.constant]] pattern = "*" # No constants will be generated ignore = true # In case you don't want to generate the documentation for this constant. generate_doc = false ``` Constants also support `version` and `cfg_condition` fields. In various cases, GObjects or boxed types can be used from multiple threads and have certain concurrency guarantees. This can be configured with the `concurrency` setting at the top-level options or per object. It will automatically implement the `Send` and `Sync` traits for the resulting object and set appropriate trait bounds for signal callbacks. The default is `none`, and apart from that `send` and `send+sync` are supported. ```toml [[object]] # object's fullname name = "Gtk.SomeClass" # can be also "manual" and "ignore" but it's simpler to just put the object in the same array status = "generate" # concurrency of the object, default is set in the top-level options or # otherwise "none". Valid values are "none", "send" and "send+sync" concurrency = "send+sync" ``` Note that `send` is only valid for types that are either not reference counted (i.e. `clone()` copies the object) or that are read-only (i.e. no API for mutating the object exists). `send+sync` is valid if the type can be sent to different threads and all API allows simultaneous calls from different threads due to internal locking via e.g. a mutex. ```toml [[object]] name = "Gtk.Something" status = "manual" # Can also be "ref-mut", "ref-immut" ref_mode = "ref" ``` When manually generating bindings, it can happen that the reference mode detected by GIR is different than what was implemented and conversion to the C types are wrong in autogenerated functions that have such objects as argument. This can be overridden with the `ref_mode` configuration. Getters are automatically renamed to comply with Rust codying style guidelines. However, this can cause name clashes with existing functions. If you want to bypass the automatic renaming mechanism, use `bypass_auto_rename = true`: ```toml [[object]] name = "Gtk.TextBuffer" [...] [[object.function]] name = "get_insert" # Avoid clash with the `insert` operation. bypass_auto_rename = true ``` Some constructors are not annotated as `constructor` in the `gir` files. In order for the naming convention to be applied, you can force a function to be considered as a constructor: ```toml [[object.function]] name = "new_for_path" # Not annotated as constructor in Gir => force it to apply naming convention constructor = true ``` ## conversion_type "Option" The `conversion_type` variant `Option` is available for types `T` implementing `glib::TryFromGlib`. As a reminder, this allows implementing `FromGlib` for `Option` and usually goes alongside with `ToGlib` for both `T` and `Option`. In this case, `Option` will be used for return values (including ffi output arguments). For in-arguments, except if the parameter is declared `mandatory`, `impl Into>` so that either an `Option` or `T` can be used. Ex. from `gstreamer-rs`: ``` rust [[object]] name = "Gst.ClockTime" status = "manual" conversion_type = "Option" ``` The type `ClockTime` implements `glib::TryFromGlib` (and `OptionToGlib`), which means that its Rust representation can take advantage of `Option`. Additionally, the user can instruct `gir` to `expect` `Some` or `Ok` results for specific arguments or return values. E.g.: ``` rust [[object]] name = "Gst.Clock" status = "generate" manual_traits = ["ClockExtManual"] [[object.function]] name = "get_calibration" [[object.function.parameter]] name = "internal" mandatory = true ``` In the above example, the user instructs gir to consider the `internal` argument (which also happens to be an out argument) with type gir `Gst.ClockTime` can be represented as a `ClockTime` without the `Option`. This argument is actually part of a set of output arguments. With the above gir declaration, the generated signature is the following (the implementation takes care of `expect`ing the value to be defined): ``` rust fn get_calibration( &self, ) -> ( ClockTime, Option, Option, Option, ); ``` For a return value, the mandatory declaration reads: ``` rust [[object.function]] name = "util_get_timestamp" /.../ [object.function.return] # always returns a value mandatory = true ``` ## conversion_type "Result" The `conversion_type` variant `Result` is available for types `T` implementing `glib::TryFromGlib` where `Err` is neither `GlibNoneError` nor `GlibNoneOrInvalidError`. In this case, `Result` will be used for return values (including `ffi` output arguments) and the type itself in argument position. In `gstreamer-rs`, the C type `GstStateChangeReturn` can represent both a successful or an error return value. In Rust, the `Result` `enum` is the idiomatic way of returning an error. In `gstreamer-rs`, bindings to functions returning `GstStateChangeReturn` had to be manually implemented so as to return `Result`. Note that in this case, the type implementing `TryFromGlib` is `StateChangeSuccess` and not `GstStateChangeReturn`. These functions can be auto-generated using: ``` rust [[object]] name = "Gst.StateChangeReturn" status = "generate" must_use = true [object.conversion_type] variant = "Result" ok_type = "gst::StateChangeSuccess" err_type = "gst::StateChangeError" ``` ## Boxed types vs. BoxedInline types For boxed types / records, gir auto-detects `copy`/`free` or `ref`/`unref` function pairs for memory management on records. It falls back to generic `g_boxed_copy`/`g_boxed_free` if these are not found, based on an existing implementation of `get_type`. Otherwise no record implementation can be generated. This works for the majority of boxed types, which are literally boxed: their memory is always allocated on the heap and memory management is left to the C library. Some boxed types, however, are special and in C code they are usually allocated on the stack or inline inside another struct. As such, their struct definition is public and part of the API/ABI. Usually these types are relatively small and allocated regularly, which would make heap allocations costly. These special boxed types are usually allocated by the caller of the C functions, and the functions are then only filling in the memory and taking values of this type as `(out caller-allocates)` parameters. In most other bindings, heap allocations are used for these boxed types too but in Rust we can do better and actually allocate them on the stack or inline inside another struct. Gir calls these special boxed types "boxed inline". ```toml [[object]] name = "GLib.TreeIter" status = "generate" boxed_inline = true ``` For inline-allocated boxed types it is possible to provide Rust expressions in the configuration for initializing newly allocated memory for them, to copy from one value into another one, and to free any resources that might be stored in values of that boxed types. By default the memory is zero-initialized and copying is done via `std::ptr::copy()`. If the boxed type contains memory that needs to be freed then these functions must be provided. The following configuration is equivalent with the one above. ```toml [[object]] name = "GLib.TreeIter" status = "generate" boxed_inline = true init_function_expression = "|_ptr| ()" copy_into_function_expression = "|dest, src| { std::ptr::copy_nonoverlapping(src, dest, 1); }" clear_function_expression = "|_ptr| ()" ``` ## Generation in API mode To generate the Rust-user API level, The command is very similar to the previous one. It's better to not put this output in the same directory as where the FFI files are. Just run: ```sh cargo run --release -- -c YourGirFile.toml -d ../gir-files -o the-output-directory ``` Now it should be done. Just go to the output directory (so `the-output-directory/auto` in our case) and try to build using `cargo build`. Don't forget to update your dependencies in both projects: nothing much to do in the FFI/sys one but the Rust-user API level will need to have a dependency over the FFI/sys one. Now, at your crate entry point (generally `lib.rs`), add the following to include all generated files: ```rust pub use auto::*; ``` ## Add manual bindings alongside generated code Unfortunately, `gir` isn't perfect (yet) and will certainly not be able to generate all the code on its own. So here's what a `gir` generated folder looks like: ```text - your_folder | |- Cargo.toml |- src | |- lib.rs |- auto | |- (all files generated by gir) ``` You can add your manual bindings directly inside the `src` folder (at the same level as `lib.rs`). Then don't forget to reexport them. Let's say you added a `Color` type in a `color.rs` file. You need to add in `lib.rs`: ```rust // We make the type public for the API users. pub use color::Color; mod color; ``` gir-0.20.5/book/src/config/ffi.md000066400000000000000000000043771475434152100164540ustar00rootroot00000000000000# FFI Options In FFI (`-m sys`) mode, `gir` generates as much as it can. So in this mode, the TOML file is mostly used to ignore some objects. To do so, you need to add its fullname to an `ignore` array. Example: ```toml ignore = ["Gtk.Widget", "Gtk.Window"] ``` And that's all. Neither `GtkWidget` nor `GtkWindow` (alongside with their functions) will be generated. You also need to add any needed external libraries in the "external_libraries" parameter. Example: ```toml [options] external_libraries = [ "GLib", "GObject", ] ``` You can specify a few other options: ```toml [options] girs_directories = ["../gir-files"] library = "GtkSource" version = "3.0" min_cfg_version = "3.0" target_path = "." # Path where lib.rs generated (defaults to /src) # auto_path = "src" work_mode = "sys" # If true then build.rs will be split into 2 parts: # always generated build_version.rs, # and build.rs that generated only if not exists. # Defaults to false split_build_rs = false # Adds extra versions to features extra_versions = [ "3.15", "3.17", ] # Change library version for version [[lib_version_overrides]] version = "3.16" lib_version = "3.16.1" # Add extra dependencies to feature [[feature_dependencies]] version = "3.16" dependencies = [ "glib-sys/v3_16" ] ``` Also, you can add rust cfg conditions on objects, functions and constants, for example, when flagging for conditional compilation: ```toml [[object]] name = "GstGL.GLDisplayEGL" status = "generate" cfg_condition = "feature = \"egl\"" [[object.function]] pattern = ".*" cfg_condition = "feature = \"egl\"" ``` ## Generation in FFI mode When you're ready, let's generate the FFI part. In the command we'll execute, `../gir-files` is where the directory with your `.gir` files is. (But again, you can just clone the [gir-files repository](https://github.com/gtk-rs/gir-files) and add your file(s) in it). Then let's run the command: ```sh cargo run --release -- -c YourSysGirFile.toml -d ../gir-files -m sys -o the-output-directory-sys ``` The generated files will be placed in `the-output-directory-sys`. Just take care about the dependencies and the crate's name generated in the `Cargo.toml` file (update them if they don't work as expected). You now have the sys part of your binding! gir-0.20.5/book/src/config/introduction.md000066400000000000000000000007741475434152100204260ustar00rootroot00000000000000# Configuration GIR uses two configurations files, one for generating the FFI part of the bindings and the other file for the Rust API. The configuration files must be named `Gir.toml` - The FFI configuration allows things such as ignoring objects, overriding the minimum required version for a specific type or renaming the generated crate name. - The Rust API configuration is a bit more complex as it allows configuring Objects, Enums, Bitfields, Functions, Properties, Signals and a few other things. gir-0.20.5/book/src/config/name_override.md000066400000000000000000000006251475434152100205170ustar00rootroot00000000000000# Crate name overrides `gir` uses simple rule to convert a namespace to a crate name and it sometimes goes wrong. For example, "WebKit2WebExtension" namespace will be converted to "web_kit2_web_extension", which looks bad. To fix it, the `crate_name_overrides` option can be used. It also replaces FFI crates' name. ```toml [crate_name_overrides] "web_kit2_web_extension" = "webkit2_webextension" ``` gir-0.20.5/book/src/introduction.md000066400000000000000000000060771475434152100171630ustar00rootroot00000000000000# Introduction [gir] is a tool to automatically generate safe wrappers for a C library with [GObject introspection](https://gi.readthedocs.io/en/latest/) information. In order to use it you need the `.gir` file containing the introspection data for the library you want to create the bindings for, as well as the `.gir` files for all its dependencies. Have a look at the tutorial if you don't know how to [find the .gir files](tutorial/finding_gir_files.md). If your library does not provide a `.gir` file, unfortunately you cannot use [gir], but maybe you can try [rust-bindgen](https://github.com/rust-lang/rust-bindgen). This book contains a tutorial on how to use [gir]. As an example we will create the bindings for [Pango](https://docs.gtk.org/Pango/). In many cases you will be able to follow the same steps with your library. If you are already familiar with [gir] and you just want to look up details about the configuration files, feel free to skip ahead to the documentation of the [configuration files](config/introduction.md). ## General steps [gir] tries to make it as simple as possible to generate a safe wrapper for your C library. The process can be divided into four steps that correspond to the four operating modes gir has. - Generating unsafe bindings: In this step, the low-level FFI bindings are created from the supplied `*.gir` files. These are essentially direct calls into the related C library and are unsafe. The resulting crate is typically appended with -sys. The operating mode is `sys`. - Generating a safe wrapper: Next, another crate for a layer on top of these unsafe (sys) bindings is created, which makes them safe for use in general Rust. The operating mode is `normal`. - Checking for missing types/methods: The operating mode `not_bound` allows you to see the detected types/methods that will not be generated for whatever reasons. - Adding documentation: After the safe wrapper is created, gir can even generate the documentation for us. Use the operating mode `doc` to do so. ## Regenerating the bindings and wrapper In order to generate the bindings and the wrapper for the first time, the above-mentioned steps should be followed. When you want to regenerate the crates because e.g. the library was updated, you can simplify the process by running the helper script `./generator.py`. The script detects `Gir.toml` configurations in the current directory and subdirectories (or the paths passed on the command-line) and generates "normal" or "sys" crates for it. Alternatively `--embed-docs` can be passed to prepare source-code for a documentation built by moving all documentation into it. For a complete overview of available options, pass `--help`. ## GIR format reference It can always be useful to look at the [reference](https://gi.readthedocs.io/en/latest/annotations/giannotations.html) or [schema](https://gitlab.gnome.org/GNOME/gobject-introspection/blob/main/docs/gir-1.2.rnc). ## Contact us If you use [gir] on another library and it fails and you can't figure out why, don't hesitate to [contact us](https://gtk-rs.org/contact)! [gir]: https://github.com/gtk-rs/girgir-0.20.5/book/src/tutorial/000077500000000000000000000000001475434152100157515ustar00rootroot00000000000000gir-0.20.5/book/src/tutorial/finding_gir_files.md000066400000000000000000000064731475434152100217460ustar00rootroot00000000000000# Where can I find those .gir files? There are multiple ways you can get the needed `.gir` files. The `*.gir` you need corresponds to the library you want to generate bindings for. If you have the library installed, you can search for the `.gir` file under `/usr/share/gir-1.0/`. Having the library installed is a good idea so that you can test the generated code. Otherwise you should be able to get it from the package that installs the library. Ubuntu for example allows you to [search](https://packages.ubuntu.com/) for packages and download them via their website. You can copy the `.gir` file of your library to the root of your project folder. You don't have to store all `.gir` files in the same folder. You can add multiple paths by changing the `girs_directories` field in the Gir.toml files. More on this in the next chapters. Have a look at the .gir file of your library. At the beginning of the file, you probably see something similar to ``. "GObject" in this case would be a dependency and you will have to find the .gir file for your dependencies as well. In most cases it will be enough to follow the next two steps of the tutorial to get all needed files. ## GTK dependencies If your library depends on GTK libraries, the recommended way to get the `.gir` files for them is to add the [gir-files repo](https://github.com/gtk-rs/gir-files) as a submodule as well. It's the recommended way, because some of the `.gir` files included in the libraries are invalid (missing or invalid annotations for example). These errors are already fixed in the gir files from the repo. Otherwise you could use the above-mentioned methods to find the files and run the [script](https://github.com/gtk-rs/gir-files/blob/main/fix.sh) to fix the `.gir` files available in the gir-files repository (and only them!). You can run it like this (at the same level of the `.gir` files you want to patch): ```sh sh fix.sh ``` ## GStreamer dependencies For GStreamer related dependencies, follow the above-mentioned steps but add this [repo](https://gitlab.freedesktop.org/gstreamer/gir-files-rs) instead. ## Other dependencies If you have other dependencies, you have to find the files yourself. They can often be found in the repo containing the source of your dependencies or if you have them installed, you might find them under `/usr/share/gir-1.0/` again. ## Example We want to generate the wrapper for pango. It is related to GTK, so in order to get its .gir files, we use the recommended way. While being in the project folder `git-tutorial`, we add the [gir-files repo](https://github.com/gtk-rs/gir-files) as a submodule and set the branch of the submodule to main. ```sh git submodule add https://github.com/gtk-rs/gir-files git config -f .gitmodules submodule.gir-files.update none git submodule set-branch --branch main -- ./gir-files ``` We also change the setting so that the submodule is not automatically checked out, otherwise anyone using your library from git will have the useless submodule checked out. Run `git submodule update --checkout` if you want to update the submodule. If you look into `gir-files`, you'll see a file named `Pango-1.0.gir`. That's the one for pango. Because we already added the gir-files repo, we also have all the other .gir files of the dependencies that we need. Now we can create the unsafe bindings.gir-0.20.5/book/src/tutorial/generate_docs.md000066400000000000000000000055361475434152100211060ustar00rootroot00000000000000# Generating documentation And finally the last feature. Just run the following command in the folder of the safe wrapper crate: ```sh gir -c Gir.toml -d ../gir-files --doc-target-path docs.md -m doc ``` * `-d ../gir-files`: flag to select the folder containing the .gir files. * `--doc-target-path docs.md`: flag to select the name of the markdown file containing the documentation. * `-m doc`: flag to select the work mode for generating the documentation. It'll generate a markdown file if everything went fine. It contains all of the crate's documentation. If you want to put it into your crate's source code like "normal" doc comments, run: ```sh cargo install rustdoc-stripper rustdoc-stripper -g -o docs.md ``` And now your crate should be completely documented as expected! Running the above commands again would duplicate the doc comments. Make sure to first remove the old ones before running the command again. You can do this by running the following commands: ```sh rustdoc-stripper -s -n rustdoc-stripper -g -o docs.md ``` Try building the documentation and also try it with the various features you might have: ```sh cargo doc ``` For building the docs on systems without the required dependencies installed (for example docs.rs or CI jobs), you can use the `docsrs` attribute. This will disable the linking of the underlying C libraries and won't throw an error if those libraries are not available. On top of that, the `docsrs` attribute will enable the [doc_cfg feature](https://doc.rust-lang.org/beta/unstable-book/language-features/doc-cfg.html) which is only available in the nightly compiler toolchain at the time of writing. This is mostly useful for clarifying feature requirements through the docs. To build the docs with the `docsrs` attribute, you can use the following command: ```sh RUSTFLAGS='--cfg docsrs' RUSTDOCFLAGS='--cfg docsrs' cargo +nightly doc --all-features ``` Congratulations, we are done. You have successfully created the safe wrapper for a C library! You can easily publish your generated bindings and the wrapper to crates.io to allow others to use it. Publishing crates is easy but keep in mind that they need to be maintained as well. We set up the project folder in a way that easily allows sharing the code. All that is needed is to add some information to your Cargo.toml. Gir will not override them when you re-generate bindings. Easy, right. If this is your first time publishing a crate, you can find a detailed guide [here](https://doc.rust-lang.org/cargo/reference/publishing.html). Before you publish the crate, please ensure docs.rs will activate the `docsrs` attribute. Feel free to go back to the chapter about the [Cargo.toml file of the safe wrapper](high_level_rust_api.md#the-cargotoml-file) to read more about it. If you skip this step, your crate and all crates depending on it will not have documentation available on docs.rs.gir-0.20.5/book/src/tutorial/handling_errors.md000066400000000000000000000125431475434152100214600ustar00rootroot00000000000000# Handling generation errors Luckily there are only a few errors which can happen with [gir] generation. Let's take a look at them. ### Cannot find macros Compilation of the generated bindings may fail with errors like the following: ```console error: cannot find macro `skip_assert_initialized` in this scope --> src/auto/enums.rs:83:9 | 83 | skip_assert_initialized!(); | ^^^^^^^^^^^^^^^^^^^^^^^ | = help: have you added the `#[macro_use]` on the module/import? error: cannot find macro `assert_initialized_main_thread` in this scope --> src/auto/example.rs:33:9 | 33 | assert_initialized_main_thread!(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: have you added the `#[macro_use]` on the module/import? ``` In this case you’ll have to implement them yourself. Macros are order-dependent and you *must* insert this code before declaring modules that use it (e.g. `mod auto`). For example, you can add the following to your `lib.rs` file: ```rust /// No-op. macro_rules! skip_assert_initialized { () => {}; } /// Asserts that this is the main thread and either `gdk::init` or `gtk::init` has been called. macro_rules! assert_initialized_main_thread { () => { if !::gtk::is_initialized_main_thread() { if ::gtk::is_initialized() { panic!("GTK may only be used from the main thread."); } else { panic!("GTK has not been initialized. Call `gtk::init` first."); } } }; } ``` One complication here is that the `assert_initialized_main_thread!` macro depends on the exact library. If it's GTK-based then the above macro is likely correct, unless the library has its own initialization function. If it has its own initialization function it would need to be handled in addition to GTK's here in the same way. For non-GTK-based libraries the following macro would handle the initialization function of that library in the same way, or if there is none it would simply do nothing: ```rust /// No-op. macro_rules! assert_initialized_main_thread { () => {}; } ``` ## Missing memory management functions If [gir] generation fails (for whatever reason), it means you'll have to implement the type yourself. Just like types from other `gtk-rs` crates, you'll need to put it into the "manual" list. Then you need to put the type into the `src` folder (or inside a subfolder, you know how Rust works). /!\ Don't forget to reexport the type inside your `src/lib.rs` file. For example, let's take a look at the [requisition.rs](https://github.com/gtk-rs/gtk3-rs/blob/master/gtk/src/requisition.rs) file from the `gtk3` crate. Since it's a "simple" type (no pointer, therefore no memory management to do), [gir] doesn't know how to generate it. You'll need to implement some traits by hand like `ToGlibPtr` or `ToGlibPtrMut` (depending on your needs). ## Bad function generation In some cases, the generated code isn't correct (array parameters are often an issue). In such cases, it's better to just make the implementation yourself. As an example, let's say you want to implement `Region::is_empty` yourself. A few changes have to be made. Let's start with `Gir.toml`: ```toml generate = [ "GtkSource.Language", ] [[object]] name = "GtkSource.Region" status = "generate" [[object.function]] name = "is_empty" ignore = true ``` So to sum up what was written above: we removed "GtkSource.Region" from the "generate" list and we created a new entry for it. Then we say to [gir] that it should generate (through `status = "generate"`). However, we also tell it that we don't want the "is_empty" function to be generated. Now that we've done that, we need to implement it. Let's create a `src/region.rs` file: ```rust use glib::object::IsA; use glib::translate::*; use Region; pub trait RegionExtManual: 'static { pub fn is_empty(&self) -> bool; } impl> RegionExtManual for O { pub fn is_empty(&self) -> bool { // blablabla true } } ``` You might wonder: "why not just implementing it on the `Region` type directly?". Because like this, a subclass will also be able to use this trait easily as long as it implements `IsA`. For instance, in gtk, everything that implements `IsA` (so almost every GTK types) can use those methods. As usual, don't forget to reexport the trait. A little tip about reexporting manual traits: in `gtk3-rs`, we create a `src/prelude.rs` file which reexports all traits (both manual and generated ones), making it simpler for users to use them through `use [DEPENDENCY]::prelude::*`. The `src/prelude.rs` file looks like this: ```rust pub use crate::auto::traits::*; pub use region::RegionExtManual; ``` Then it's reexported as follows from the `src/lib.rs` file: ```rust pub mod prelude; pub use prelude::*; ``` ## Manually defined traits missing from the documentation If you defined traits manually, you can add them to the "Implements" section in the documentation for classes and interfaces by using the `manual_traits = []` option in the `Gir.toml` file. Here is an example: ```toml [[object]] name = "Gtk.Assistant" status = "generate" #add link to trait from current crate manual_traits = ["AssistantExtManual"] [[object]] name = "Gtk.Application" status = "generate" #add link to trait from other crate manual_traits = ["gio::ApplicationExtManual"] ``` [gir]: https://github.com/gtk-rs/gir gir-0.20.5/book/src/tutorial/high_level_rust_api.md000066400000000000000000000357271475434152100223250ustar00rootroot00000000000000# Generating the Rust API In the previous step we successfully created the unsafe bindings of the -sys crate. We are now in the directory of the safe wrapper crate (`gir-tutorial/pango`). ## The Cargo.toml file The Cargo.toml file will not be replaced when you run gir. So it is our responsibility to make sure the information in it is correct. Open the Cargo.toml file and have a look at it. Make sure everything under `[package]` is to your liking. Add the following lines to the file: ```toml [package.metadata.docs.rs] all-features = true # For build.rs scripts rustc-args = ["--cfg", "docsrs"] # For rustdoc rustdoc-args = ["--cfg", "docsrs"] ``` This automatically activates the `docsrs` attribute if you chose to publish the bindings and docs.rs tries to build the documentation. With the `docsrs` attribute, the crate skips linking the C libraries and allows docs.rs to build the documentation without having the underlying libraries installed. Even if you don't plan to publish it, this line is not going to hurt. We also need to add `libc`, `bitflags`, `glib` and `glib-sys` and all other dependencies we used in the sys crate as dependencies. Because we are creating a wrapper for the sys crate, which we generated in the previous chapter, we also need to add the sys crate to the list of dependencies. In the automatically generated code, the sys crate is always called `ffi`, so we need to rename the sys crate in our `Cargo.toml`. For our example, this results in the following dependencies: ```toml [dependencies] libc = "0.2" bitflags = "2.2" [dependencies.ffi] package = "pango-sys" path = "./pango-sys" [dependencies.glib] package = "glib-sys" git = "https://github.com/gtk-rs/gtk-rs-core" [dependencies.gobject] package = "gobject-sys" git = "https://github.com/gtk-rs/gtk-rs-core" ``` In order to make the features of the sys crate available for users of your safe wrapper, you need to add features. Copy the `[features]` part of the Cargo.toml of your sys crate and paste it into the Cargo.toml of the normal crate. The features are supposed to activate the corresponding features of the sys crate, so you need to make some changes. If for example you have the following sys features: ```toml [features] v1_2 = [] v1_4 = ["v1_2"] v1_6 = ["v1_4"] v1_8 = ["v1_6"] v1_10 = ["v1_8"] v1_12 = ["v1_10"] v1_14 = ["v1_12"] v1_16 = ["v1_14"] v1_18 = ["v1_16"] v1_20 = ["v1_18"] v1_22 = ["v1_20"] v1_24 = ["v1_22"] v1_26 = ["v1_24"] v1_30 = ["v1_26"] v1_31 = ["v1_30"] v1_32 = ["v1_31"] v1_32_4 = ["v1_32"] v1_34 = ["v1_32_4"] v1_36_7 = ["v1_34"] v1_38 = ["v1_36_7"] v1_42 = ["v1_38"] v1_44 = ["v1_42"] v1_46 = ["v1_44"] v1_48 = ["v1_46"] v1_50 = ["v1_48"] v1_52 = ["v1_50"] ``` You need to change the features in the Cargo.toml of your normal crate to ```toml [features] v1_2 = ["ffi/v1_2"] v1_4 = ["ffi/v1_4", "v1_2"] v1_6 = ["ffi/v1_6", "v1_4"] v1_8 = ["ffi/v1_8", "v1_6"] v1_10 = ["ffi/v1_10", "v1_8"] v1_12 = ["ffi/v1_12", "v1_10"] v1_14 = ["ffi/v1_14", "v1_12"] v1_16 = ["ffi/v1_16", "v1_14"] v1_18 = ["ffi/v1_18", "v1_16"] v1_20 = ["ffi/v1_20", "v1_18"] v1_22 = ["ffi/v1_22", "v1_20"] v1_24 = ["ffi/v1_24", "v1_22"] v1_26 = ["ffi/v1_26", "v1_24"] v1_30 = ["ffi/v1_30", "v1_26"] v1_31 = ["ffi/v1_31", "v1_30"] v1_32 = ["ffi/v1_32", "v1_31"] v1_32_4 = ["ffi/v1_32_4", "v1_32"] v1_34 = ["ffi/v1_34", "v1_32_4"] v1_36_7 = ["ffi/v1_36_7", "v1_34"] v1_38 = ["ffi/v1_38", "v1_36_7"] v1_42 = ["ffi/v1_42", "v1_38"] v1_44 = ["ffi/v1_44", "v1_42"] v1_46 = ["ffi/v1_46", "v1_44"] v1_48 = ["ffi/v1_48", "v1_46"] v1_50 = ["ffi/v1_50", "v1_48"] v1_52 = ["ffi/v1_52", "v1_50"] ``` ## The lib.rs file The lib.rs file will not be replaced when you run gir. All the code that gir will generate for us is going to be in src/auto. We need to include all `auto` files in our library. Also it needs to include sys create so auto files can use it. To do so, let's update the `src/lib.rs` file as follows: ```rust #![cfg_attr(docsrs, feature(doc_cfg))] use ffi; pub use auto::*; mod auto; ``` ## The Gir.toml file As you certainly guessed, we have to fill our `Gir.toml` file for the normal crate as well. Let's write it: ```toml [options] library = "Pango" version = "1.0" min_cfg_version = "1.0" target_path = "." girs_directories = ["../gir-files"] work_mode = "normal" single_version_file = true generate_safety_asserts = true deprecate_by_min_version = true generate = [] manual = [] ``` Many of these options look familiar from the last chapter but there are also a few new things in here. Let's take a look at them: * `work_mode` value is now set to `normal`, it means it'll generate the high-level Rust api instead of the sys-level. * `generate_safety_asserts` is used to generates checks to ensure that, or any other kind of initialization needed before being able to use the library. * `deprecate_by_min_version` is used to generate a [Rust "#[deprecated]"](https://doc.rust-lang.org/edition-guide/rust-2018/the-compiler/an-attribute-for-deprecation.html) attribute based on the deprecation information provided by the `.gir` file. * `generate = []`: this line currently does nothing. We say to [gir] to generate nothing. We'll fill it later on. * `manual = []`: this line currently does nothing. We can let [gir] know about objects which it does not have to generate code for. Let's make a first generation of our high-level Rust API! ```sh gir -o . ``` If you take a look at which files and folders were created, you'll see a new "auto" folder inside "src". This folder contains all the generated code. It doesn't contain anything though. Which makes sense since we're generating nothing. Now it's time to introduce you to a whole new [gir] mode: `not_bound`. Let's give it a try: ```console > gir -o . -m not_bound [NOT GENERATED] Pango.Glyph [NOT GENERATED] Pango.GlyphUnit [NOT GENERATED] Pango.GlyphItem [NOT GENERATED] Pango.LayoutRun [NOT GENERATED] Pango.Alignment [NOT GENERATED] Pango.Font [NOT GENERATED] Pango.Language [NOT GENERATED] Pango.Analysis [NOT GENERATED] Pango.AttrType [NOT GENERATED] Pango.Attribute [NOT GENERATED] Pango.Color [NOT GENERATED] Pango.AttrColor [NOT GENERATED] Pango.AttrFloat [NOT GENERATED] Pango.FontDescription [NOT GENERATED] Pango.AttrFontDesc [NOT GENERATED] Pango.AttrFontFeatures [NOT GENERATED] Pango.AttrInt [NOT GENERATED] Pango.AttrIterator [NOT GENERATED] Pango.AttrLanguage [NOT GENERATED] Pango.AttrList [NOT GENERATED] Pango.Rectangle [NOT GENERATED] Pango.AttrShape [NOT GENERATED] Pango.AttrSize [NOT GENERATED] Pango.AttrString [NOT GENERATED] Pango.BaselineShift [NOT GENERATED] Pango.BidiType (deprecated in 1.44) [NOT GENERATED] Pango.Context [NOT GENERATED] Pango.Direction [NOT GENERATED] Pango.Gravity [NOT GENERATED] Pango.FontMap [NOT GENERATED] Pango.GravityHint [NOT GENERATED] Pango.Matrix [NOT GENERATED] Pango.FontMetrics [NOT GENERATED] Pango.FontFamily [NOT GENERATED] Pango.Fontset [NOT GENERATED] Pango.Coverage [NOT GENERATED] Pango.CoverageLevel [NOT GENERATED] Pango.EllipsizeMode [NOT GENERATED] Pango.FontFace [NOT GENERATED] Pango.FontMask [NOT GENERATED] Pango.Stretch [NOT GENERATED] Pango.Style [NOT GENERATED] Pango.Variant [NOT GENERATED] Pango.Weight [NOT GENERATED] Pango.FontScale [NOT GENERATED] Pango.FontsetSimple [NOT GENERATED PARENT] Pango.Fontset [NOT GENERATED] Pango.GlyphGeometry [NOT GENERATED] Pango.GlyphVisAttr [NOT GENERATED] Pango.GlyphInfo [NOT GENERATED] Pango.Item [NOT GENERATED] Pango.GlyphString [NOT GENERATED] Pango.LogAttr [NOT GENERATED] Pango.GlyphItemIter [NOT GENERATED] Pango.Script [NOT GENERATED] Pango.Layout [NOT GENERATED] Pango.LayoutDeserializeFlags [NOT GENERATED] Pango.LayoutIter [NOT GENERATED] Pango.LayoutLine [NOT GENERATED] Pango.TabArray [NOT GENERATED] Pango.WrapMode [NOT GENERATED] Pango.LayoutSerializeFlags [NOT GENERATED] Pango.LayoutDeserializeError [NOT GENERATED] Pango.Overline [NOT GENERATED] Pango.RenderPart [NOT GENERATED] Pango.Renderer [NOT GENERATED] Pango.Underline [NOT GENERATED] Pango.ScriptIter [NOT GENERATED] Pango.ShapeFlags [NOT GENERATED] Pango.ShowFlags [NOT GENERATED] Pango.TabAlign [NOT GENERATED] Pango.TextTransform [NOT GENERATED FUNCTION] Pango.attr_allow_breaks_new because of Pango.Attribute [NOT GENERATED FUNCTION] Pango.attr_background_alpha_new because of Pango.Attribute [NOT GENERATED FUNCTION] Pango.attr_background_new because of Pango.Attribute [NOT GENERATED FUNCTION] Pango.attr_baseline_shift_new because of Pango.Attribute [NOT GENERATED FUNCTION] Pango.attr_break because of Pango.AttrList and Pango.LogAttr [NOT GENERATED FUNCTION] Pango.attr_fallback_new because of Pango.Attribute [NOT GENERATED FUNCTION] Pango.attr_family_new because of Pango.Attribute [NOT GENERATED FUNCTION] Pango.attr_font_scale_new because of Pango.FontScale and Pango.Attribute [NOT GENERATED FUNCTION] Pango.attr_foreground_alpha_new because of Pango.Attribute [NOT GENERATED FUNCTION] Pango.attr_foreground_new because of Pango.Attribute [NOT GENERATED FUNCTION] Pango.attr_gravity_hint_new because of Pango.GravityHint and Pango.Attribute [NOT GENERATED FUNCTION] Pango.attr_gravity_new because of Pango.Gravity and Pango.Attribute [NOT GENERATED FUNCTION] Pango.attr_insert_hyphens_new because of Pango.Attribute [NOT GENERATED FUNCTION] Pango.attr_letter_spacing_new because of Pango.Attribute [NOT GENERATED FUNCTION] Pango.attr_line_height_new because of Pango.Attribute [NOT GENERATED FUNCTION] Pango.attr_line_height_new_absolute because of Pango.Attribute [NOT GENERATED FUNCTION] Pango.attr_overline_color_new because of Pango.Attribute [NOT GENERATED FUNCTION] Pango.attr_overline_new because of Pango.Overline and Pango.Attribute [NOT GENERATED FUNCTION] Pango.attr_rise_new because of Pango.Attribute [NOT GENERATED FUNCTION] Pango.attr_scale_new because of Pango.Attribute [NOT GENERATED FUNCTION] Pango.attr_sentence_new because of Pango.Attribute [NOT GENERATED FUNCTION] Pango.attr_show_new because of Pango.ShowFlags and Pango.Attribute [NOT GENERATED FUNCTION] Pango.attr_stretch_new because of Pango.Stretch and Pango.Attribute [NOT GENERATED FUNCTION] Pango.attr_strikethrough_color_new because of Pango.Attribute [NOT GENERATED FUNCTION] Pango.attr_strikethrough_new because of Pango.Attribute [NOT GENERATED FUNCTION] Pango.attr_style_new because of Pango.Style and Pango.Attribute [NOT GENERATED FUNCTION] Pango.attr_text_transform_new because of Pango.TextTransform and Pango.Attribute [NOT GENERATED FUNCTION] Pango.attr_underline_color_new because of Pango.Attribute [NOT GENERATED FUNCTION] Pango.attr_underline_new because of Pango.Underline and Pango.Attribute [NOT GENERATED FUNCTION] Pango.attr_variant_new because of Pango.Variant and Pango.Attribute [NOT GENERATED FUNCTION] Pango.attr_weight_new because of Pango.Weight and Pango.Attribute [NOT GENERATED FUNCTION] Pango.attr_word_new because of Pango.Attribute [NOT GENERATED FUNCTION] Pango.break (deprecated in 1.44) because of Pango.Analysis and Pango.LogAttr [NOT GENERATED FUNCTION] Pango.default_break because of Pango.Analysis and Pango.LogAttr [NOT GENERATED FUNCTION] Pango.extents_to_pixels because of Pango.Rectangle and Pango.Rectangle [NOT GENERATED FUNCTION] Pango.find_base_dir because of Pango.Direction [NOT GENERATED FUNCTION] Pango.get_log_attrs because of Pango.Language and Pango.LogAttr [NOT GENERATED FUNCTION] Pango.itemize because of Pango.Context, Pango.AttrList, Pango.AttrIterator and Pango.Item [NOT GENERATED FUNCTION] Pango.itemize_with_base_dir because of Pango.Context, Pango.Direction, Pango.AttrList, Pango.AttrIterator and Pango.Item [NOT GENERATED FUNCTION] Pango.log2vis_get_embedding_levels because of Pango.Direction [NOT GENERATED FUNCTION] Pango.markup_parser_finish because of GLib.MarkupParseContext, Pango.AttrList and GLib.Error [NOT GENERATED FUNCTION] Pango.markup_parser_new because of GLib.MarkupParseContext [NOT GENERATED FUNCTION] Pango.parse_markup because of Pango.AttrList and GLib.Error [NOT GENERATED FUNCTION] Pango.parse_stretch because of Pango.Stretch [NOT GENERATED FUNCTION] Pango.parse_style because of Pango.Style [NOT GENERATED FUNCTION] Pango.parse_variant because of Pango.Variant [NOT GENERATED FUNCTION] Pango.parse_weight because of Pango.Weight [NOT GENERATED FUNCTION] Pango.read_line (deprecated in 1.38) because of GLib.String [NOT GENERATED FUNCTION] Pango.reorder_items because of Pango.Item and Pango.Item [NOT GENERATED FUNCTION] Pango.scan_string (deprecated in 1.38) because of GLib.String [NOT GENERATED FUNCTION] Pango.scan_word (deprecated in 1.38) because of GLib.String [NOT GENERATED FUNCTION] Pango.shape because of Pango.Analysis and Pango.GlyphString [NOT GENERATED FUNCTION] Pango.shape_full because of Pango.Analysis and Pango.GlyphString [NOT GENERATED FUNCTION] Pango.shape_item because of Pango.Item, Pango.LogAttr, Pango.GlyphString and Pango.ShapeFlags [NOT GENERATED FUNCTION] Pango.shape_with_flags because of Pango.Analysis, Pango.GlyphString and Pango.ShapeFlags [NOT GENERATED FUNCTION] Pango.tailor_break because of Pango.Analysis and Pango.LogAttr [NOT GENERATED FUNCTION] Pango.unichar_direction because of Pango.Direction ``` We now have the list of all the not-yet generated items. Quite convenient. There can be different kinds of not generated items: * `[NOT GENERATED]`: Objects marked with `[NOT GENERATED]` are objects that we can generate, but we did not (yet) add to the `generate` array. * `[NOT GENERATED PARENT]`: These objects live in a dependency of the current library. These are the objects we will add to the `manual` array in the following steps. * `[NOT GENERATED FUNCTION]`: These are global functions that were not generated. To fix it, we just add `"NameOfYourLibrary.*"` to the `generate` array in the Git.toml and add the following line to your src/lib.rs file: ```rust pub mod functions { pub use super::auto::functions::*; } ``` ## Generating the code In order to generate the code for the safe wrapper, we follow these steps until all objects have been generated: - Run `gir -o . -m not_bound` to see which objects have not been generated yet - Pick one of the types marked with `[NOT GENERATED]` - Add it to the `generate` array in the Gir.toml file - Run `gir -o .` to generate the code - Open the generated files under src/auto and have a look at them - Search for `/*Ignored*/`. If the type name following `/*Ignored*/` is prepended by `[crate_name]::` (e.g `Ignored*/&glib::MarkupParseContext`), - then we add it to the `manual` array. By doing so we tell [gir] that those types have been generated somewhere else and that they can be used just like the other types. - Otherwise, the type comes from the current crate and we just put it into the `generate` list of the `Gir.toml` file. - Start with the first step again The names of the objects are not the same as the crates names. You have to use the names of the corresponding gir files. Okay, let's go through that process for a few objects of our example. 🚧 TODO: Add remaining steps of the pango example 🚧 Again, if you do it on another library and it fails and you can't figure out why, don't hesitate to [contact us](https://gtk-rs.org/contact)! At this point, you should have almost everything you need. Let's have a look at errors that can happen in this process. [gir]: https://github.com/gtk-rs/gir gir-0.20.5/book/src/tutorial/introduction.md000066400000000000000000000014731475434152100210210ustar00rootroot00000000000000# Tutorial In this tutorial, we go through the basic steps needed to generate a safe wrapper for a simple C library. We start with finding the .gir files needed and end with generating the documentation. We also look at a few common errors and how to fix them. Because this can be a bit abstract sometimes, we use the creation of the bindings for [Pango](https://docs.gtk.org/Pango/) as an example. The example continues through all of the chapters of the tutorial. If you follow along until the end, you will have generated a safe wrapper including the documentation. In case you are stuck at any point or there are other errors and you can't figure out what's going on, don't hesitate to reach us so we can give you a hand! Let's dive right into it and let's set up our project folder! [gir]: https://github.com/gtk-rs/gir gir-0.20.5/book/src/tutorial/preparation.md000066400000000000000000000044201475434152100206170ustar00rootroot00000000000000# Preparation In order to install gir and nicely structure the project, there are a few things to set up. ## Set up the project folder In order to keep the project folder nicely organized, let's create a folder where we will work in and initialize the repo. We will create two library crates. pango will contain the safe wrapper crate and because it is a wrapper for the unsafe bindings, we create the pango-sys crate within the pango crate. If no Cargo.toml file is present in the sys create, a new one will be generated, so let's be safe and delete the automatically created file before we begin. The following commands will set up the project folder as described. ```sh mkdir gir-tutorial cd gir-tutorial/ git init cargo new pango --lib cd pango cargo new pango-sys --lib rm pango-sys/Cargo.toml ``` We will also create a file called "Gir.toml" in each of the crates. ```sh touch Gir.toml touch pango-sys/Gir.toml cd .. ``` ## Installing gir Of course we also need to download and install [gir]. ```sh git submodule add https://github.com/gtk-rs/gir git config -f .gitmodules submodule.gir.update none git submodule set-branch --branch main -- ./gir cd gir cargo install --path . cd .. ``` By adding it as a submodule, we are able to fetch future updates of the tool and we always exactly know which gir version we used to generate our bindings. We also change the setting so that the submodule is not automatically checked out, otherwise anyone using your library from git will have the useless submodule checked out. Run `git submodule update --checkout` if you want to update the submodule. Then we set the branch of the submodule to main. If there are any updates to gir in the future, we can install them by opening our project folder `gir-tutorial` and running ```sh git submodule update --remote cd gir cargo install --path . cd .. ``` ## Summary You should now have a folder looking like this: ```text gir | |---- ... pango/ | |---- Cargo.toml |---- Gir.toml |---- pango-sys/ | | | |---- Gir.toml | |---- src/ | | | |---- lib.rs |---- src/ | |---- lib.rs .git | |---- ... .gitmodules ``` Now that we installed gir and prepared our project folder, let's get the .gir files. [gir]: https://github.com/gtk-rs/gir gir-0.20.5/book/src/tutorial/sys_library.md000066400000000000000000000121041475434152100206330ustar00rootroot00000000000000# Generating the FFI library We have installed [gir], set up our repo and have all the `.gir` files we need. Now let's work on the unsafe bindings of the -sys crate. Let's change into the directory of the sys crate. ```sh cd pango/pango-sys ``` ## The Gir.toml file The first step is to let [gir] know what to generate. This is what the `Gir.toml` file that we created inside the `pango-sys` folder is for. This file will not be replaced when we run gir again. The file is currently empty so let's add the following to it: ```toml [options] library = "Pango" version = "1.0" min_cfg_version = "1.0" target_path = "." girs_directories = ["../../gir-files/"] work_mode = "sys" single_version_file = true ``` * `library` stands for the name of the library we want to generate. * `version` stands for the version of the library to be used. * `min_cfg_version` will be the minimum version supported by the generated bindings. * `target_path` stands for the location where the files will be generated. * `girs_directories` stands for the location of the `.gir` files. * `work_mode` stands for the mode gir is using. The options here are `sys` and `normal`. * `single_version_file` is a very useful option when you have a lot of generated files (like we'll have). Instead of generating the gir hash commit used for the generation in the header of all generated files, it'll just write it inside one file, removing `git diff` noise **a lot**. You can find out the values for `library` and `version` by looking at the name of the .gir file of your library. In our case it is called Pango-1.0.gir. This tells us that the `library` is Pango and the `version` is 1.0. If you don't know what value to use for `min_cfg_version`, use the same as you use for `version`. If not all needed `.gir` files reside in `../../gir-files/`, you can add the path to the other files by changing `girs_directories`. If for example you also have `.gir` files in the root of your project folder, change it to `girs_directories = ["../../gir-files/", "../.."]`. Because we are generating the unsafe bindings, we use the `sys` work mode. Let's generate the `sys` crate now: ```sh gir -o . ``` You should now see new files and a new folder. * `build.rs` * `Cargo.toml` * `Gir.toml` * `src/lib.rs` * `tests/` Now let's try to build it: ```sh cargo build ``` Surprise. It doesn't build at all and you should see a lot of errors. Well, that was expected. We need to add some dependencies in order to make it work. Have a look at the errors of the compiler to find out which are missing. In our example, the compiler throws the following errors: ```rust use of undeclared crate or module `glib` use of undeclared crate or module `gobject` ``` The dependencies need to be added to the `external_libraries` part. The names of the dependencies are the same as in the .gir files and not what the packages to install the libraries might be called. The compiler told us that the `GLib` and the `GObject` dependencies are missing. Let's update our `Gir.toml` file to fix it: ```toml [options] library = "Pango" version = "1.0" min_cfg_version = "1.0" target_path = "." girs_directories = ["../../gir-files/"] work_mode = "sys" external_libraries = [ "GLib", "GObject", ] ``` If one of your .gir files changed or you want to use an updated version of gir to generate the code, there is no need to delete the Cargo.toml or the Cargo.lock files before you regenerate the code. Because we made some changes to the Gir.toml file, we have to run gir again. Changing the content of the external_libraries array means that additional dependencies have to be added. gir does this automatically for you, but only if there is no Cargo.toml and no Cargo.lock file present. Just remove the `Cargo.*` files and run gir again and the additional dependencies will be added. If you made any manual changes to the file, you would have to do these changes again. After regenerating the code, we build the crate to see if the errors are gone. ```sh rm Cargo.* gir -o . cargo build ``` When executing the above commands, there should not be any errors and everything should work fine. Just to be sure everything was correctly generated, we can run some tests (graciously generated by [gir] as well): ```sh cargo test ``` Normally, all tests passed. If you get an error when running those tests, it's very likely that the `sys` generation is invalid and/or incomplete. ## The Gir.toml file This file was automatically generated but it will not be replaced when we run gir again. Make sure to look at your Cargo.toml to optionally add more information to it. If there are any `[features]`, you should try building and testing with these features activated as well. If you'd like, you can also set the default features. This can be useful for example if you want to always activate the newest version unless the user of the crate specifies an older version. For our example, we now have a working `sys` crate containing all functions and objects definition. We are done here and can go back to the folder of the wrapper crate: ```sh cd .. ``` Time to generate the high-level Rust API! [gir]: https://github.com/gtk-rs/gir gir-0.20.5/build.rs000066400000000000000000000014261475434152100140350ustar00rootroot00000000000000use std::{fs::File, io::Write}; // Build.rs does not use all provided functions #[allow(dead_code)] #[path = "src/git.rs"] mod git; fn main() { let repo_path = git::git_dir(".").unwrap(); let head_path = repo_path.join("HEAD"); println!("cargo:rerun-if-changed={}", head_path.display()); let head = std::fs::read_to_string(&head_path).unwrap(); if let Some(ref_) = head.trim_end().strip_prefix("ref: ") { let ref_path = repo_path.join(ref_); assert!(ref_path.is_file()); println!("cargo:rerun-if-changed={}", ref_path.display()); } let ver = git::repo_hash(".").unwrap_or_else(|| "???".into()); File::create("src/gir_version.rs") .and_then(|mut f| writeln!(f, "pub const VERSION: &str = \"{ver}\";",)) .unwrap(); } gir-0.20.5/generator.py000077500000000000000000000171431475434152100147360ustar00rootroot00000000000000#!/usr/bin/env python3 from pathlib import Path import argparse import subprocess import sys import asyncio DEFAULT_GIR_FILES_DIRECTORY = Path("./gir-files") DEFAULT_GIR_DIRECTORY = Path("./gir/") # TODO: This is typically the directory _of this Python script_ (which external projects symlink to) def run_command(command, folder=None): return subprocess.run(command, cwd=folder, check=True) async def spawn_process(exe, args): p = await asyncio.create_subprocess_exec( str(exe), *(str(arg) for arg in args), stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) stdout, stderr = await p.communicate() stdout = stdout.decode("utf-8") stderr = stderr.decode("utf-8") assert p.returncode == 0, stderr.strip() return stdout, stderr async def spawn_gir(conf, args): if conf.gir_path: stdout, stderr = await spawn_process(conf.gir_path, args) else: cargo_args = ["run", "--release", "--quiet", "--manifest-path", DEFAULT_GIR_DIRECTORY / "Cargo.toml", "--"] stdout, stderr = await spawn_process("cargo", cargo_args + args) # Gir doesn't print anything to stdout. If it does, this is likely out of # order with stderr, unless the printer/logging flushes in between. assert not stdout, "`gir` printed unexpected stdout: {}".format(stdout) if stderr: return "===> stderr:\n\n" + stderr + "\n" return "" def ask_yes_no_question(question, conf): question = "{} [y/N] ".format(question) if conf.yes: print(question + "y") return True line = input(question) return line.strip().lower() == "y" def update_submodule(submodule_path, conf): if any(submodule_path.iterdir()): return False print("=> Initializing {} submodule...".format(submodule_path)) run_command(["git", "submodule", "update", "--init", submodule_path]) print("<= Done!") if ask_yes_no_question( "Do you want to update {} submodule?".format(submodule_path), conf ): print("=> Updating submodule...") run_command(["git", "reset", "--hard", "HEAD"], submodule_path) run_command(["git", "pull", "-f", "origin", "main"], submodule_path) print("<= Done!") return True return False async def regenerate_crate_docs(conf, crate_dir, base_gir_args): doc_path = "docs.md" # Generate into docs.md instead of the default vendor.md doc_args = base_gir_args + ["-m", "doc", "--doc-target-path", doc_path] # The above `gir -m doc` generates docs.md relative to the directory containing Gir.toml doc_path = crate_dir / doc_path embed_args = ["-m", "-d", crate_dir / "src"] logs = "" if conf.strip_docs: logs += "==> Stripping documentation from `{}`...\n".format(crate_dir) # -n dumps stripped docs to stdout _, stderr = await spawn_process("rustdoc-stripper", embed_args + ["-s", "-n"]) if stderr: logs += "===> stderr:\n\n" + stderr + "\n" if conf.embed_docs: logs += "==> Regenerating documentation for `{}` into `{}`...\n".format( crate_dir, doc_path ) logs += await spawn_gir(conf, doc_args) logs += "==> Embedding documentation from `{}` into `{}`...\n".format( doc_path, crate_dir ) stdout, stderr = await spawn_process( "rustdoc-stripper", embed_args + ["-g", "-o", doc_path] ) if stdout: logs += "===> stdout:\n\n" + stdout + "\n" if stderr: logs += "===> stderr:\n\n" + stderr + "\n" return logs def regen_crates(path, conf): processes = [] if path.is_dir(): for entry in path.rglob("Gir*.toml"): processes += regen_crates(entry, conf) elif path.match("Gir*.toml"): args = ["-c", path, "-o", path.parent] + [ d for path in conf.gir_files_paths for d in ("-d", path) ] is_sys_crate = path.parent.name.endswith("sys") if conf.embed_docs or conf.strip_docs: # Embedding documentation only applies to non-sys crates if is_sys_crate: return processes processes.append(regenerate_crate_docs(conf, path.parent, args)) else: if is_sys_crate: args.extend(["-m", "sys"]) async def regenerate_crate(path, args): return "==> Regenerating `{}`...\n".format(path) + await spawn_gir( conf, args ) processes.append(regenerate_crate(path, args)) else: raise Exception("`{}` is not a valid Gir*.toml file".format(path)) return processes def valid_path(path): path = Path(path) if not path.exists(): raise argparse.ArgumentTypeError("`{}` no such file or directory".format(path)) return path def directory_path(path): path = Path(path) if not path.is_dir(): raise argparse.ArgumentTypeError("`{}` directory not found".format(path)) return path def file_path(path): path = Path(path) if not path.is_file(): raise argparse.ArgumentTypeError("`{}` file not found".format(path)) return path def parse_args(): parser = argparse.ArgumentParser( description="Helper to regenerate gtk-rs crates using gir.", formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) parser.add_argument( "path", nargs="*", default=[Path(".")], type=valid_path, help="Paths in which to look for Gir.toml files", ) parser.add_argument( "--gir-files-directories", nargs="+", # If the option is used, we expect at least one folder! dest="gir_files_paths", default=[], type=directory_path, help="Path of the gir-files folder", ) parser.add_argument( "--gir-path", type=file_path, help="Path of the gir executable to run", ) parser.add_argument( "--yes", action="store_true", help=" Always answer `yes` to any question asked by the script", ) parser.add_argument( "--no-fmt", action="store_true", help="If set, this script will not run `cargo fmt`", ) parser.add_argument( "--embed-docs", action="store_true", help="Build documentation with `gir -m doc`, and embed it with `rustdoc-stripper -g`", ) parser.add_argument( "--strip-docs", action="store_true", help="Remove documentation with `rustdoc-stripper -s -n`. Can be used in conjunction with --embed-docs", ) return parser.parse_args() async def main(): conf = parse_args() if not conf.gir_files_paths: update_submodule(DEFAULT_GIR_FILES_DIRECTORY, conf) if conf.gir_path is None: update_submodule(DEFAULT_GIR_DIRECTORY, conf) print("=> Building gir...") run_command(["cargo", "build", "--release", "--manifest-path", DEFAULT_GIR_DIRECTORY / "Cargo.toml"]) print("<= Done!") print("=> Regenerating crates...") for path in conf.path: print("=> Looking in path `{}`".format(path)) # Collect and print the results as soon as they trickle in, one process at a time: for coro in asyncio.as_completed(regen_crates(path, conf)): print(await coro, end="") if not conf.no_fmt and not run_command(["cargo", "fmt"]): return 1 print("<= Done!") print("Don't forget to check if everything has been correctly generated!") return 0 if __name__ == "__main__": try: asyncio.run(main()) except Exception as e: print("Error: {}".format(e), file=sys.stderr) sys.exit(1) gir-0.20.5/rustfmt.toml000066400000000000000000000000211475434152100147570ustar00rootroot00000000000000edition = "2021" gir-0.20.5/src/000077500000000000000000000000001475434152100131545ustar00rootroot00000000000000gir-0.20.5/src/analysis/000077500000000000000000000000001475434152100147775ustar00rootroot00000000000000gir-0.20.5/src/analysis/bounds.rs000066400000000000000000000406231475434152100166440ustar00rootroot00000000000000use std::{collections::vec_deque::VecDeque, slice::Iter}; use crate::{ analysis::{ function_parameters::CParameter, functions::{find_function, find_index_to_ignore, finish_function_name}, imports::Imports, out_parameters::use_function_return_for_result, ref_mode::RefMode, rust_type::RustType, }, config, consts::TYPE_PARAMETERS_START, env::Env, library::{Basic, Class, Concurrency, Function, ParameterDirection, Type, TypeId}, traits::IntoString, }; #[derive(Clone, Eq, Debug, PartialEq)] pub enum BoundType { NoWrapper, // lifetime IsA(Option), // lifetime <- shouldn't be used but just in case... AsRef(Option), } impl BoundType { pub fn need_isa(&self) -> bool { matches!(*self, Self::IsA(_)) } // TODO: This is just a heuristic for now, based on what we do in codegen! // Theoretically the surrounding function should determine whether it needs to // reuse an alias (ie. to use in `call_func::`) or not. // In the latter case an `impl` is generated instead of a type name/alias. pub fn has_alias(&self) -> bool { matches!(*self, Self::NoWrapper) } } #[derive(Clone, Eq, Debug, PartialEq)] pub struct Bound { pub bound_type: BoundType, pub parameter_name: String, /// Bound does not have an alias when `param: impl type_str` is used pub alias: Option, pub type_str: String, pub callback_modified: bool, } #[derive(Clone, Debug)] pub struct Bounds { unused: VecDeque, used: Vec, lifetimes: Vec, } impl Default for Bounds { fn default() -> Bounds { Bounds { unused: (TYPE_PARAMETERS_START..) .take_while(|x| *x <= 'Z') .collect(), used: Vec::new(), lifetimes: Vec::new(), } } } #[derive(Debug, Clone)] pub struct CallbackInfo { pub callback_type: String, pub success_parameters: String, pub error_parameters: Option, pub bound_name: char, } impl Bounds { pub fn add_for_parameter( &mut self, env: &Env, func: &Function, par: &CParameter, r#async: bool, concurrency: Concurrency, configured_functions: &[&config::functions::Function], ) -> (Option, Option) { let type_name = RustType::builder(env, par.typ) .ref_mode(RefMode::ByRefFake) .try_build(); if type_name.is_err() { return (None, None); } let mut type_string = type_name.into_string(); let mut callback_info = None; let mut ret = None; let mut need_is_into_check = false; if !par.instance_parameter && par.direction != ParameterDirection::Out { if let Some(bound_type) = Bounds::type_for(env, par.typ) { ret = Some(Bounds::get_to_glib_extra( &bound_type, *par.nullable, par.instance_parameter, par.move_, )); if r#async && (par.name == "callback" || par.name.ends_with("_callback")) { let func_name = func.c_identifier.as_ref().unwrap(); let finish_func_name = if let Some(finish_func_name) = &func.finish_func { finish_func_name.to_string() } else { finish_function_name(func_name) }; if let Some(function) = find_function(env, &finish_func_name) { // FIXME: This should work completely based on the analysis of the finish() // function but that a) happens afterwards and b) is // not accessible from here either. let mut out_parameters = find_out_parameters(env, function, configured_functions); if use_function_return_for_result( env, function.ret.typ, &func.name, configured_functions, ) { let nullable = configured_functions .iter() .find_map(|f| f.ret.nullable) .unwrap_or(function.ret.nullable); out_parameters.insert( 0, RustType::builder(env, function.ret.typ) .direction(function.ret.direction) .nullable(nullable) .try_build() .into_string(), ); } let parameters = format_out_parameters(&out_parameters); let error_type = find_error_type(env, function); if let Some(ref error) = error_type { type_string = format!("FnOnce(Result<{parameters}, {error}>) + 'static"); } else { type_string = format!("FnOnce({parameters}) + 'static"); } let bound_name = *self.unused.front().unwrap(); callback_info = Some(CallbackInfo { callback_type: type_string.clone(), success_parameters: parameters, error_parameters: error_type, bound_name, }); } } else if par.c_type == "GDestroyNotify" || env.library.type_(par.typ).is_function() { need_is_into_check = par.c_type != "GDestroyNotify"; if let Type::Function(_) = env.library.type_(par.typ) { let callback_parameters_config = configured_functions.iter().find_map(|f| { f.parameters .iter() .find(|p| p.ident.is_match(&par.name)) .map(|p| &p.callback_parameters) }); let mut rust_ty = RustType::builder(env, par.typ) .direction(par.direction) .scope(par.scope) .concurrency(concurrency); if let Some(callback_parameters_config) = callback_parameters_config { rust_ty = rust_ty.callback_parameters_config(callback_parameters_config); } type_string = rust_ty .try_from_glib(&par.try_from_glib) .try_build() .into_string(); let bound_name = *self.unused.front().unwrap(); callback_info = Some(CallbackInfo { callback_type: type_string.clone(), success_parameters: String::new(), error_parameters: None, bound_name, }); } } if (!need_is_into_check || !*par.nullable) && par.c_type != "GDestroyNotify" { self.add_parameter(&par.name, &type_string, bound_type, r#async); } } } else if par.instance_parameter { if let Some(bound_type) = Bounds::type_for(env, par.typ) { ret = Some(Bounds::get_to_glib_extra( &bound_type, *par.nullable, true, par.move_, )); } } (ret, callback_info) } pub fn type_for(env: &Env, type_id: TypeId) -> Option { use self::BoundType::*; match env.library.type_(type_id) { Type::Basic(Basic::Filename | Basic::OsString) => Some(AsRef(None)), Type::Class(Class { is_fundamental: true, .. }) => Some(AsRef(None)), Type::Class(Class { final_type: true, .. }) => None, Type::Class(Class { final_type: false, .. }) => Some(IsA(None)), Type::Interface(..) => Some(IsA(None)), Type::List(_) | Type::SList(_) | Type::CArray(_) => None, Type::Function(_) => Some(NoWrapper), _ => None, } } pub fn get_to_glib_extra( bound_type: &BoundType, nullable: bool, instance_parameter: bool, move_: bool, ) -> String { use self::BoundType::*; match bound_type { AsRef(_) if move_ && nullable => ".map(|p| p.as_ref().clone().upcast())".to_owned(), AsRef(_) if nullable => ".as_ref().map(|p| p.as_ref())".to_owned(), AsRef(_) if move_ => ".upcast()".to_owned(), AsRef(_) => ".as_ref()".to_owned(), IsA(_) if move_ && nullable => ".map(|p| p.upcast())".to_owned(), IsA(_) if nullable && !instance_parameter => ".map(|p| p.as_ref())".to_owned(), IsA(_) if move_ => ".upcast()".to_owned(), IsA(_) => ".as_ref()".to_owned(), _ => String::new(), } } pub fn add_parameter( &mut self, name: &str, type_str: &str, mut bound_type: BoundType, r#async: bool, ) { if r#async && name == "callback" { bound_type = BoundType::NoWrapper; } if self.used.iter().any(|n| n.parameter_name == name) { return; } let alias = bound_type .has_alias() .then(|| self.unused.pop_front().expect("No free type aliases!")); self.used.push(Bound { bound_type, parameter_name: name.to_owned(), alias, type_str: type_str.to_owned(), callback_modified: false, }); } pub fn get_parameter_bound(&self, name: &str) -> Option<&Bound> { self.iter().find(move |n| n.parameter_name == name) } pub fn update_imports(&self, imports: &mut Imports) { // TODO: import with versions use self::BoundType::*; for used in &self.used { match used.bound_type { NoWrapper => (), IsA(_) => imports.add("glib::prelude::*"), AsRef(_) => imports.add_used_type(&used.type_str), } } } pub fn is_empty(&self) -> bool { self.used.is_empty() } pub fn iter(&self) -> Iter<'_, Bound> { self.used.iter() } pub fn iter_lifetimes(&self) -> Iter<'_, char> { self.lifetimes.iter() } } #[derive(Clone, Debug)] pub struct PropertyBound { pub alias: char, pub type_str: String, } impl PropertyBound { pub fn get(env: &Env, type_id: TypeId) -> Option { let type_ = env.type_(type_id); if type_.is_final_type() { return None; } Some(Self { alias: TYPE_PARAMETERS_START, type_str: RustType::builder(env, type_id) .ref_mode(RefMode::ByRefFake) .try_build() .into_string(), }) } } fn find_out_parameters( env: &Env, function: &Function, configured_functions: &[&config::functions::Function], ) -> Vec { let index_to_ignore = find_index_to_ignore(&function.parameters, Some(&function.ret)); function .parameters .iter() .enumerate() .filter(|&(index, param)| { Some(index) != index_to_ignore && param.direction == ParameterDirection::Out && param.name != "error" }) .map(|(_, param)| { // FIXME: This should work completely based on the analysis of the finish() // function but that a) happens afterwards and b) is not accessible // from here either. let nullable = configured_functions .iter() .find_map(|f| { f.parameters .iter() .filter(|p| p.ident.is_match(¶m.name)) .find_map(|p| p.nullable) }) .unwrap_or(param.nullable); RustType::builder(env, param.typ) .direction(param.direction) .nullable(nullable) .try_build() .into_string() }) .collect() } fn format_out_parameters(parameters: &[String]) -> String { if parameters.len() == 1 { parameters[0].to_string() } else { format!("({})", parameters.join(", ")) } } fn find_error_type(env: &Env, function: &Function) -> Option { let error_param = function .parameters .iter() .find(|param| param.direction.is_out() && param.is_error)?; if let Type::Record(_) = env.type_(error_param.typ) { Some( RustType::builder(env, error_param.typ) .direction(error_param.direction) .try_build() .into_string(), ) } else { None } } #[cfg(test)] mod tests { use super::*; #[test] fn get_new_all() { let mut bounds: Bounds = Default::default(); let typ = BoundType::IsA(None); bounds.add_parameter("a", "", typ.clone(), false); assert_eq!(bounds.iter().len(), 1); // Don't add second time bounds.add_parameter("a", "", typ.clone(), false); assert_eq!(bounds.iter().len(), 1); bounds.add_parameter("b", "", typ.clone(), false); bounds.add_parameter("c", "", typ.clone(), false); bounds.add_parameter("d", "", typ.clone(), false); bounds.add_parameter("e", "", typ.clone(), false); bounds.add_parameter("f", "", typ.clone(), false); bounds.add_parameter("g", "", typ.clone(), false); bounds.add_parameter("h", "", typ.clone(), false); assert_eq!(bounds.iter().len(), 8); bounds.add_parameter("h", "", typ.clone(), false); assert_eq!(bounds.iter().len(), 8); bounds.add_parameter("i", "", typ.clone(), false); bounds.add_parameter("j", "", typ.clone(), false); bounds.add_parameter("k", "", typ, false); } #[test] #[should_panic(expected = "No free type aliases!")] fn exhaust_type_parameters() { let mut bounds: Bounds = Default::default(); let typ = BoundType::NoWrapper; for c in 'a'..='l' { // Should panic on `l` because all type parameters are exhausted bounds.add_parameter(c.to_string().as_str(), "", typ.clone(), false); } } #[test] fn get_parameter_bound() { let mut bounds: Bounds = Default::default(); let typ = BoundType::NoWrapper; bounds.add_parameter("a", "", typ.clone(), false); bounds.add_parameter("b", "", typ.clone(), false); let bound = bounds.get_parameter_bound("a").unwrap(); // `NoWrapper `bounds are expected to have an alias: assert_eq!(bound.alias, Some('P')); assert_eq!(bound.bound_type, typ); let bound = bounds.get_parameter_bound("b").unwrap(); assert_eq!(bound.alias, Some('Q')); assert_eq!(bound.bound_type, typ); assert_eq!(bounds.get_parameter_bound("c"), None); } #[test] fn impl_bound() { let mut bounds: Bounds = Default::default(); let typ = BoundType::IsA(None); bounds.add_parameter("a", "", typ.clone(), false); bounds.add_parameter("b", "", typ.clone(), false); let bound = bounds.get_parameter_bound("a").unwrap(); // `IsA` is simplified to an inline `foo: impl IsA` and // lacks an alias/type-parameter: assert_eq!(bound.alias, None); assert_eq!(bound.bound_type, typ); let typ = BoundType::AsRef(None); bounds.add_parameter("c", "", typ.clone(), false); let bound = bounds.get_parameter_bound("c").unwrap(); // Same `impl AsRef` simplification as `IsA`: assert_eq!(bound.alias, None); assert_eq!(bound.bound_type, typ); } } gir-0.20.5/src/analysis/c_type.rs000066400000000000000000000046241475434152100166360ustar00rootroot00000000000000use log::trace; use crate::{env::Env, library::TypeId}; pub fn rustify_pointers(c_type: &str) -> (String, String) { let mut input = c_type.trim(); let leading_const = input.starts_with("const "); if leading_const { input = &input[6..]; } let end = [ input.find(" const"), input.find("*const"), input.find('*'), Some(input.len()), ] .iter() .filter_map(|&x| x) .min() .unwrap(); let inner = input[..end].trim().into(); let mut ptrs: Vec<_> = input[end..] .rsplit('*') .skip(1) .map(|s| { if s.contains("const") { "*const" } else { "*mut" } }) .collect(); if let (true, Some(p)) = (leading_const, ptrs.last_mut()) { *p = "*const"; } let res = (ptrs.join(" "), inner); trace!("rustify `{}` -> `{}` `{}`", c_type, res.0, res.1); res } pub fn is_mut_ptr(c_type: &str) -> bool { let (ptr, _inner) = rustify_pointers(c_type); ptr.find("*mut") == Some(0) } pub fn implements_c_type(env: &Env, tid: TypeId, c_type: &str) -> bool { env.class_hierarchy .supertypes(tid) .iter() .any(|&super_tid| env.library.type_(super_tid).get_glib_name() == Some(c_type)) } #[cfg(test)] mod tests { use super::rustify_pointers as rustify_ptr; fn s(x: &str, y: &str) -> (String, String) { (x.into(), y.into()) } #[test] fn rustify_pointers() { assert_eq!(rustify_ptr("char"), s("", "char")); assert_eq!(rustify_ptr("char*"), s("*mut", "char")); assert_eq!(rustify_ptr("const char*"), s("*const", "char")); assert_eq!(rustify_ptr("char const*"), s("*const", "char")); assert_eq!(rustify_ptr("char const *"), s("*const", "char")); assert_eq!(rustify_ptr(" char * * "), s("*mut *mut", "char")); assert_eq!(rustify_ptr("const char**"), s("*mut *const", "char")); assert_eq!(rustify_ptr("char const**"), s("*mut *const", "char")); assert_eq!( rustify_ptr("const char* const*"), s("*const *const", "char") ); assert_eq!( rustify_ptr("char const * const *"), s("*const *const", "char") ); assert_eq!(rustify_ptr("char* const*"), s("*const *mut", "char")); assert_eq!(rustify_ptr("GtkWidget*"), s("*mut", "GtkWidget")); } } gir-0.20.5/src/analysis/child_properties.rs000066400000000000000000000135141475434152100207100ustar00rootroot00000000000000use log::error; use crate::{ analysis::{ bounds::{Bound, Bounds}, imports::Imports, ref_mode::RefMode, rust_type::RustType, }, config, consts::TYPE_PARAMETERS_START, env::Env, library::{self, ParameterDirection}, nameutil, traits::*, }; #[derive(Clone, Debug)] pub struct ChildProperty { pub name: String, pub prop_name: String, pub getter_name: String, pub typ: library::TypeId, pub child_name: String, pub child_type: Option, pub nullable: library::Nullable, pub get_out_ref_mode: RefMode, pub set_in_ref_mode: RefMode, pub doc_hidden: bool, pub set_params: String, pub bounds: String, pub to_glib_extra: String, } pub type ChildProperties = Vec; pub fn analyze( env: &Env, config: Option<&config::ChildProperties>, type_tid: library::TypeId, imports: &mut Imports, ) -> ChildProperties { let mut properties = Vec::new(); if config.is_none() { return properties; } let config = config.unwrap(); let child_name = config.child_name.as_ref().map_or("child", |s| s.as_str()); let child_type = config .child_type .as_ref() .and_then(|name| env.library.find_type(0, name)); if config.child_type.is_some() && child_type.is_none() { let owner_name = RustType::try_new(env, type_tid).into_string(); let child_type: &str = config.child_type.as_ref().unwrap(); error!("Bad child type `{}` for `{}`", child_type, owner_name); return properties; } for prop in &config.properties { if let Some(prop) = analyze_property(env, prop, child_name, child_type, type_tid, config, imports) { properties.push(prop); } } if !properties.is_empty() { imports.add("glib::prelude::*"); if let Some(rust_type) = child_type.and_then(|typ| RustType::try_new(env, typ).ok()) { imports.add_used_types(rust_type.used_types()); } } properties } fn analyze_property( env: &Env, prop: &config::ChildProperty, child_name: &str, child_type: Option, type_tid: library::TypeId, config: &config::ChildProperties, imports: &mut Imports, ) -> Option { let name = prop.name.clone(); let prop_name = nameutil::signal_to_snake(&prop.name); let getter_rename = config .properties .iter() .find(|cp| cp.name == name) .and_then(|cp| cp.rename_getter.clone()); let is_getter_renamed = getter_rename.is_some(); let mut getter_name = getter_rename.unwrap_or_else(|| prop_name.clone()); if let Some(typ) = env.library.find_type(0, &prop.type_name) { let doc_hidden = prop.doc_hidden; imports.add("glib::prelude::*"); if let Ok(rust_type) = RustType::try_new(env, typ) { imports.add_used_types(rust_type.used_types()); } let get_out_ref_mode = RefMode::of(env, typ, library::ParameterDirection::Return); if !is_getter_renamed { if let Ok(new_name) = getter_rules::try_rename_getter_suffix( &getter_name, typ == library::TypeId::tid_bool(), ) { getter_name = new_name.unwrap(); } } let mut set_in_ref_mode = RefMode::of(env, typ, library::ParameterDirection::In); if set_in_ref_mode == RefMode::ByRefMut { set_in_ref_mode = RefMode::ByRef; } let nullable = library::Nullable(set_in_ref_mode.is_ref()); let mut bounds_str = String::new(); let dir = ParameterDirection::In; let set_params = if let Some(bound) = Bounds::type_for(env, typ) { let r_type = RustType::builder(env, typ) .ref_mode(RefMode::ByRefFake) .try_build() .into_string(); let _bound = Bound { bound_type: bound, parameter_name: TYPE_PARAMETERS_START.to_string(), alias: Some(TYPE_PARAMETERS_START.to_owned()), type_str: r_type, callback_modified: false, }; // TODO: bounds_str push?!?! bounds_str.push_str("TODO"); format!("{prop_name}: {TYPE_PARAMETERS_START}") // let mut bounds = Bounds::default(); // bounds.add_parameter("P", &r_type, bound, false); // let (s_bounds, _) = function::bounds(&bounds, &[], false); // // Because the bounds won't necessarily be added into the final // function, we // only keep the "inner" part to make // the string computation easier. So // `` becomes // `T: X`. bounds_str.push_str(&s_bounds[1..s_bounds. // len() - 1]); format!("{}: {}", prop_name, // bounds.iter().last().unwrap().alias) } else { format!( "{}: {}", prop_name, RustType::builder(env, typ) .direction(dir) .nullable(nullable) .ref_mode(set_in_ref_mode) .try_build_param() .into_string() ) }; Some(ChildProperty { name, prop_name, getter_name, typ, child_name: child_name.to_owned(), child_type, nullable, get_out_ref_mode, set_in_ref_mode, doc_hidden, set_params, bounds: bounds_str, to_glib_extra: String::new(), }) } else { let owner_name = RustType::try_new(env, type_tid).into_string(); error!( "Bad type `{}` of child property `{}` for `{}`", &prop.type_name, name, owner_name ); None } } gir-0.20.5/src/analysis/class_builder.rs000066400000000000000000000076741475434152100201760ustar00rootroot00000000000000use std::collections::HashSet; use crate::{ analysis::{ bounds::Bounds, imports::Imports, properties::{get_property_ref_modes, Property}, rust_type::RustType, }, config::{self, GObject}, env::Env, library, traits::*, }; pub fn analyze( env: &Env, props: &[library::Property], type_tid: library::TypeId, obj: &GObject, imports: &mut Imports, ) -> Vec<(Vec, library::TypeId)> { if !obj.generate_builder { return Vec::new(); } let mut names = HashSet::::new(); let mut builder_properties = vec![( analyze_properties(env, type_tid, props, obj, imports, &mut names), type_tid, )]; for &super_tid in env.class_hierarchy.supertypes(type_tid) { let type_ = env.type_(super_tid); let super_properties = match type_ { library::Type::Class(class) => &class.properties, library::Type::Interface(iface) => &iface.properties, _ => continue, }; let super_obj = if let Some(super_obj) = env.config.objects.get(&super_tid.full_name(&env.library)) { super_obj } else { continue; }; let new_builder_properties = ( analyze_properties( env, super_tid, super_properties, super_obj, imports, &mut names, ), super_tid, ); builder_properties.push(new_builder_properties); } builder_properties } fn analyze_properties( env: &Env, type_tid: library::TypeId, props: &[library::Property], obj: &GObject, imports: &mut Imports, names: &mut HashSet, ) -> Vec { let mut builder_properties = Vec::new(); for prop in props { if names.contains(&prop.name) { continue; } let configured_properties = obj.properties.matched(&prop.name); if !configured_properties .iter() .all(|f| f.status.need_generate()) { continue; } if env.is_totally_deprecated(Some(type_tid.ns_id), prop.deprecated_version) { continue; } let builder = analyze_property(env, prop, &configured_properties, imports); if let Some(builder) = builder { builder_properties.push(builder); names.insert(prop.name.clone()); } } builder_properties } fn analyze_property( env: &Env, prop: &library::Property, configured_properties: &[&config::properties::Property], imports: &mut Imports, ) -> Option { let prop_version = configured_properties .iter() .filter_map(|f| f.version) .min() .or(prop.version); let for_builder = prop.construct_only || prop.construct || prop.writable; if !for_builder { return None; } let imports = &mut imports.with_defaults(prop_version, &None); let rust_type_res = RustType::try_new(env, prop.typ); if let Ok(ref rust_type) = rust_type_res { if !rust_type.as_str().contains("GString") { imports.add_used_types(rust_type.used_types()); } } let (get_out_ref_mode, set_in_ref_mode, nullable) = get_property_ref_modes(env, prop); let mut bounds = Bounds::default(); if let Some(bound) = Bounds::type_for(env, prop.typ) { imports.add("glib::prelude::*"); bounds.add_parameter(&prop.name, &rust_type_res.into_string(), bound, false); } Some(Property { name: prop.name.clone(), var_name: String::new(), typ: prop.typ, is_get: false, func_name: String::new(), func_name_alias: None, nullable, get_out_ref_mode, set_in_ref_mode, set_bound: None, bounds, version: prop_version, deprecated_version: prop.deprecated_version, }) } gir-0.20.5/src/analysis/class_hierarchy.rs000066400000000000000000000035431475434152100205150ustar00rootroot00000000000000use std::{ collections::{HashMap, HashSet}, iter, }; use crate::library::*; #[derive(Debug)] struct Node { supers: Vec, subs: HashSet, } #[derive(Debug)] pub struct Info { hier: HashMap, } pub fn run(library: &Library) -> Info { let mut hier = HashMap::new(); for (tid, _) in library.types() { get_node(library, &mut hier, tid); } Info { hier } } fn get_node<'a>( library: &Library, hier: &'a mut HashMap, tid: TypeId, ) -> Option<&'a mut Node> { if hier.contains_key(&tid) { return hier.get_mut(&tid); } let direct_supers: Vec = match library.type_(tid) { Type::Class(Class { parent, implements, .. }) => parent.iter().chain(implements.iter()).copied().collect(), Type::Interface(Interface { prerequisites, .. }) => prerequisites.clone(), _ => return None, }; let mut supers = Vec::new(); for super_ in direct_supers { let node = get_node(library, hier, super_).expect("parent must be a class or interface"); node.subs.insert(tid); for &tid in [super_].iter().chain(node.supers.iter()) { if !supers.contains(&tid) { supers.push(tid); } } } hier.insert( tid, Node { supers, subs: HashSet::new(), }, ); hier.get_mut(&tid) } impl Info { pub fn subtypes<'a>(&'a self, tid: TypeId) -> Box + 'a> { match self.hier.get(&tid) { Some(node) => Box::new(node.subs.iter().copied()), None => Box::new(iter::empty()), } } pub fn supertypes(&self, tid: TypeId) -> &[TypeId] { match self.hier.get(&tid) { Some(node) => &node.supers, None => &[], } } } gir-0.20.5/src/analysis/constants.rs000066400000000000000000000033471475434152100173700ustar00rootroot00000000000000use std::borrow::Borrow; use crate::{config, env::Env, library, nameutil, traits::*, version::Version}; #[derive(Debug)] pub struct Info { pub name: String, pub glib_name: String, pub typ: library::TypeId, pub version: Option, pub deprecated_version: Option, pub cfg_condition: Option, } pub fn analyze>( env: &Env, constants: &[F], obj: &config::gobjects::GObject, ) -> Vec { let mut consts = Vec::new(); for constant in constants { let constant = constant.borrow(); let configured_constants = obj.constants.matched(&constant.name); if !configured_constants .iter() .all(|c| c.status.need_generate()) { continue; } if env.is_totally_deprecated(None, constant.deprecated_version) { continue; } match env.type_(constant.typ) { library::Type::Basic(library::Basic::Utf8) => (), _ => continue, } let version = configured_constants .iter() .filter_map(|c| c.version) .min() .or(constant.version); let version = env.config.filter_version(version); let deprecated_version = constant.deprecated_version; let cfg_condition = configured_constants .iter() .find_map(|c| c.cfg_condition.clone()); let name = nameutil::mangle_keywords(&*constant.name).into_owned(); consts.push(Info { name, glib_name: constant.c_identifier.clone(), typ: constant.typ, version, deprecated_version, cfg_condition, }); } consts } gir-0.20.5/src/analysis/conversion_type.rs000066400000000000000000000075031475434152100206000ustar00rootroot00000000000000use std::sync::Arc; use crate::{env, library::*}; #[derive(Default, Clone, Debug, Eq, PartialEq)] pub enum ConversionType { /// Coded without conversion. Direct, /// Coded with from_glib. Scalar, /// Type implementing TryFromGlib. Option, /// Type implementing TryFromGlib where Err is neither GlibNoneError /// nor GlibNoneOrInvalidError. Embeds the Error type name. /// Defaults to the object's type for the `Ok` variant if `ok_type` is /// `None`. Result { ok_type: Arc, err_type: Arc, }, /// Coded with from_glib_xxx. Pointer, // Same as Pointer, except that use from_glib_borrow instead from_glib_none. Borrow, #[default] Unknown, } impl ConversionType { pub fn of(env: &env::Env, type_id: TypeId) -> Self { use crate::library::{Basic::*, Type::*}; let library = &env.library; if let Some(conversion_type) = env .config .objects .get(&type_id.full_name(library)) .and_then(|gobject| gobject.conversion_type.clone()) { return conversion_type; } match library.type_(type_id) { Basic(fund) => match fund { Boolean => Self::Scalar, Int8 => Self::Direct, UInt8 => Self::Direct, Int16 => Self::Direct, UInt16 => Self::Direct, Int32 => Self::Direct, UInt32 => Self::Direct, Int64 => Self::Direct, UInt64 => Self::Direct, Char => Self::Scalar, UChar => Self::Scalar, Short => Self::Direct, UShort => Self::Direct, Int => Self::Direct, UInt => Self::Direct, Long => Self::Direct, ULong => Self::Direct, Size => Self::Direct, SSize => Self::Direct, Float => Self::Direct, Double => Self::Direct, UniChar => Self::Scalar, Pointer => Self::Pointer, VarArgs => Self::Unknown, Utf8 => Self::Pointer, Filename => Self::Pointer, OsString => Self::Pointer, Type => Self::Scalar, TimeT => Self::Direct, OffT => Self::Direct, DevT => Self::Direct, GidT => Self::Direct, PidT => Self::Direct, SockLenT => Self::Direct, UidT => Self::Direct, None => Self::Unknown, IntPtr => Self::Direct, UIntPtr => Self::Direct, Bool => Self::Direct, Unsupported => Self::Unknown, }, Alias(alias) if alias.c_identifier == "GQuark" => Self::Scalar, Alias(alias) => Self::of(env, alias.typ), Bitfield(_) => Self::Scalar, Record(_) => Self::Pointer, Union(_) => Self::Pointer, Enumeration(_) => Self::Scalar, Interface(_) => Self::Pointer, Class(_) => Self::Pointer, CArray(_) => Self::Pointer, FixedArray(..) => Self::Pointer, List(_) => Self::Pointer, SList(_) => Self::Pointer, PtrArray(_) => Self::Pointer, Function(super::library::Function { name, .. }) if name == "AsyncReadyCallback" => { Self::Direct } Function(_) => Self::Direct, Custom(super::library::Custom { conversion_type, .. }) => conversion_type.clone(), _ => Self::Unknown, } } pub fn can_use_to_generate(&self) -> bool { matches!(self, Self::Option | Self::Result { .. }) } } gir-0.20.5/src/analysis/enums.rs000066400000000000000000000065111475434152100164770ustar00rootroot00000000000000use log::info; use super::{function_parameters::TransformationType, imports::Imports, *}; use crate::{codegen::Visibility, config::gobjects::GObject, env::Env, nameutil::*, traits::*}; #[derive(Debug, Default)] pub struct Info { pub full_name: String, pub type_id: library::TypeId, pub name: String, pub functions: Vec, pub specials: special_functions::Infos, pub visibility: Visibility, } impl Info { pub fn type_<'a>(&self, library: &'a library::Library) -> &'a library::Enumeration { let type_ = library .type_(self.type_id) .maybe_ref() .unwrap_or_else(|| panic!("{} is not an enumeration.", self.full_name)); type_ } } pub fn new(env: &Env, obj: &GObject, imports: &mut Imports) -> Option { info!("Analyzing enumeration {}", obj.name); if obj.status.ignored() { return None; } let enumeration_tid = env.library.find_type(0, &obj.name)?; let type_ = env.type_(enumeration_tid); let enumeration: &library::Enumeration = type_.maybe_ref()?; let name = split_namespace_name(&obj.name).1; if obj.status.need_generate() { // Mark the type as available within the enum namespace: imports.add_defined(&format!("crate::{name}")); imports.add("crate::ffi"); let imports = &mut imports.with_defaults(enumeration.version, &None); imports.add("glib::translate::*"); let has_get_quark = enumeration.error_domain.is_some(); if has_get_quark { imports.add("glib::prelude::*"); } let has_get_type = enumeration.glib_get_type.is_some(); if has_get_type { imports.add("glib::prelude::*"); } } let mut functions = functions::analyze( env, &enumeration.functions, Some(enumeration_tid), false, false, obj, imports, None, None, ); // Gir does not currently mark the first parameter of associated enum functions // - that are identical to its enum type - as instance parameter since most // languages do not support this. for f in &mut functions { if f.parameters.c_parameters.is_empty() { continue; } let first_param = &mut f.parameters.c_parameters[0]; if first_param.typ == enumeration_tid { first_param.instance_parameter = true; let t = f .parameters .transformations .iter_mut() .find(|t| t.ind_c == 0) .unwrap(); if let TransformationType::ToGlibScalar { name, .. } = &mut t.transformation_type { *name = "self".to_owned(); } else { panic!( "Enumeration function instance param must be passed as scalar, not {:?}", t.transformation_type ); } } } let specials = special_functions::extract(&mut functions, type_, obj); if obj.status.need_generate() { special_functions::analyze_imports(&specials, imports); } let info = Info { full_name: obj.name.clone(), type_id: enumeration_tid, name: name.to_owned(), functions, specials, visibility: obj.visibility, }; Some(info) } gir-0.20.5/src/analysis/ffi_type.rs000066400000000000000000000174561475434152100171670ustar00rootroot00000000000000use log::{info, trace}; use crate::{ analysis::{ c_type::{implements_c_type, rustify_pointers}, is_gpointer, rust_type::{Result, TypeError}, }, env::Env, library::*, nameutil::{use_glib_if_needed, use_glib_type}, traits::*, }; pub fn used_ffi_type(env: &Env, type_id: TypeId, c_type: &str) -> Option { let (_ptr, inner) = rustify_pointers(c_type); let type_ = ffi_inner(env, type_id, &inner); type_.ok().and_then(|type_name| { if type_name.as_str().find(':').is_some() { Some(type_name.into_string()) } else { None } }) } pub fn ffi_type(env: &Env, tid: TypeId, c_type: &str) -> Result { let (ptr, inner) = rustify_pointers(c_type); let res = if ptr.is_empty() { if let Some(c_tid) = env.library.find_type(0, c_type) { // Fast track plain basic types avoiding some checks if env.library.type_(c_tid).maybe_ref_as::().is_some() { match env.library.type_(tid) { Type::FixedArray(_, size, _) => { ffi_inner(env, c_tid, c_type).map_any(|rust_type| { rust_type.alter_type(|typ_| format!("[{typ_}; {size}]")) }) } Type::Class(Class { c_type: expected, .. }) | Type::Interface(Interface { c_type: expected, .. }) if is_gpointer(c_type) => { info!("[c:type `gpointer` instead of `*mut {}`, fixing]", expected); ffi_inner(env, tid, expected).map_any(|rust_type| { rust_type.alter_type(|typ_| format!("*mut {typ_}")) }) } _ => ffi_inner(env, c_tid, c_type), } } else { // c_type isn't basic ffi_inner(env, tid, &inner) } } else { // c_type doesn't match any type in the library by name ffi_inner(env, tid, &inner) } } else { // ptr not empty ffi_inner(env, tid, &inner) .map_any(|rust_type| rust_type.alter_type(|typ_| format!("{ptr} {typ_}"))) }; trace!("ffi_type({:?}, {}) -> {:?}", tid, c_type, res); res } fn ffi_inner(env: &Env, tid: TypeId, inner: &str) -> Result { let typ = env.library.type_(tid); match *typ { Type::Basic(fund) => { use crate::library::Basic::*; let inner = match fund { None => "std::ffi::c_void", Boolean => return Ok(use_glib_if_needed(env, "ffi::gboolean").into()), Int8 => "i8", UInt8 => "u8", Int16 => "i16", UInt16 => "u16", Int32 => "i32", UInt32 => "u32", Int64 => "i64", UInt64 => "u64", Char => "std::ffi::c_char", UChar => "std::ffi::c_uchar", Short => "std::ffi::c_short", UShort => "std::ffi::c_ushort", Int => "std::ffi::c_int", UInt => "std::ffi::c_uint", Long => "std::ffi::c_long", ULong => "std::ffi::c_ulong", Size => "libc::size_t", SSize => "libc::ssize_t", Float => "std::ffi::c_float", Double => "std::ffi::c_double", TimeT => "libc::time_t", OffT => "libc::off_t", DevT => "libc::dev_t", GidT => "libc::gid_t", PidT => "libc::pid_t", SockLenT => "libc::socklen_t", UidT => "libc::uid_t", UniChar => "u32", Utf8 => "std::ffi::c_char", Filename => "std::ffi::c_char", Type => return Ok(use_glib_if_needed(env, "ffi::GType").into()), IntPtr => "libc::intptr_t", UIntPtr => "libc::uintptr_t", Bool => "bool", _ => return Err(TypeError::Unimplemented(inner.into())), }; Ok(inner.into()) } Type::Union(..) | Type::Record(..) | Type::Alias(..) | Type::Function(..) => { if let Some(declared_c_type) = typ.get_glib_name() { if declared_c_type != inner { let msg = format!( "[c:type mismatch `{}` != `{}` of `{}`]", inner, declared_c_type, typ.get_name() ); warn_main!(tid, "{}", msg); return Err(TypeError::Mismatch(msg)); } } else { warn_main!(tid, "Type `{}` missing c_type", typ.get_name()); } fix_name(env, tid, inner) } Type::CArray(inner_tid) => ffi_inner(env, inner_tid, inner), Type::FixedArray(inner_tid, size, _) => ffi_inner(env, inner_tid, inner) .map_any(|rust_type| rust_type.alter_type(|typ_| format!("[{typ_}; {size}]"))), Type::Array(..) | Type::PtrArray(..) | Type::List(..) | Type::SList(..) | Type::HashTable(..) => fix_name(env, tid, inner), _ => { if let Some(glib_name) = env.library.type_(tid).get_glib_name() { if inner != glib_name { if implements_c_type(env, tid, inner) { info!( "[c:type {} of {} <: {}, fixing]", glib_name, env.library.type_(tid).get_name(), inner ); fix_name(env, tid, glib_name) } else { let msg = format!( "[c:type mismatch {} != {} of {}]", inner, glib_name, env.library.type_(tid).get_name() ); warn_main!(tid, "{}", msg); Err(TypeError::Mismatch(msg)) } } else { fix_name(env, tid, inner) } } else { let msg = format!( "[Missing glib_name of {}, can't match != {}]", env.library.type_(tid).get_name(), inner ); warn_main!(tid, "{}", msg); Err(TypeError::Mismatch(msg)) } } } } fn fix_name(env: &Env, type_id: TypeId, name: &str) -> Result { if type_id.ns_id == INTERNAL_NAMESPACE { match env.library.type_(type_id) { Type::Array(..) | Type::PtrArray(..) | Type::List(..) | Type::SList(..) | Type::HashTable(..) => Ok(use_glib_if_needed(env, &format!("ffi::{name}")).into()), _ => Ok(name.into()), } } else { let sys_crate_name = &env.namespaces[type_id.ns_id].sys_crate_name; let sys_crate_name = if sys_crate_name == "gobject_ffi" { use_glib_type(env, "gobject_ffi") } else if type_id.ns_id == MAIN_NAMESPACE { sys_crate_name.clone() } else { format!( "{}::{}", env.namespaces[type_id.ns_id].crate_name, sys_crate_name ) }; let name_with_prefix = format!("{sys_crate_name}::{name}"); if env .type_status_sys(&type_id.full_name(&env.library)) .ignored() { Err(TypeError::Ignored(name_with_prefix)) } else { Ok(name_with_prefix.into()) } } } gir-0.20.5/src/analysis/flags.rs000066400000000000000000000062501475434152100164440ustar00rootroot00000000000000use log::info; use super::{function_parameters::TransformationType, imports::Imports, *}; use crate::{codegen::Visibility, config::gobjects::GObject, env::Env, nameutil::*, traits::*}; #[derive(Debug, Default)] pub struct Info { pub full_name: String, pub type_id: library::TypeId, pub name: String, pub functions: Vec, pub specials: special_functions::Infos, pub visibility: Visibility, } impl Info { pub fn type_<'a>(&self, library: &'a library::Library) -> &'a library::Bitfield { let type_ = library .type_(self.type_id) .maybe_ref() .unwrap_or_else(|| panic!("{} is not an flags.", self.full_name)); type_ } } pub fn new(env: &Env, obj: &GObject, imports: &mut Imports) -> Option { info!("Analyzing flags {}", obj.name); if obj.status.ignored() { return None; } let flags_tid = env.library.find_type(0, &obj.name)?; let type_ = env.type_(flags_tid); let flags: &library::Bitfield = type_.maybe_ref()?; let name = split_namespace_name(&obj.name).1; if obj.status.need_generate() { // Mark the type as available within the bitfield namespace: imports.add_defined(&format!("crate::{name}")); imports.add("crate::ffi"); let imports = &mut imports.with_defaults(flags.version, &None); imports.add("glib::translate::*"); imports.add("glib::bitflags::bitflags"); let has_get_type = flags.glib_get_type.is_some(); if has_get_type { imports.add("glib::prelude::*"); } } let mut functions = functions::analyze( env, &flags.functions, Some(flags_tid), false, false, obj, imports, None, None, ); // Gir does not currently mark the first parameter of associated bitfield // functions - that are identical to its bitfield type - as instance // parameter since most languages do not support this. for f in &mut functions { if f.parameters.c_parameters.is_empty() { continue; } let first_param = &mut f.parameters.c_parameters[0]; if first_param.typ == flags_tid { first_param.instance_parameter = true; let t = f .parameters .transformations .iter_mut() .find(|t| t.ind_c == 0) .unwrap(); if let TransformationType::ToGlibScalar { name, .. } = &mut t.transformation_type { *name = "self".to_owned(); } else { panic!( "Bitfield function instance param must be passed as scalar, not {:?}", t.transformation_type ); } } } let specials = special_functions::extract(&mut functions, type_, obj); if obj.status.need_generate() { special_functions::analyze_imports(&specials, imports); } let info = Info { full_name: obj.name.clone(), type_id: flags_tid, name: name.to_owned(), functions, specials, visibility: obj.visibility, }; Some(info) } gir-0.20.5/src/analysis/function_parameters.rs000066400000000000000000000400761475434152100214240ustar00rootroot00000000000000use std::collections::HashMap; use super::{ conversion_type::ConversionType, out_parameters::can_as_return, override_string_type::override_string_type_parameter, ref_mode::RefMode, rust_type::RustType, try_from_glib::TryFromGlib, }; use crate::{ analysis::{self, bounds::Bounds}, config::{self, parameter_matchable::ParameterMatchable}, env::Env, library::{self, Nullable, ParameterScope, Transfer, TypeId}, nameutil, traits::IntoString, }; #[derive(Clone, Debug)] pub struct Parameter { pub lib_par: library::Parameter, pub try_from_glib: TryFromGlib, } impl Parameter { pub fn from_parameter( env: &Env, lib_par: &library::Parameter, configured_parameters: &[&config::functions::Parameter], ) -> Self { Parameter { lib_par: lib_par.clone(), try_from_glib: TryFromGlib::from_parameter(env, lib_par.typ, configured_parameters), } } pub fn from_return_value( env: &Env, lib_par: &library::Parameter, configured_functions: &[&config::functions::Function], ) -> Self { Parameter { lib_par: lib_par.clone(), try_from_glib: TryFromGlib::from_return_value(env, lib_par.typ, configured_functions), } } } // TODO: remove unused fields #[derive(Clone, Debug)] pub struct RustParameter { pub ind_c: usize, // index in `Vec` pub name: String, pub typ: TypeId, } #[derive(Clone, Debug)] pub struct CParameter { pub name: String, pub typ: TypeId, pub c_type: String, pub instance_parameter: bool, pub direction: library::ParameterDirection, pub nullable: library::Nullable, pub transfer: library::Transfer, pub caller_allocates: bool, pub is_error: bool, pub scope: ParameterScope, /// Index of the user data parameter associated with the callback. pub user_data_index: Option, /// Index of the destroy notification parameter associated with the /// callback. pub destroy_index: Option, // analysis fields pub ref_mode: RefMode, pub try_from_glib: TryFromGlib, pub move_: bool, } #[derive(Clone, Debug)] pub enum TransformationType { ToGlibDirect { name: String, }, ToGlibScalar { name: String, nullable: library::Nullable, needs_into: bool, }, ToGlibPointer { name: String, instance_parameter: bool, transfer: library::Transfer, ref_mode: RefMode, // filled by functions to_glib_extra: String, explicit_target_type: String, pointer_cast: String, in_trait: bool, nullable: bool, move_: bool, }, ToGlibBorrow, ToGlibUnknown { name: String, }, Length { array_name: String, array_length_name: String, array_length_type: String, }, IntoRaw(String), ToSome(String), } impl TransformationType { pub fn is_to_glib(&self) -> bool { matches!( *self, Self::ToGlibDirect { .. } | Self::ToGlibScalar { .. } | Self::ToGlibPointer { .. } | Self::ToGlibBorrow | Self::ToGlibUnknown { .. } | Self::ToSome(_) | Self::IntoRaw(_) ) } pub fn set_to_glib_extra(&mut self, to_glib_extra_: &str) { if let Self::ToGlibPointer { to_glib_extra, .. } = self { *to_glib_extra = to_glib_extra_.to_owned(); } } } #[derive(Clone, Debug)] pub struct Transformation { pub ind_c: usize, // index in `Vec` pub ind_rust: Option, // index in `Vec` pub transformation_type: TransformationType, } #[derive(Clone, Default, Debug)] pub struct Parameters { pub rust_parameters: Vec, pub c_parameters: Vec, pub transformations: Vec, } impl Parameters { fn new(capacity: usize) -> Self { Self { rust_parameters: Vec::with_capacity(capacity), c_parameters: Vec::with_capacity(capacity), transformations: Vec::with_capacity(capacity), } } pub fn analyze_return(&mut self, env: &Env, ret: &Option) { let ret_data = ret .as_ref() .map(|r| (r.lib_par.array_length, &r.try_from_glib)); let (ind_c, try_from_glib) = match ret_data { Some((Some(array_length), try_from_glib)) => (array_length as usize, try_from_glib), _ => return, }; let c_par = if let Some(c_par) = self.c_parameters.get_mut(ind_c) { c_par.try_from_glib = try_from_glib.clone(); c_par } else { return; }; let transformation = Transformation { ind_c, ind_rust: None, transformation_type: get_length_type(env, "", &c_par.name, c_par.typ), }; self.transformations.push(transformation); } } pub fn analyze( env: &Env, function_parameters: &[library::Parameter], configured_functions: &[&config::functions::Function], disable_length_detect: bool, async_func: bool, in_trait: bool, ) -> Parameters { let mut parameters = Parameters::new(function_parameters.len()); // Map: length argument position => parameter let array_lengths: HashMap = function_parameters .iter() .filter_map(|p| p.array_length.map(|pos| (pos, p))) .collect(); let mut to_remove = Vec::new(); let mut correction_instance = 0; for par in function_parameters.iter() { if par.scope.is_none() { continue; } if let Some(index) = par.closure { to_remove.push(index); } if let Some(index) = par.destroy { to_remove.push(index); } } for (pos, par) in function_parameters.iter().enumerate() { let name = if par.instance_parameter { par.name.clone() } else { nameutil::mangle_keywords(&*par.name).into_owned() }; if par.instance_parameter { correction_instance = 1; } let configured_parameters = configured_functions.matched_parameters(&name); let c_type = par.c_type.clone(); let typ = override_string_type_parameter(env, par.typ, &configured_parameters); let ind_c = parameters.c_parameters.len(); let mut ind_rust = Some(parameters.rust_parameters.len()); let mut add_rust_parameter = match par.direction { library::ParameterDirection::In | library::ParameterDirection::InOut => true, library::ParameterDirection::Return => false, library::ParameterDirection::Out => !can_as_return(env, par) && !async_func, library::ParameterDirection::None => { panic!("undefined direction for parameter {par:?}") } }; if async_func && pos >= correction_instance && to_remove.contains(&(pos - correction_instance)) { add_rust_parameter = false; } let mut transfer = par.transfer; let mut caller_allocates = par.caller_allocates; let conversion = ConversionType::of(env, typ); if let ConversionType::Direct | ConversionType::Scalar | ConversionType::Option | ConversionType::Result { .. } = conversion { // For simple types no reason to have these flags caller_allocates = false; transfer = library::Transfer::None; } let move_ = configured_parameters .iter() .find_map(|p| p.move_) .unwrap_or_else(|| { // FIXME: drop the condition here once we have figured out how to handle // the Vec use case, e.g with something like PtrSlice if matches!(env.library.type_(typ), library::Type::CArray(_)) { false } else { transfer == Transfer::Full && par.direction.is_in() } }); let mut array_par = configured_parameters.iter().find_map(|cp| { cp.length_of .as_ref() .and_then(|n| function_parameters.iter().find(|fp| fp.name == *n)) }); if array_par.is_none() { array_par = array_lengths.get(&(pos as u32)).copied(); } if array_par.is_none() && !disable_length_detect { array_par = detect_length(env, pos, par, function_parameters); } if let Some(array_par) = array_par { let mut array_name = nameutil::mangle_keywords(&array_par.name); if let Some(bound_type) = Bounds::type_for(env, array_par.typ) { array_name = (array_name.into_owned() + &Bounds::get_to_glib_extra( &bound_type, *array_par.nullable, array_par.instance_parameter, move_, )) .into(); } add_rust_parameter = false; let transformation = Transformation { ind_c, ind_rust: None, transformation_type: get_length_type(env, &array_name, &par.name, typ), }; parameters.transformations.push(transformation); } let immutable = configured_parameters.iter().any(|p| p.constant); let ref_mode = RefMode::without_unneeded_mut(env, par, immutable, in_trait && par.instance_parameter); let nullable_override = configured_parameters.iter().find_map(|p| p.nullable); let nullable = nullable_override.unwrap_or(par.nullable); let try_from_glib = TryFromGlib::from_parameter(env, typ, &configured_parameters); let c_par = CParameter { name: name.clone(), typ, c_type, instance_parameter: par.instance_parameter, direction: par.direction, transfer, caller_allocates, nullable, ref_mode, is_error: par.is_error, scope: par.scope, user_data_index: par.closure, destroy_index: par.destroy, try_from_glib: try_from_glib.clone(), move_, }; parameters.c_parameters.push(c_par); let data_param_name = "user_data"; let callback_param_name = "callback"; if add_rust_parameter { let rust_par = RustParameter { name: name.clone(), typ, ind_c, }; parameters.rust_parameters.push(rust_par); } else { ind_rust = None; } let transformation_type = match conversion { ConversionType::Direct => { if par.c_type != "GLib.Pid" { TransformationType::ToGlibDirect { name } } else { TransformationType::ToGlibScalar { name, nullable, needs_into: false, } } } ConversionType::Scalar => TransformationType::ToGlibScalar { name, nullable, needs_into: false, }, ConversionType::Option => { let needs_into = match try_from_glib { TryFromGlib::Option => par.direction == library::ParameterDirection::In, TryFromGlib::OptionMandatory => false, other => unreachable!("{:?} inconsistent / conversion type", other), }; TransformationType::ToGlibScalar { name, nullable: Nullable(false), needs_into, } } ConversionType::Result { .. } => { let needs_into = match try_from_glib { TryFromGlib::Result { .. } => par.direction == library::ParameterDirection::In, TryFromGlib::ResultInfallible { .. } => false, other => unreachable!("{:?} inconsistent / conversion type", other), }; TransformationType::ToGlibScalar { name, nullable: Nullable(false), needs_into, } } ConversionType::Pointer => TransformationType::ToGlibPointer { name, instance_parameter: par.instance_parameter, transfer, ref_mode, to_glib_extra: Default::default(), explicit_target_type: Default::default(), pointer_cast: if matches!(env.library.type_(typ), library::Type::CArray(_)) && par.c_type == "gpointer" { format!(" as {}", nameutil::use_glib_if_needed(env, "ffi::gpointer")) } else { Default::default() }, in_trait, nullable: *nullable, move_, }, ConversionType::Borrow => TransformationType::ToGlibBorrow, ConversionType::Unknown => TransformationType::ToGlibUnknown { name }, }; let mut transformation = Transformation { ind_c, ind_rust, transformation_type, }; let mut transformation_type = None; match transformation.transformation_type { TransformationType::ToGlibDirect { ref name, .. } | TransformationType::ToGlibUnknown { ref name, .. } => { if async_func && name == callback_param_name { // Remove the conversion of callback for async functions. transformation_type = Some(TransformationType::ToSome(name.clone())); } } TransformationType::ToGlibPointer { ref name, .. } => { if async_func && name == data_param_name { // Do the conversion of user_data for async functions. // In async functions, this argument is used to send the callback. transformation_type = Some(TransformationType::IntoRaw(name.clone())); } } _ => (), } if let Some(transformation_type) = transformation_type { transformation.transformation_type = transformation_type; } parameters.transformations.push(transformation); } parameters } fn get_length_type( env: &Env, array_name: &str, length_name: &str, length_typ: TypeId, ) -> TransformationType { let array_length_type = RustType::try_new(env, length_typ).into_string(); TransformationType::Length { array_name: array_name.to_string(), array_length_name: length_name.to_string(), array_length_type, } } fn detect_length<'a>( env: &Env, pos: usize, par: &library::Parameter, parameters: &'a [library::Parameter], ) -> Option<&'a library::Parameter> { if !is_length(par) || pos == 0 { return None; } parameters.get(pos - 1).and_then(|p| { if has_length(env, p.typ) { Some(p) } else { None } }) } fn is_length(par: &library::Parameter) -> bool { if par.direction != library::ParameterDirection::In { return false; } let len = par.name.len(); if len >= 3 && &par.name[len - 3..len] == "len" { return true; } par.name.contains("length") } fn has_length(env: &Env, typ: TypeId) -> bool { use crate::library::{Basic::*, Type}; let typ = env.library.type_(typ); match typ { Type::Basic(Utf8 | Filename | OsString) => true, Type::CArray(..) | Type::FixedArray(..) | Type::Array(..) | Type::PtrArray(..) | Type::List(..) | Type::SList(..) | Type::HashTable(..) => true, Type::Alias(alias) => has_length(env, alias.typ), _ => false, } } gir-0.20.5/src/analysis/functions.rs000066400000000000000000001314531475434152100173640ustar00rootroot00000000000000// TODO: better heuristic (https://bugzilla.gnome.org/show_bug.cgi?id=623635#c5) // TODO: ProgressCallback types (not specific to async). // TODO: add annotation for methods like g_file_replace_contents_bytes_async // where the finish method has a different prefix. use std::{ borrow::Borrow, collections::{HashMap, HashSet}, }; use log::warn; use super::{namespaces::NsId, special_functions}; use crate::{ analysis::{ self, bounds::{Bounds, CallbackInfo}, function_parameters::{self, CParameter, Parameters, Transformation, TransformationType}, imports::Imports, is_gpointer, out_parameters::{self, use_function_return_for_result}, ref_mode::RefMode, return_value, rust_type::*, safety_assertion_mode::SafetyAssertionMode, signatures::{Signature, Signatures}, trampolines::Trampoline, }, codegen::Visibility, config::{self, gobjects::GStatus}, env::Env, library::{ self, Function, FunctionKind, ParameterDirection, ParameterScope, Transfer, Type, MAIN_NAMESPACE, }, nameutil, traits::*, version::Version, }; #[derive(Clone, Debug)] pub struct AsyncTrampoline { pub is_method: bool, pub has_error_parameter: bool, pub name: String, pub finish_func_name: String, pub callback_type: String, pub bound_name: char, pub output_params: Vec, pub ffi_ret: Option, } #[derive(Clone, Debug)] pub struct AsyncFuture { pub is_method: bool, pub name: String, pub success_parameters: String, pub error_parameters: Option, pub assertion: SafetyAssertionMode, } #[derive(Debug)] pub struct Info { pub name: String, pub func_name: String, pub new_name: Option, pub glib_name: String, pub status: GStatus, pub kind: library::FunctionKind, pub visibility: Visibility, pub type_name: Result, pub parameters: Parameters, pub ret: return_value::Info, pub bounds: Bounds, pub outs: out_parameters::Info, pub version: Option, pub deprecated_version: Option, pub not_version: Option, pub cfg_condition: Option, pub assertion: SafetyAssertionMode, pub doc_hidden: bool, pub doc_trait_name: Option, pub doc_struct_name: Option, pub doc_ignore_parameters: HashSet, pub r#async: bool, pub unsafe_: bool, pub trampoline: Option, pub callbacks: Vec, pub destroys: Vec, pub remove_params: Vec, pub async_future: Option, /// Whether the function is hidden (an implementation detail) /// Like the ref/unref/copy/free functions pub hidden: bool, /// Whether the function can't be generated pub commented: bool, /// In order to generate docs links we need to know in which namespace /// this potential global function is defined pub ns_id: NsId, pub generate_doc: bool, pub get_property: Option, pub set_property: Option, } impl Info { pub fn codegen_name(&self) -> &str { self.new_name.as_ref().unwrap_or(&self.name) } pub fn is_special(&self) -> bool { self.codegen_name() .trim_end_matches('_') .rsplit('_') .next() .is_some_and(|i| i.parse::().is_ok()) } // returns whether the method can be linked in the docs pub fn should_be_doc_linked(&self, env: &Env) -> bool { self.should_docs_be_generated(env) && (self.status.manual() || (!self.commented && !self.hidden)) } pub fn should_docs_be_generated(&self, env: &Env) -> bool { !self.status.ignored() && !self.is_special() && !self.is_async_finish(env) } pub fn doc_link( &self, parent: Option<&str>, visible_parent: Option<&str>, is_self: bool, ) -> String { if let Some(p) = parent { if is_self { format!("[`{f}()`][Self::{f}()]", f = self.codegen_name()) } else { format!( "[`{visible_parent}::{f}()`][crate::{p}::{f}()]", visible_parent = visible_parent.unwrap_or(p), p = p, f = self.codegen_name() ) } } else { format!( "[`{fn_name}()`][crate::{fn_name}()]", fn_name = self.codegen_name() ) } } pub fn is_async_finish(&self, env: &Env) -> bool { let has_async_result = self .parameters .rust_parameters .iter() .any(|param| param.typ.full_name(&env.library) == "Gio.AsyncResult"); self.name.ends_with("_finish") && has_async_result } } pub fn analyze>( env: &Env, functions: &[F], type_tid: Option, in_trait: bool, is_boxed: bool, obj: &config::gobjects::GObject, imports: &mut Imports, mut signatures: Option<&mut Signatures>, deps: Option<&[library::TypeId]>, ) -> Vec { let mut funcs = Vec::new(); 'func: for func in functions { let func = func.borrow(); let configured_functions = obj.functions.matched(&func.name); let mut status = obj.status; for f in &configured_functions { match f.status { GStatus::Ignore => continue 'func, GStatus::Manual => { status = GStatus::Manual; break; } GStatus::Generate => (), } } if env.is_totally_deprecated( Some(type_tid.unwrap_or_default().ns_id), func.deprecated_version, ) { continue; } let name = nameutil::mangle_keywords(&*func.name).into_owned(); let signature_params = Signature::new(func); let mut not_version = None; if func.kind == library::FunctionKind::Method { if let Some(deps) = deps { let (has, version) = signature_params.has_in_deps(env, &name, deps); if has { if let Some(v) = version { if v > env.config.min_cfg_version { not_version = version; } } } } } if let Some(signatures) = signatures.as_mut() { signatures.insert(name.clone(), signature_params); } let mut info = analyze_function( env, obj, &func.name, name, status, func, type_tid, in_trait, is_boxed, &configured_functions, imports, ); info.not_version = not_version; funcs.push(info); } funcs } fn fixup_gpointer_parameter( env: &Env, type_tid: library::TypeId, is_boxed: bool, in_trait: bool, parameters: &mut Parameters, idx: usize, ) { use crate::analysis::ffi_type; let instance_parameter = idx == 0; let glib_name = env.library.type_(type_tid).get_glib_name().unwrap(); let ffi_name = ffi_type::ffi_type(env, type_tid, glib_name).unwrap(); let pointer_type = if is_boxed { "*const" } else { "*mut" }; parameters.rust_parameters[idx].typ = type_tid; parameters.c_parameters[idx].typ = type_tid; parameters.c_parameters[idx].instance_parameter = instance_parameter; parameters.c_parameters[idx].ref_mode = RefMode::ByRef; parameters.c_parameters[idx].transfer = Transfer::None; parameters.transformations[idx] = Transformation { ind_c: idx, ind_rust: Some(idx), transformation_type: TransformationType::ToGlibPointer { name: parameters.rust_parameters[idx].name.clone(), instance_parameter, transfer: Transfer::None, ref_mode: RefMode::ByRef, to_glib_extra: Default::default(), explicit_target_type: format!("{} {}", pointer_type, ffi_name.as_str()), pointer_cast: format!( " as {}", nameutil::use_glib_if_needed(env, "ffi::gconstpointer") ), in_trait, nullable: false, move_: false, }, }; } fn fixup_special_functions( env: &Env, name: &str, type_tid: library::TypeId, is_boxed: bool, in_trait: bool, parameters: &mut Parameters, ) { // Workaround for some _hash() / _compare() / _equal() functions taking // "gconstpointer" as arguments instead of the actual type if name == "hash" && parameters.c_parameters.len() == 1 && parameters.c_parameters[0].c_type == "gconstpointer" { fixup_gpointer_parameter(env, type_tid, is_boxed, in_trait, parameters, 0); } if (name == "compare" || name == "equal" || name == "is_equal") && parameters.c_parameters.len() == 2 && parameters.c_parameters[0].c_type == "gconstpointer" && parameters.c_parameters[1].c_type == "gconstpointer" { fixup_gpointer_parameter(env, type_tid, is_boxed, in_trait, parameters, 0); fixup_gpointer_parameter(env, type_tid, is_boxed, in_trait, parameters, 1); } } fn find_callback_bound_to_destructor( callbacks: &[Trampoline], destroy: &mut Trampoline, destroy_index: usize, ) -> bool { for call in callbacks { if call.destroy_index == destroy_index { destroy.nullable = call.nullable; destroy.bound_name = call.bound_name.clone(); return true; } } false } fn analyze_callbacks( env: &Env, func: &library::Function, cross_user_data_check: &mut HashMap, user_data_indexes: &mut HashSet, parameters: &mut Parameters, used_types: &mut Vec, bounds: &mut Bounds, to_glib_extras: &mut HashMap, imports: &mut Imports, destroys: &mut Vec, callbacks: &mut Vec, params: &mut Vec, configured_functions: &[&config::functions::Function], disable_length_detect: bool, in_trait: bool, commented: &mut bool, concurrency: library::Concurrency, type_tid: library::TypeId, ) { let mut to_replace = Vec::new(); let mut to_remove = Vec::new(); { // When closure data and destroy are specified in gir, they don't take into // account the actual closure parameter. let mut c_parameters = Vec::new(); for (pos, par) in parameters.c_parameters.iter().enumerate() { if par.instance_parameter { continue; } c_parameters.push((par, pos)); } let func_name = match &func.c_identifier { Some(n) => n, None => &func.name, }; let mut destructors_to_update = Vec::new(); for pos in 0..parameters.c_parameters.len() { // If it is a user data parameter, we ignore it. if cross_user_data_check.values().any(|p| *p == pos) || user_data_indexes.contains(&pos) { continue; } let par = ¶meters.c_parameters[pos]; assert!( !par.instance_parameter || pos == 0, "Wrong instance parameter in {}", func.c_identifier.as_ref().unwrap() ); if let Ok(rust_type) = RustType::builder(env, par.typ) .direction(par.direction) .try_from_glib(&par.try_from_glib) .try_build() { used_types.extend(rust_type.into_used_types()); } let rust_type = env.library.type_(par.typ); let callback_info = if !*par.nullable || !rust_type.is_function() { let (to_glib_extra, callback_info) = bounds.add_for_parameter( env, func, par, false, concurrency, configured_functions, ); if let Some(to_glib_extra) = to_glib_extra { if par.c_type != "GDestroyNotify" { to_glib_extras.insert(pos, to_glib_extra); } } callback_info } else { None }; if rust_type.is_function() { if par.c_type != "GDestroyNotify" { let callback_parameters_config = configured_functions.iter().find_map(|f| { f.parameters .iter() .find(|p| p.ident.is_match(&par.name)) .map(|p| &p.callback_parameters) }); if let Some((mut callback, destroy_index)) = analyze_callback( func_name, type_tid, env, par, &callback_info, commented, imports, &c_parameters, rust_type, callback_parameters_config, ) { if let Some(destroy_index) = destroy_index { let user_data = cross_user_data_check .entry(destroy_index) .or_insert_with(|| callback.user_data_index); if *user_data != callback.user_data_index { warn_main!( type_tid, "`{}`: Different destructors cannot share the same user data", func_name ); *commented = true; } callback.destroy_index = destroy_index; } else { user_data_indexes.insert(callback.user_data_index); to_remove.push(callback.user_data_index); } callbacks.push(callback); to_replace.push((pos, par.typ)); continue; } } else if let Some((mut callback, _)) = analyze_callback( func_name, type_tid, env, par, &callback_info, commented, imports, &c_parameters, rust_type, None, ) { // We just assume that for API "cleanness", the destroy callback will always // be |-> *after* <-| the initial callback. if let Some(user_data_index) = cross_user_data_check.get(&pos) { callback.user_data_index = *user_data_index; callback.destroy_index = pos; } else { warn_main!( type_tid, "`{}`: no user data point to the destroy callback", func_name, ); *commented = true; } // We check if the user trampoline is there. If so, we change the destroy // nullable value if needed. if !find_callback_bound_to_destructor(callbacks, &mut callback, pos) { // Maybe the linked callback is after so we store it just in case... destructors_to_update.push((pos, destroys.len())); } destroys.push(callback); to_remove.push(pos); continue; } } if !*commented { *commented |= RustType::builder(env, par.typ) .direction(par.direction) .scope(par.scope) .try_from_glib(&par.try_from_glib) .try_build_param() .is_err(); } } for (destroy_index, pos_in_destroys) in destructors_to_update { if !find_callback_bound_to_destructor( callbacks, &mut destroys[pos_in_destroys], destroy_index, ) { warn_main!( type_tid, "`{}`: destructor without linked callback", func_name ); } } } // Check for cross "user data". if cross_user_data_check .values() .collect::>() .windows(2) .any(|a| a[0] == a[1]) { *commented = true; warn_main!( type_tid, "`{}`: Different user data share the same destructors", func.name ); } if !destroys.is_empty() || !callbacks.is_empty() { for (pos, typ) in to_replace { let ty = env.library.type_(typ); params[pos].typ = typ; params[pos].c_type = ty.get_glib_name().unwrap().to_owned(); } let mut s = to_remove .iter() .chain(cross_user_data_check.values()) .collect::>() // To prevent duplicates. .into_iter() .collect::>(); s.sort(); // We need to sort the array, otherwise the indexes won't be working // anymore. for pos in s.iter().rev() { params.remove(**pos); } *parameters = function_parameters::analyze( env, params, configured_functions, disable_length_detect, false, in_trait, ); } else { warn_main!( type_tid, "`{}`: this is supposed to be a callback function but no callback was found...", func.name ); *commented = true; } } fn analyze_function( env: &Env, obj: &config::gobjects::GObject, func_name: &str, name: String, status: GStatus, func: &library::Function, type_tid: Option, in_trait: bool, is_boxed: bool, configured_functions: &[&config::functions::Function], imports: &mut Imports, ) -> Info { let ns_id = type_tid.map_or(MAIN_NAMESPACE, |t| t.ns_id); let type_tid = type_tid.unwrap_or_default(); let r#async = func.finish_func.is_some() || func.parameters.iter().any(|parameter| { parameter.scope == ParameterScope::Async && parameter.c_type == "GAsyncReadyCallback" }); let has_callback_parameter = !r#async && func .parameters .iter() .any(|par| env.library.type_(par.typ).is_function()); let concurrency = match env.library.type_(type_tid) { library::Type::Class(_) | library::Type::Interface(_) | library::Type::Record(_) => { obj.concurrency } _ => library::Concurrency::SendSync, }; let mut commented = false; let mut bounds: Bounds = Default::default(); let mut to_glib_extras = HashMap::::new(); let mut used_types: Vec = Vec::with_capacity(4); let mut trampoline = None; let mut callbacks = Vec::new(); let mut destroys = Vec::new(); let mut async_future = None; if !r#async && !has_callback_parameter && func .parameters .iter() .any(|par| par.c_type == "GDestroyNotify") { // In here, We have a DestroyNotify callback but no other callback is provided. // A good example of this situation is this function: // https://developer.gnome.org/gio/stable/GTlsPassword.html#g-tls-password-set-value-full warn_main!( type_tid, "Function \"{}\" with destroy callback without callbacks", func.name ); commented = true; } let mut new_name = configured_functions.iter().find_map(|f| f.rename.clone()); let is_constructor = configured_functions.iter().find_map(|f| f.is_constructor); let bypass_auto_rename = configured_functions.iter().any(|f| f.bypass_auto_rename); let is_constructor = is_constructor.unwrap_or(false); if !bypass_auto_rename && new_name.is_none() { if func.kind == library::FunctionKind::Constructor || is_constructor { if func.kind == library::FunctionKind::Constructor && is_constructor { warn_main!( type_tid, "`{}`: config forces 'constructor' on an already gir-annotated 'constructor'", func_name ); } if name.starts_with("new_from") || name.starts_with("new_with") || name.starts_with("new_for") { new_name = Some(name[4..].to_string()); } } else { let nb_in_params = func .parameters .iter() .filter(|param| library::ParameterDirection::In == param.direction) .fold(0, |acc, _| acc + 1); let is_bool_getter = (func.parameters.len() == nb_in_params) && (func.ret.typ == library::TypeId::tid_bool() || func.ret.typ == library::TypeId::tid_c_bool()); new_name = getter_rules::try_rename_would_be_getter(&name, is_bool_getter) .ok() .map(getter_rules::NewName::unwrap); } } let version = configured_functions .iter() .filter_map(|f| f.version) .min() .or(func.version); let version = env.config.filter_version(version); let deprecated_version = func.deprecated_version; let visibility = configured_functions .iter() .find_map(|f| f.visibility) .unwrap_or_default(); let cfg_condition = configured_functions .iter() .find_map(|f| f.cfg_condition.clone()); let doc_hidden = configured_functions.iter().any(|f| f.doc_hidden); let doc_trait_name = configured_functions .iter() .find_map(|f| f.doc_trait_name.clone()); let doc_struct_name = configured_functions .iter() .find_map(|f| f.doc_struct_name.clone()); let doc_ignore_parameters = configured_functions .iter() .find(|f| !f.doc_ignore_parameters.is_empty()) .map(|f| f.doc_ignore_parameters.clone()) .unwrap_or_default(); let disable_length_detect = configured_functions.iter().any(|f| f.disable_length_detect); let no_future = configured_functions.iter().any(|f| f.no_future); let unsafe_ = configured_functions.iter().any(|f| f.unsafe_); let assertion = configured_functions.iter().find_map(|f| f.assertion); let imports = &mut imports.with_defaults(version, &cfg_condition); let ret = return_value::analyze( env, obj, func, type_tid, configured_functions, &mut used_types, imports, ); commented |= ret.commented; let mut params = func.parameters.clone(); let mut parameters = function_parameters::analyze( env, ¶ms, configured_functions, disable_length_detect, r#async, in_trait, ); parameters.analyze_return(env, &ret.parameter); if let Some(ref f) = ret.parameter { if let Type::Function(_) = env.library.type_(f.lib_par.typ) { if env.config.work_mode.is_normal() { warn!("Function \"{}\" returns callback", func.name); commented = true; } } } fixup_special_functions( env, name.as_str(), type_tid, is_boxed, in_trait, &mut parameters, ); // Key: destroy callback index // Value: associated user data index let mut cross_user_data_check: HashMap = HashMap::new(); let mut user_data_indexes: HashSet = HashSet::new(); if status.need_generate() { if !has_callback_parameter { let mut to_remove = Vec::new(); let mut correction_instance = 0; for par in parameters.c_parameters.iter() { if par.scope.is_none() { continue; } if let Some(index) = par.user_data_index { to_remove.push(index); } if let Some(index) = par.destroy_index { to_remove.push(index); } } for (pos, par) in parameters.c_parameters.iter().enumerate() { if par.instance_parameter { correction_instance = 1; } if r#async && pos >= correction_instance && to_remove.contains(&(pos - correction_instance)) { continue; } assert!( !par.instance_parameter || pos == 0, "Wrong instance parameter in {}", func.c_identifier.as_ref().unwrap() ); if let Ok(rust_type) = RustType::builder(env, par.typ) .direction(par.direction) .try_from_glib(&par.try_from_glib) .try_build() { if !rust_type.as_str().ends_with("GString") || par.c_type == "gchar***" { used_types.extend(rust_type.into_used_types()); } } let (to_glib_extra, callback_info) = bounds.add_for_parameter( env, func, par, r#async, library::Concurrency::None, configured_functions, ); if let Some(to_glib_extra) = to_glib_extra { to_glib_extras.insert(pos, to_glib_extra); } analyze_async( env, func, type_tid, new_name.as_ref().unwrap_or(&name), callback_info, &mut commented, &mut trampoline, no_future, &mut async_future, configured_functions, ¶meters, ); let type_error = !(r#async && *env.library.type_(par.typ) == Type::Basic(library::Basic::Pointer)) && RustType::builder(env, par.typ) .direction(par.direction) .scope(par.scope) .try_from_glib(&par.try_from_glib) .try_build_param() .is_err(); if type_error { commented = true; } } if r#async && trampoline.is_none() { commented = true; } } else { analyze_callbacks( env, func, &mut cross_user_data_check, &mut user_data_indexes, &mut parameters, &mut used_types, &mut bounds, &mut to_glib_extras, imports, &mut destroys, &mut callbacks, &mut params, configured_functions, disable_length_detect, in_trait, &mut commented, concurrency, type_tid, ); } } for par in ¶meters.rust_parameters { // Disallow basic arrays without length let is_len_for_par = |t: &Transformation| { if let TransformationType::Length { ref array_name, .. } = t.transformation_type { array_name == &par.name } else { false } }; if is_carray_with_direct_elements(env, par.typ) && !parameters.transformations.iter().any(is_len_for_par) { commented = true; } } let (outs, unsupported_outs) = out_parameters::analyze( env, func, ¶meters.c_parameters, &ret, configured_functions, ); if unsupported_outs { warn_main!( type_tid, "Function {} has unsupported outs", func.c_identifier.as_ref().unwrap_or(&func.name) ); commented = true; } if r#async && status.need_generate() && !commented { imports.add("std::boxed::Box as Box_"); imports.add("std::pin::Pin"); if let Some(ref trampoline) = trampoline { for out in &trampoline.output_params { if let Ok(rust_type) = RustType::builder(env, out.lib_par.typ) .direction(ParameterDirection::Out) .try_build() { used_types.extend(rust_type.into_used_types()); } } if let Some(ref out) = trampoline.ffi_ret { if let Ok(rust_type) = RustType::builder(env, out.lib_par.typ) .direction(ParameterDirection::Return) .try_build() { used_types.extend(rust_type.into_used_types()); } } } } if status.need_generate() && !commented { if (!destroys.is_empty() || !callbacks.is_empty()) && callbacks.iter().any(|c| !c.scope.is_call()) { imports.add("std::boxed::Box as Box_"); } for transformation in &mut parameters.transformations { if let Some(to_glib_extra) = to_glib_extras.get(&transformation.ind_c) { transformation .transformation_type .set_to_glib_extra(to_glib_extra); } } imports.add("crate::ffi"); imports.add_used_types(&used_types); if ret.base_tid.is_some() { imports.add("glib::prelude::*"); } if func.name.parse::().is_err() || parameters.c_parameters.iter().any(|p| p.move_) { imports.add("glib::translate::*"); } bounds.update_imports(imports); } let is_method = func.kind == library::FunctionKind::Method; let assertion = assertion.unwrap_or_else(|| SafetyAssertionMode::of(env, is_method, ¶meters)); let generate_doc = configured_functions.iter().all(|f| f.generate_doc); Info { name, func_name: func_name.to_string(), new_name, glib_name: func.c_identifier.as_ref().unwrap().clone(), status, kind: func.kind, visibility, type_name: RustType::try_new(env, type_tid), parameters, ret, bounds, outs, version, deprecated_version, not_version: None, cfg_condition, assertion, doc_hidden, doc_trait_name, doc_struct_name, doc_ignore_parameters, r#async, unsafe_, trampoline, async_future, callbacks, destroys, remove_params: cross_user_data_check.values().copied().collect::>(), commented, hidden: false, ns_id, generate_doc, get_property: func.get_property.clone(), set_property: func.set_property.clone(), } } pub fn is_carray_with_direct_elements(env: &Env, typ: library::TypeId) -> bool { match *env.library.type_(typ) { Type::CArray(inner_tid) => { use super::conversion_type::ConversionType; matches!(env.library.type_(inner_tid), Type::Basic(..) if ConversionType::of(env, inner_tid) == ConversionType::Direct) } _ => false, } } fn analyze_async( env: &Env, func: &library::Function, type_tid: library::TypeId, codegen_name: &str, callback_info: Option, commented: &mut bool, trampoline: &mut Option, no_future: bool, async_future: &mut Option, configured_functions: &[&config::functions::Function], parameters: &function_parameters::Parameters, ) -> bool { if let Some(CallbackInfo { callback_type, success_parameters, error_parameters, bound_name, }) = callback_info { // Checks for /*Ignored*/ or other error comments *commented |= callback_type.contains("/*"); let func_name = func.c_identifier.as_ref().unwrap(); let finish_func_name = if let Some(finish_func_name) = &func.finish_func { finish_func_name.to_string() } else { finish_function_name(func_name) }; let mut output_params = vec![]; let mut ffi_ret = None; if let Some(function) = find_function(env, &finish_func_name) { if use_function_return_for_result( env, function.ret.typ, &func.name, configured_functions, ) { ffi_ret = Some(analysis::Parameter::from_return_value( env, &function.ret, configured_functions, )); } for param in &function.parameters { let mut lib_par = param.clone(); if nameutil::needs_mangling(¶m.name) { lib_par.name = nameutil::mangle_keywords(&*param.name).into_owned(); } let configured_parameters = configured_functions.matched_parameters(&lib_par.name); output_params.push(analysis::Parameter::from_parameter( env, &lib_par, &configured_parameters, )); } } if trampoline.is_some() || async_future.is_some() { warn_main!( type_tid, "{}: Cannot handle callbacks and async parameters at the same time for the \ moment", func.name ); *commented = true; return false; } if !*commented && success_parameters.is_empty() { if success_parameters.is_empty() { warn_main!( type_tid, "{}: missing success parameters for async future", func.name ); } *commented = true; return false; } let is_method = func.kind == FunctionKind::Method; *trampoline = Some(AsyncTrampoline { is_method, has_error_parameter: error_parameters.is_some(), name: format!("{codegen_name}_trampoline"), finish_func_name: format!("{}::{}", env.main_sys_crate_name(), finish_func_name), callback_type, bound_name, output_params, ffi_ret, }); if !no_future { *async_future = Some(AsyncFuture { is_method, name: format!("{}_future", codegen_name.trim_end_matches("_async")), success_parameters, error_parameters, assertion: match SafetyAssertionMode::of(env, is_method, parameters) { SafetyAssertionMode::None => SafetyAssertionMode::None, // "_future" functions calls the "async" one which has the init check, so no // need to do it twice. _ => SafetyAssertionMode::Skip, }, }); } true } else { false } } fn analyze_callback( func_name: &str, type_tid: library::TypeId, env: &Env, par: &CParameter, callback_info: &Option, commented: &mut bool, imports: &mut Imports, c_parameters: &[(&CParameter, usize)], rust_type: &Type, callback_parameters_config: Option<&config::functions::CallbackParameters>, ) -> Option<(Trampoline, Option)> { let mut imports_to_add = Vec::new(); if let Type::Function(func) = rust_type { if par.c_type != "GDestroyNotify" { if let Some(user_data) = par.user_data_index { if user_data >= c_parameters.len() { warn_main!(type_tid, "function `{}` has an invalid user data index of {} when there are {} parameters", func_name, user_data, c_parameters.len()); return None; } else if !is_gpointer(&c_parameters[user_data].0.c_type) { *commented = true; warn_main!( type_tid, "function `{}`'s callback `{}` has invalid user data", func_name, par.name ); return None; } } else { *commented = true; warn_main!( type_tid, "function `{}`'s callback `{}` without associated user data", func_name, par.name ); return None; } if let Some(destroy_index) = par.destroy_index { if destroy_index >= c_parameters.len() { warn_main!( type_tid, "function `{}` has an invalid destroy index of {} when there are {} \ parameters", func_name, destroy_index, c_parameters.len() ); return None; } if c_parameters[destroy_index].0.c_type != "GDestroyNotify" { *commented = true; warn_main!( type_tid, "function `{}`'s callback `{}` has invalid destroy callback", func_name, par.name ); return None; } } } // If we don't have a "user data" parameter, we can't get the closure so there's // nothing we can do... if par.c_type != "GDestroyNotify" && (func.parameters.is_empty() || !func.parameters.iter().any(|c| c.closure.is_some())) { *commented = true; warn_main!( type_tid, "Closure type `{}` doesn't provide user data for function {}", par.c_type, func_name, ); return None; } let parameters = crate::analysis::trampoline_parameters::analyze( env, &func.parameters, par.typ, &[], callback_parameters_config, ); if par.c_type != "GDestroyNotify" && !*commented { *commented |= func.parameters.iter().any(|p| { if p.closure.is_none() { crate::analysis::trampolines::type_error(env, p).is_some() } else { false } }); } for p in ¶meters.rust_parameters { if let Ok(rust_type) = RustType::builder(env, p.typ) .direction(p.direction) .nullable(p.nullable) .try_from_glib(&p.try_from_glib) .try_build() { imports_to_add.extend(rust_type.into_used_types()); } } if let Ok(rust_type) = RustType::builder(env, func.ret.typ) .direction(ParameterDirection::Return) .try_build() { if !rust_type.as_str().ends_with("GString") && !rust_type.as_str().ends_with("GAsyncResult") { imports_to_add.extend(rust_type.into_used_types()); } } let user_data_index = par.user_data_index.unwrap_or(0); if par.c_type != "GDestroyNotify" && c_parameters.len() <= user_data_index { warn_main!( type_tid, "`{}`: Invalid user data index of `{}`", func.name, user_data_index ); *commented = true; None } else if match par.destroy_index { Some(destroy_index) => c_parameters.len() <= destroy_index, None => false, } { warn_main!( type_tid, "`{}`: Invalid destroy index of `{}`", func.name, par.destroy_index.unwrap() ); *commented = true; None } else { if !*commented { for import in imports_to_add { imports.add_used_type(&import); } } Some(( Trampoline { name: par.name.to_string(), parameters, ret: func.ret.clone(), bound_name: match callback_info { Some(x) => x.bound_name.to_string(), None => match RustType::builder(env, par.typ) .direction(par.direction) .nullable(par.nullable) .scope(par.scope) .try_build() { Ok(rust_type) => rust_type.into_string(), Err(_) => { warn_main!(type_tid, "`{}`: unknown type", func.name); return None; } }, }, bounds: Bounds::default(), version: None, inhibit: false, concurrency: library::Concurrency::None, is_notify: false, scope: par.scope, // If destroy callback, id doesn't matter. user_data_index: if par.c_type != "GDestroyNotify" { c_parameters[user_data_index].1 } else { 0 }, destroy_index: 0, nullable: par.nullable, type_name: env.library.type_(type_tid).get_name(), }, par.destroy_index .map(|destroy_index| c_parameters[destroy_index].1), )) } } else { None } } pub fn find_function<'a>(env: &'a Env, c_identifier: &str) -> Option<&'a Function> { let find = |functions: &'a [Function]| -> Option<&'a Function> { for function in functions { if let Some(ref func_c_identifier) = function.c_identifier { if func_c_identifier == c_identifier { return Some(function); } } } None }; if let Some(index) = env.library.find_namespace(&env.config.library_name) { let namespace = env.library.namespace(index); if let Some(f) = find(&namespace.functions) { return Some(f); } for typ in &namespace.types { if let Some(Type::Class(class)) = typ { if let Some(f) = find(&class.functions) { return Some(f); } } else if let Some(Type::Interface(interface)) = typ { if let Some(f) = find(&interface.functions) { return Some(f); } } } } None } /// Given async function name tries to guess the name of finish function. pub fn finish_function_name(mut func_name: &str) -> String { if func_name.ends_with("_async") { let len = func_name.len() - "_async".len(); func_name = &func_name[0..len]; } format!("{}_finish", &func_name) } pub fn find_index_to_ignore<'a>( parameters: impl IntoIterator, ret: Option<&'a library::Parameter>, ) -> Option { parameters .into_iter() .chain(ret) .find(|param| param.array_length.is_some()) .and_then(|param| param.array_length.map(|length| length as usize)) } #[cfg(test)] mod tests { use super::*; #[test] fn test_finish_function_name() { assert_eq!( "g_file_copy_finish", &finish_function_name("g_file_copy_async") ); assert_eq!("g_bus_get_finish", &finish_function_name("g_bus_get")); } } gir-0.20.5/src/analysis/general.rs000066400000000000000000000002551475434152100167640ustar00rootroot00000000000000use crate::{config::gobjects::*, library::*}; #[derive(Debug, Clone)] pub struct StatusedTypeId { pub type_id: TypeId, pub name: String, pub status: GStatus, } gir-0.20.5/src/analysis/imports.rs000066400000000000000000000302101475434152100170360ustar00rootroot00000000000000use std::{ borrow::Cow, cmp::Ordering, collections::{btree_map::BTreeMap, HashSet}, ops::{Deref, DerefMut}, vec::IntoIter, }; use super::namespaces; use crate::{library::Library, nameutil::crate_name, version::Version}; fn is_first_char_up(s: &str) -> bool { s.chars().next().unwrap().is_uppercase() } fn check_up_eq(a: &str, b: &str) -> Ordering { let is_a_up = is_first_char_up(a); let is_b_up = is_first_char_up(b); if is_a_up != is_b_up { if is_a_up { return Ordering::Greater; } return Ordering::Less; } Ordering::Equal } /// This function is used by the `Imports` type to generate output like `cargo /// fmt` would. /// /// For example: /// /// ```text /// use gdk; // lowercases come first. /// use Window; /// /// use gdk::foo; // lowercases come first here as well. /// use gdk::Foo; /// ``` fn compare_imports(a: &(&String, &ImportConditions), b: &(&String, &ImportConditions)) -> Ordering { let s = check_up_eq(a.0, b.0); if s != Ordering::Equal { return s; } let mut a = a.0.split("::"); let mut b = b.0.split("::"); loop { match (a.next(), b.next()) { (Some(a), Some(b)) => { let s = check_up_eq(a, b); if s != Ordering::Equal { break s; } let s = a.partial_cmp(b).unwrap(); if s != Ordering::Equal { break s; } } (Some(_), None) => break Ordering::Greater, (None, Some(_)) => break Ordering::Less, (None, None) => break Ordering::Equal, } } } /// Provides assistance in generating use declarations. /// /// It takes into account that use declaration referring to names within the /// same crate will look differently. It also avoids generating spurious /// declarations referring to names from within the same module as the one we /// are generating code for. #[derive(Clone, Debug, Default)] pub struct Imports { /// Name of the current crate. crate_name: String, /// Names defined within current module. It doesn't need use declaration. defined: HashSet, defaults: ImportConditions, map: BTreeMap, } impl Imports { pub fn new(gir: &Library) -> Self { Self { crate_name: make_crate_name(gir), defined: HashSet::new(), defaults: ImportConditions::default(), map: BTreeMap::new(), } } pub fn with_defined(gir: &Library, name: &str) -> Self { Self { crate_name: make_crate_name(gir), defined: std::iter::once(name.to_owned()).collect(), defaults: ImportConditions::default(), map: BTreeMap::new(), } } #[must_use = "ImportsWithDefault must live while defaults are needed"] pub fn with_defaults( &mut self, version: Option, constraint: &Option, ) -> ImportsWithDefault<'_> { let constraints = if let Some(constraint) = constraint { vec![constraint.clone()] } else { vec![] }; self.defaults = ImportConditions { version, constraints, }; ImportsWithDefault::new(self) } fn reset_defaults(&mut self) { self.defaults.clear(); } /// The goals of this function is to discard unwanted imports like "crate". /// It also extends the checks in case you are implementing "X". For /// example, you don't want to import "X" or "crate::X" in this case. fn common_checks(&self, name: &str) -> bool { if (!name.contains("::") && name != "xlib") || self.defined.contains(name) { false } else if let Some(name) = name.strip_prefix("crate::") { !self.defined.contains(name) } else { true } } /// Declares that `name` is defined in scope /// /// Removes existing imports from `self.map` and marks `name` as /// available to counter future import "requests". pub fn add_defined(&mut self, name: &str) { if self.defined.insert(name.to_owned()) { self.map.remove(name); } } /// Declares that name should be available through its last path component. /// /// For example, if name is `X::Y::Z` then it will be available as `Z`. /// Uses defaults. pub fn add(&mut self, name: &str) { if !self.common_checks(name) { return; } if let Some(mut name) = self.strip_crate_name(name) { if name == "xlib" { name = if self.crate_name == "gdk_x11" { // Dirty little hack to allow to have correct import for GDKX11. Cow::Borrowed("x11::xlib") } else { // gtk has a module named "xlib" which is why this hack is needed too. Cow::Borrowed("crate::xlib") }; } let defaults = &self.defaults; let entry = self .map .entry(name.into_owned()) .or_insert_with(|| defaults.clone()); entry.update_version(self.defaults.version); entry.update_constraints(&self.defaults.constraints); } } /// Declares that name should be available through its last path component. /// /// For example, if name is `X::Y::Z` then it will be available as `Z`. pub fn add_with_version(&mut self, name: &str, version: Option) { if !self.common_checks(name) { return; } if let Some(name) = self.strip_crate_name(name) { let entry = self .map .entry(name.into_owned()) .or_insert(ImportConditions { version, constraints: Vec::new(), }); entry.update_version(version); // Since there is no constraint on this import, if any constraint // is present, we can just remove it. entry.constraints.clear(); } } /// Declares that name should be available through its last path component /// and provides an optional feature constraint. /// /// For example, if name is `X::Y::Z` then it will be available as `Z`. pub fn add_with_constraint( &mut self, name: &str, version: Option, constraint: Option<&str>, ) { if !self.common_checks(name) { return; } if let Some(name) = self.strip_crate_name(name) { let entry = if let Some(constraint) = constraint { let constraint = String::from(constraint); let entry = self .map .entry(name.into_owned()) .or_insert(ImportConditions { version, constraints: vec![constraint.clone()], }); entry.add_constraint(constraint); entry } else { let entry = self .map .entry(name.into_owned()) .or_insert(ImportConditions { version, constraints: Vec::new(), }); // Since there is no constraint on this import, if any constraint // is present, we can just remove it. entry.constraints.clear(); entry }; entry.update_version(version); } } /// Declares that name should be available through its full path. /// /// For example, if name is `X::Y` then it will be available as `X::Y`. pub fn add_used_type(&mut self, used_type: &str) { if let Some(i) = used_type.find("::") { if i == 0 { self.add(&used_type[2..]); } else { self.add(&used_type[..i]); } } else { self.add(&format!("crate::{used_type}")); } } pub fn add_used_types(&mut self, used_types: &[String]) { for s in used_types { self.add_used_type(s); } } /// Declares that name should be available through its full path. /// /// For example, if name is `X::Y` then it will be available as `X::Y`. pub fn add_used_type_with_version(&mut self, used_type: &str, version: Option) { if let Some(i) = used_type.find("::") { if i == 0 { self.add_with_version(&used_type[2..], version); } else { self.add_with_version(&used_type[..i], version); } } else { self.add_with_version(&format!("crate::{used_type}"), version); } } /// Tries to strip crate name prefix from given name. /// /// Returns `None` if name matches crate name exactly. Otherwise returns /// name with crate name prefix stripped or full name if there was no match. fn strip_crate_name<'a>(&self, name: &'a str) -> Option> { let prefix = &self.crate_name; if !name.starts_with(prefix) { return Some(Cow::Borrowed(name)); } let rest = &name[prefix.len()..]; if rest.is_empty() { None } else if rest.starts_with("::") { Some(Cow::Owned(format!("crate{rest}"))) } else { // It was false positive, return the whole name. Some(Cow::Borrowed(name)) } } pub fn iter(&self) -> IntoIter<(&String, &ImportConditions)> { let mut imports = self.map.iter().collect::>(); imports.sort_by(compare_imports); imports.into_iter() } } pub struct ImportsWithDefault<'a> { imports: &'a mut Imports, } impl<'a> ImportsWithDefault<'a> { fn new(imports: &'a mut Imports) -> Self { Self { imports } } } impl Drop for ImportsWithDefault<'_> { fn drop(&mut self) { self.imports.reset_defaults(); } } impl Deref for ImportsWithDefault<'_> { type Target = Imports; fn deref(&self) -> &Self::Target { self.imports } } impl DerefMut for ImportsWithDefault<'_> { fn deref_mut(&mut self) -> &mut Self::Target { self.imports } } #[derive(Clone, Debug, Default, Ord, PartialEq, PartialOrd, Eq)] pub struct ImportConditions { pub version: Option, pub constraints: Vec, } impl ImportConditions { fn clear(&mut self) { self.version = None; self.constraints.clear(); } fn update_version(&mut self, version: Option) { if version < self.version { self.version = version; } } fn add_constraint(&mut self, constraint: String) { // If the import is already present but doesn't have any constraint, // we don't want to add one. if self.constraints.is_empty() { return; } // Otherwise, we just check if the constraint // is already present or not before adding it. if !self.constraints.iter().any(|x| x == &constraint) { self.constraints.push(constraint); } } fn update_constraints(&mut self, constraints: &[String]) { // If the import is already present but doesn't have any constraint, // we don't want to add one. if self.constraints.is_empty() { return; } if constraints.is_empty() { // Since there is no constraint on this import, if any constraint // is present, we can just remove it. self.constraints.clear(); } else { // Otherwise, we just check if the constraint // is already present or not before adding it. for constraint in constraints { if !self.constraints.iter().any(|x| x == constraint) { self.constraints.push(constraint.clone()); } } } } } fn make_crate_name(gir: &Library) -> String { if gir.is_glib_crate() { crate_name("GLib") } else { crate_name(gir.namespace(namespaces::MAIN).name.as_str()) } } gir-0.20.5/src/analysis/info_base.rs000066400000000000000000000023441475434152100172750ustar00rootroot00000000000000use super::{imports::Imports, *}; use crate::{codegen::Visibility, library, version::Version}; #[derive(Debug, Default)] pub struct InfoBase { pub full_name: String, pub type_id: library::TypeId, pub name: String, pub functions: Vec, pub specials: special_functions::Infos, pub imports: Imports, pub version: Option, pub deprecated_version: Option, pub cfg_condition: Option, pub concurrency: library::Concurrency, pub visibility: Visibility, } impl InfoBase { /// TODO: return iterator pub fn constructors(&self) -> Vec<&functions::Info> { self.functions .iter() .filter(|f| f.status.need_generate() && f.kind == library::FunctionKind::Constructor) .collect() } pub fn methods(&self) -> Vec<&functions::Info> { self.functions .iter() .filter(|f| f.status.need_generate() && f.kind == library::FunctionKind::Method) .collect() } pub fn functions(&self) -> Vec<&functions::Info> { self.functions .iter() .filter(|f| f.status.need_generate() && f.kind == library::FunctionKind::Function) .collect() } } gir-0.20.5/src/analysis/mod.rs000066400000000000000000000226171475434152100161340ustar00rootroot00000000000000use std::collections::BTreeMap; use imports::Imports; use log::error; use crate::{ env::Env, library::{self, Type, TypeId}, }; pub mod bounds; pub mod c_type; pub mod child_properties; pub mod class_builder; pub mod class_hierarchy; pub mod constants; pub mod conversion_type; pub mod enums; pub mod ffi_type; pub mod flags; pub mod function_parameters; pub use function_parameters::Parameter; pub mod functions; pub mod general; pub mod imports; pub mod info_base; pub mod namespaces; pub mod object; pub mod out_parameters; mod override_string_type; pub mod properties; pub mod record; pub mod record_type; pub mod ref_mode; pub mod return_value; pub mod rust_type; pub mod safety_assertion_mode; pub mod signals; pub mod signatures; pub mod special_functions; pub mod supertypes; pub mod symbols; pub mod trampoline_parameters; pub mod trampolines; pub mod try_from_glib; pub mod types; #[derive(Debug, Default)] pub struct Analysis { pub objects: BTreeMap, pub records: BTreeMap, pub global_functions: Option, pub constants: Vec, pub enumerations: Vec, pub enum_imports: Imports, pub flags: Vec, pub flags_imports: Imports, } fn find_function<'a>( env: &Env, mut functions: impl Iterator, search_fn: impl Fn(&functions::Info) -> bool + Copy, ) -> Option<&'a functions::Info> { functions.find(|fn_info| fn_info.should_be_doc_linked(env) && search_fn(fn_info)) } impl Analysis { pub fn find_global_function bool + Copy>( &self, env: &Env, search: F, ) -> Option<&functions::Info> { self.global_functions .as_ref() .and_then(move |info| find_function(env, info.functions.iter(), search)) } pub fn find_record_by_function< F: Fn(&functions::Info) -> bool + Copy, G: Fn(&record::Info) -> bool + Copy, >( &self, env: &Env, search_record: G, search_fn: F, ) -> Option<(&record::Info, &functions::Info)> { self.records .values() .filter(|r| search_record(r)) .find_map(|record_info| { find_function(env, record_info.functions.iter(), search_fn) .map(|fn_info| (record_info, fn_info)) }) } pub fn find_object_by_virtual_method< F: Fn(&functions::Info) -> bool + Copy, G: Fn(&object::Info) -> bool + Copy, >( &self, env: &Env, search_obj: G, search_fn: F, ) -> Option<(&object::Info, &functions::Info)> { self.objects .values() .filter(|o| search_obj(o)) .find_map(|obj_info| { find_function(env, obj_info.virtual_methods.iter(), search_fn) .map(|fn_info| (obj_info, fn_info)) }) } pub fn find_object_by_function< F: Fn(&functions::Info) -> bool + Copy, G: Fn(&object::Info) -> bool + Copy, >( &self, env: &Env, search_obj: G, search_fn: F, ) -> Option<(&object::Info, &functions::Info)> { self.objects .values() .filter(|o| search_obj(o)) .find_map(|obj_info| { find_function(env, obj_info.functions.iter(), search_fn) .map(|fn_info| (obj_info, fn_info)) }) } pub fn find_enum_by_function< F: Fn(&functions::Info) -> bool + Copy, G: Fn(&enums::Info) -> bool + Copy, >( &self, env: &Env, search_enum: G, search_fn: F, ) -> Option<(&enums::Info, &functions::Info)> { self.enumerations .iter() .filter(|o| search_enum(o)) .find_map(|obj_info| { find_function(env, obj_info.functions.iter(), search_fn) .map(|fn_info| (obj_info, fn_info)) }) } pub fn find_flag_by_function< F: Fn(&functions::Info) -> bool + Copy, G: Fn(&flags::Info) -> bool + Copy, >( &self, env: &Env, search_flag: G, search_fn: F, ) -> Option<(&flags::Info, &functions::Info)> { self.flags .iter() .filter(|o| search_flag(o)) .find_map(|obj_info| { find_function(env, obj_info.functions.iter(), search_fn) .map(|fn_info| (obj_info, fn_info)) }) } } pub fn run(env: &mut Env) { let mut to_analyze: Vec<(TypeId, Vec)> = Vec::with_capacity(env.config.objects.len()); for obj in env.config.objects.values() { if obj.status.ignored() { continue; } let Some(tid) = env.library.find_type(0, &obj.name) else { continue; }; let deps = supertypes::dependencies(env, tid); to_analyze.push((tid, deps)); } let mut analyzed = 1; while analyzed > 0 { analyzed = 0; let mut new_to_analyze: Vec<(TypeId, Vec)> = Vec::with_capacity(to_analyze.len()); for (tid, ref deps) in to_analyze { if !is_all_deps_analyzed(env, deps) { new_to_analyze.push((tid, deps.clone())); continue; } analyze(env, tid, deps); analyzed += 1; } to_analyze = new_to_analyze; } if !to_analyze.is_empty() { error!( "Not analyzed {} objects due unfinished dependencies", to_analyze.len() ); return; } analyze_enums(env); analyze_flags(env); analyze_constants(env); // Analyze free functions as the last step once all types are analyzed analyze_global_functions(env); } fn analyze_enums(env: &mut Env) { let mut imports = Imports::new(&env.library); for obj in env.config.objects.values() { if obj.status.ignored() { continue; } let Some(tid) = env.library.find_type(0, &obj.name) else { continue; }; if let Type::Enumeration(_) = env.library.type_(tid) { if let Some(info) = enums::new(env, obj, &mut imports) { env.analysis.enumerations.push(info); } } } env.analysis.enum_imports = imports; } fn analyze_flags(env: &mut Env) { let mut imports = Imports::new(&env.library); for obj in env.config.objects.values() { if obj.status.ignored() { continue; } let Some(tid) = env.library.find_type(0, &obj.name) else { continue; }; if let Type::Bitfield(_) = env.library.type_(tid) { if let Some(info) = flags::new(env, obj, &mut imports) { env.analysis.flags.push(info); } } } env.analysis.flags_imports = imports; } fn analyze_global_functions(env: &mut Env) { let ns = env.library.namespace(library::MAIN_NAMESPACE); let full_name = format!("{}.*", ns.name); let obj = match env.config.objects.get(&*full_name) { Some(obj) if obj.status.need_generate() => obj, _ => return, }; let functions: Vec<_> = ns .functions .iter() .filter(|f| f.kind == library::FunctionKind::Global) .collect(); if functions.is_empty() { return; } let mut imports = imports::Imports::new(&env.library); imports.add("glib::translate::*"); imports.add("crate:ffi"); let functions = functions::analyze( env, &functions, None, false, false, obj, &mut imports, None, None, ); env.analysis.global_functions = Some(info_base::InfoBase { full_name, type_id: TypeId::tid_none(), name: "*".into(), functions, imports, ..Default::default() }); } fn analyze_constants(env: &mut Env) { let ns = env.library.namespace(library::MAIN_NAMESPACE); let full_name = format!("{}.*", ns.name); let obj = match env.config.objects.get(&*full_name) { Some(obj) if obj.status.need_generate() => obj, _ => return, }; let constants: Vec<_> = ns.constants.iter().collect(); if constants.is_empty() { return; } env.analysis.constants = constants::analyze(env, &constants, obj); } fn analyze(env: &mut Env, tid: TypeId, deps: &[TypeId]) { let full_name = tid.full_name(&env.library); let Some(obj) = env.config.objects.get(&*full_name) else { return; }; match env.library.type_(tid) { Type::Class(_) => { if let Some(info) = object::class(env, obj, deps) { env.analysis.objects.insert(full_name, info); } } Type::Interface(_) => { if let Some(info) = object::interface(env, obj, deps) { env.analysis.objects.insert(full_name, info); } } Type::Record(_) => { if let Some(info) = record::new(env, obj) { env.analysis.records.insert(full_name, info); } } _ => {} } } fn is_all_deps_analyzed(env: &Env, deps: &[TypeId]) -> bool { for tid in deps { let full_name = tid.full_name(&env.library); if !env.analysis.objects.contains_key(&full_name) { return false; } } true } pub fn is_gpointer(s: &str) -> bool { s == "gpointer" || s == "void*" } gir-0.20.5/src/analysis/namespaces.rs000066400000000000000000000042151475434152100174660ustar00rootroot00000000000000use std::ops::Index; use crate::{library, nameutil, version::Version}; pub type NsId = u16; pub const MAIN: NsId = library::MAIN_NAMESPACE; pub const INTERNAL: NsId = library::INTERNAL_NAMESPACE; #[derive(Debug)] pub struct Namespace { pub name: String, pub crate_name: String, pub sys_crate_name: String, pub higher_crate_name: String, pub package_names: Vec, pub symbol_prefixes: Vec, pub shared_libs: Vec, pub versions: Vec, } #[derive(Debug)] pub struct Info { namespaces: Vec, pub is_glib_crate: bool, pub glib_ns_id: NsId, } impl Info { pub fn main(&self) -> &Namespace { &self[MAIN] } } impl Index for Info { type Output = Namespace; fn index(&self, index: NsId) -> &Namespace { &self.namespaces[index as usize] } } pub fn run(gir: &library::Library) -> Info { let mut namespaces = Vec::with_capacity(gir.namespaces.len()); let mut is_glib_crate = false; let mut glib_ns_id = None; for (ns_id, ns) in gir.namespaces.iter().enumerate() { let ns_id = ns_id as NsId; let crate_name = nameutil::crate_name(&ns.name); let (sys_crate_name, higher_crate_name) = match crate_name.as_str() { "gobject" => ("gobject_ffi".to_owned(), "glib".to_owned()), _ => ("ffi".to_owned(), crate_name.clone()), }; namespaces.push(Namespace { name: ns.name.clone(), crate_name, sys_crate_name, higher_crate_name, package_names: ns.package_names.clone(), symbol_prefixes: ns.symbol_prefixes.clone(), shared_libs: ns.shared_library.clone(), versions: ns.versions.iter().copied().collect(), }); if ns.name == "GLib" { glib_ns_id = Some(ns_id); if ns_id == MAIN { is_glib_crate = true; } } else if ns.name == "GObject" && ns_id == MAIN { is_glib_crate = true; } } Info { namespaces, is_glib_crate, glib_ns_id: glib_ns_id.expect("Missing `GLib` namespace!"), } } gir-0.20.5/src/analysis/object.rs000066400000000000000000000340271475434152100166210ustar00rootroot00000000000000use std::{borrow::Cow, ops::Deref}; use log::info; use super::{ child_properties::ChildProperties, imports::Imports, info_base::InfoBase, signatures::Signatures, *, }; use crate::{ config::gobjects::{GObject, GStatus}, env::Env, library::{self, FunctionKind}, nameutil::*, traits::*, }; /// The location of an item within the object #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum LocationInObject { Impl, VirtualExt, ClassExt, ClassExtManual, Ext, ExtManual, Builder, } #[derive(Debug, Default)] pub struct Info { pub base: InfoBase, pub c_type: String, pub c_class_type: Option, pub get_type: String, pub is_interface: bool, pub is_fundamental: bool, pub supertypes: Vec, pub final_type: bool, pub generate_trait: bool, pub trait_name: String, pub has_constructors: bool, pub has_functions: bool, pub virtual_methods: Vec, pub signals: Vec, pub notify_signals: Vec, pub properties: Vec, pub builder_properties: Vec<(Vec, TypeId)>, pub builder_postprocess: Option, pub child_properties: ChildProperties, pub signatures: Signatures, /// Specific to fundamental types pub ref_fn: Option, /// Specific to fundamental types pub unref_fn: Option, } impl Info { pub fn has_signals(&self) -> bool { self.signals.iter().any(|s| s.trampoline.is_ok()) || self.notify_signals.iter().any(|s| s.trampoline.is_ok()) } /// Whether we should generate an impl block for this object /// We don't generate an impl block if the type doesn't have any of the /// followings: /// - Constructors / Functions / Builder properties (no build function) /// - Is a final type & doesn't have either methods / properties / child /// properties / signals pub fn should_generate_impl_block(&self) -> bool { self.has_constructors || has_builder_properties(&self.builder_properties) || !(self.need_generate_trait() && self.methods().is_empty() && self.properties.is_empty() && self.child_properties.is_empty() && self.signals.is_empty()) || self.has_functions } pub fn need_generate_inherent(&self) -> bool { self.has_constructors || self.has_functions || !self.need_generate_trait() || has_builder_properties(&self.builder_properties) } pub fn need_generate_trait(&self) -> bool { self.generate_trait } pub fn has_action_signals(&self) -> bool { self.signals.iter().any(|s| s.action_emit_name.is_some()) } /// Returns the location of the function within this object pub fn function_location(&self, fn_info: &functions::Info) -> LocationInObject { if fn_info.kind == FunctionKind::ClassMethod { // TODO: Fix location here once we can auto generate virtual methods LocationInObject::ClassExt } else if fn_info.kind == FunctionKind::VirtualMethod { // TODO: Fix location here once we can auto generate virtual methods LocationInObject::VirtualExt } else if self.final_type || self.is_fundamental || matches!( fn_info.kind, FunctionKind::Constructor | FunctionKind::Function ) { LocationInObject::Impl } else if fn_info.status == GStatus::Generate || self.full_name == "GObject.Object" { LocationInObject::Ext } else { LocationInObject::ExtManual } } /// Generate doc name based on function location within this object /// See also [`Self::function_location()`]. /// Returns `(item/crate path including type name, just the type name)` pub fn generate_doc_link_info( &self, fn_info: &functions::Info, ) -> (Cow<'_, str>, Cow<'_, str>) { match self.function_location(fn_info) { LocationInObject::Impl => (self.name.as_str().into(), self.name.as_str().into()), LocationInObject::ExtManual => { let trait_name = format!("{}Manual", self.trait_name); (format!("prelude::{trait_name}").into(), trait_name.into()) } LocationInObject::Ext => ( format!("prelude::{}", self.trait_name).into(), self.trait_name.as_str().into(), ), LocationInObject::VirtualExt => { // TODO: maybe a different config for subclass trait name? let trait_name = format!("{}Impl", self.trait_name.trim_end_matches("Ext")); ( format!("subclass::prelude::{trait_name}").into(), trait_name.into(), ) } LocationInObject::ClassExt | LocationInObject::ClassExtManual => { let trait_name = format!("{}Ext", self.trait_name); ( format!("subclass::prelude::{}", trait_name).into(), trait_name.into(), ) } LocationInObject::Builder => { panic!("C documentation is not expected to link to builders (a Rust concept)!") } } } } impl Deref for Info { type Target = InfoBase; fn deref(&self) -> &InfoBase { &self.base } } pub fn has_builder_properties(builder_properties: &[(Vec, TypeId)]) -> bool { builder_properties .iter() .map(|b| b.0.iter().len()) .sum::() > 0 } pub fn class(env: &Env, obj: &GObject, deps: &[library::TypeId]) -> Option { info!("Analyzing class {}", obj.name); let full_name = obj.name.clone(); let class_tid = env.library.find_type(0, &full_name)?; let type_ = env.type_(class_tid); let name: String = split_namespace_name(&full_name).1.into(); let klass: &library::Class = type_.maybe_ref()?; let version = obj.version.or(klass.version); let deprecated_version = klass.deprecated_version; let mut imports = Imports::with_defined(&env.library, &name); let is_fundamental = obj.fundamental_type.unwrap_or(klass.is_fundamental); let supertypes = supertypes::analyze(env, class_tid, version, &mut imports, is_fundamental); let supertypes_properties = supertypes .iter() .filter_map(|t| match env.type_(t.type_id) { Type::Class(c) => Some(&c.properties), Type::Interface(i) => Some(&i.properties), _ => None, }) .flatten() .collect::>(); let final_type = klass.final_type; let trait_name = obj .trait_name .as_ref() .cloned() .unwrap_or_else(|| format!("{name}Ext")); let mut signatures = Signatures::with_capacity(klass.functions.len()); // As we don't generate virtual methods yet, we don't pass imports here // it would need to be fixed once work in generating virtual methods is done let virtual_methods = functions::analyze( env, &klass.virtual_methods, Some(class_tid), true, false, obj, &mut Imports::default(), None, Some(deps), ); let mut functions = functions::analyze( env, &klass.functions, Some(class_tid), !final_type, false, obj, &mut imports, Some(&mut signatures), Some(deps), ); let mut specials = special_functions::extract(&mut functions, type_, obj); // `copy` will duplicate an object while `clone` just adds a reference special_functions::unhide(&mut functions, &specials, special_functions::Type::Copy); // these are all automatically derived on objects and compare by pointer. If // such functions exist they will provide additional functionality for t in &[ special_functions::Type::Hash, special_functions::Type::Equal, special_functions::Type::Compare, ] { special_functions::unhide(&mut functions, &specials, *t); specials.traits_mut().remove(t); } special_functions::analyze_imports(&specials, &mut imports); let signals = signals::analyze( env, &klass.signals, class_tid, !final_type, is_fundamental, obj, &mut imports, ); let (properties, notify_signals) = properties::analyze( env, &klass.properties, &supertypes_properties, class_tid, !final_type, is_fundamental, obj, &mut imports, &signatures, deps, &functions, ); let builder_properties = class_builder::analyze(env, &klass.properties, class_tid, obj, &mut imports); let child_properties = child_properties::analyze(env, obj.child_properties.as_ref(), class_tid, &mut imports); let has_methods = functions .iter() .any(|f| f.kind == library::FunctionKind::Method && f.status.need_generate()); let has_signals = signals.iter().any(|s| s.trampoline.is_ok()) || notify_signals.iter().any(|s| s.trampoline.is_ok()); // There's no point in generating a trait if there are no signals, methods, // properties and child properties: it would be empty // // There's also no point in generating a trait for final types: there are no // possible subtypes let generate_trait = !final_type && !is_fundamental && (has_signals || has_methods || !properties.is_empty() || !child_properties.is_empty()); imports.add("crate::ffi"); if is_fundamental { imports.add("glib::translate::*"); imports.add("glib::prelude::*"); } if has_builder_properties(&builder_properties) { imports.add("glib::prelude::*"); } if generate_trait { imports.add("glib::prelude::*"); } let base = InfoBase { full_name, type_id: class_tid, name, functions, specials, imports, version, deprecated_version, cfg_condition: obj.cfg_condition.clone(), concurrency: obj.concurrency, visibility: obj.visibility, }; // patch up trait methods in the symbol table if generate_trait { let mut symbols = env.symbols.borrow_mut(); for func in base.methods() { if let Some(symbol) = symbols.by_c_name_mut(&func.glib_name) { symbol.make_trait_method(&trait_name); } } } let has_constructors = !base.constructors().is_empty(); let has_functions = !base.functions().is_empty(); let info = Info { base, c_type: klass.c_type.clone(), c_class_type: klass.c_class_type.clone(), get_type: klass.glib_get_type.clone(), is_interface: false, is_fundamental, supertypes, final_type, generate_trait, trait_name, has_constructors, has_functions, virtual_methods, signals, notify_signals, properties, builder_properties, builder_postprocess: obj.builder_postprocess.clone(), child_properties, signatures, ref_fn: klass.ref_fn.clone(), unref_fn: klass.unref_fn.clone(), }; Some(info) } pub fn interface(env: &Env, obj: &GObject, deps: &[library::TypeId]) -> Option { info!("Analyzing interface {}", obj.name); let full_name = obj.name.clone(); let iface_tid = env.library.find_type(0, &full_name)?; let type_ = env.type_(iface_tid); let name: String = split_namespace_name(&full_name).1.into(); let iface: &library::Interface = type_.maybe_ref()?; let version = obj.version.or(iface.version); let deprecated_version = iface.deprecated_version; let mut imports = Imports::with_defined(&env.library, &name); imports.add("glib::prelude::*"); imports.add("crate::ffi"); let supertypes = supertypes::analyze(env, iface_tid, version, &mut imports, false); let supertypes_properties = supertypes .iter() .filter_map(|t| match env.type_(t.type_id) { Type::Class(c) => Some(&c.properties), Type::Interface(i) => Some(&i.properties), _ => None, }) .flatten() .collect::>(); let trait_name = obj .trait_name .as_ref() .cloned() .unwrap_or_else(|| format!("{name}Ext")); let mut signatures = Signatures::with_capacity(iface.functions.len()); let functions = functions::analyze( env, &iface.functions, Some(iface_tid), true, false, obj, &mut imports, Some(&mut signatures), Some(deps), ); let signals = signals::analyze( env, &iface.signals, iface_tid, true, false, obj, &mut imports, ); let (properties, notify_signals) = properties::analyze( env, &iface.properties, &supertypes_properties, iface_tid, true, false, obj, &mut imports, &signatures, deps, &functions, ); let base = InfoBase { full_name, type_id: iface_tid, name, functions, specials: Default::default(), imports, version, deprecated_version, cfg_condition: obj.cfg_condition.clone(), concurrency: obj.concurrency, visibility: obj.visibility, }; let has_functions = !base.functions().is_empty(); let info = Info { base, c_type: iface.c_type.clone(), c_class_type: iface.c_class_type.clone(), get_type: iface.glib_get_type.clone(), is_interface: true, supertypes, final_type: false, generate_trait: true, trait_name, has_functions, signals, notify_signals, properties, signatures, ..Default::default() }; Some(info) } gir-0.20.5/src/analysis/out_parameters.rs000066400000000000000000000135441475434152100204060ustar00rootroot00000000000000use std::slice::Iter; use log::error; use crate::{ analysis::{ self, conversion_type::ConversionType, function_parameters::CParameter, functions::is_carray_with_direct_elements, return_value, rust_type::RustType, }, config::{self, parameter_matchable::ParameterMatchable}, env::Env, library::{ self, Basic, Function, Nullable, ParameterDirection, Type, TypeId, INTERNAL_NAMESPACE, }, nameutil, }; #[derive(Default, Clone, Copy, Debug, Eq, PartialEq)] pub enum ThrowFunctionReturnStrategy { #[default] ReturnResult, CheckError, Void, } #[derive(Default, Clone, Copy, Debug, Eq, PartialEq)] pub enum Mode { #[default] None, Normal, Optional, Combined, Throws(ThrowFunctionReturnStrategy), } #[derive(Debug, Default)] pub struct Info { pub mode: Mode, pub params: Vec, } impl Info { pub fn is_empty(&self) -> bool { self.mode == Mode::None } pub fn iter(&self) -> Iter<'_, analysis::Parameter> { self.params.iter() } } pub fn analyze( env: &Env, func: &Function, func_c_params: &[CParameter], func_ret: &return_value::Info, configured_functions: &[&config::functions::Function], ) -> (Info, bool) { let mut info: Info = Default::default(); let mut unsupported_outs = false; let nullable_override = configured_functions.iter().find_map(|f| f.ret.nullable); if func.throws { let return_strategy = decide_throw_function_return_strategy(env, func_ret, &func.name, configured_functions); info.mode = Mode::Throws(return_strategy); } else if func.ret.typ == TypeId::tid_none() { info.mode = Mode::Normal; } else if func.ret.typ == TypeId::tid_bool() || func.ret.typ == TypeId::tid_c_bool() { if nullable_override == Some(Nullable(false)) { info.mode = Mode::Combined; } else { info.mode = Mode::Optional; } } else { info.mode = Mode::Combined; } for lib_par in &func.parameters { if lib_par.direction != ParameterDirection::Out { continue; } if can_as_return(env, lib_par) { let mut lib_par = lib_par.clone(); lib_par.name = nameutil::mangle_keywords(&lib_par.name).into_owned(); let configured_parameters = configured_functions.matched_parameters(&lib_par.name); let mut out = analysis::Parameter::from_parameter(env, &lib_par, &configured_parameters); // FIXME: temporary solution for string_type, nullable override. This should // completely work based on the analyzed parameters instead of the // library parameters. if let Some(c_par) = func_c_params .iter() .find(|c_par| c_par.name == lib_par.name) { out.lib_par.typ = c_par.typ; out.lib_par.nullable = c_par.nullable; } info.params.push(out); } else { unsupported_outs = true; } } if info.params.is_empty() { info.mode = Mode::None; } if info.mode == Mode::Combined || info.mode == Mode::Throws(ThrowFunctionReturnStrategy::ReturnResult) { let mut ret = analysis::Parameter::from_return_value(env, &func.ret, configured_functions); // TODO: fully switch to use analyzed returns (it add too many Return>) if let Some(ref par) = func_ret.parameter { ret.lib_par.typ = par.lib_par.typ; } if let Some(val) = nullable_override { ret.lib_par.nullable = val; } info.params.insert(0, ret); } (info, unsupported_outs) } pub fn can_as_return(env: &Env, par: &library::Parameter) -> bool { use super::conversion_type::ConversionType::*; match ConversionType::of(env, par.typ) { Direct | Scalar | Option | Result { .. } => true, Pointer => { // Disallow Basic arrays without length if is_carray_with_direct_elements(env, par.typ) && par.array_length.is_none() { return false; } RustType::builder(env, par.typ) .direction(ParameterDirection::Out) .scope(par.scope) .try_build_param() .is_ok() } Borrow => false, Unknown => false, } } fn decide_throw_function_return_strategy( env: &Env, ret: &return_value::Info, func_name: &str, configured_functions: &[&config::functions::Function], ) -> ThrowFunctionReturnStrategy { let typ = ret .parameter .as_ref() .map(|par| par.lib_par.typ) .unwrap_or_default(); if env.type_(typ).eq(&Type::Basic(Basic::None)) { ThrowFunctionReturnStrategy::Void } else if use_function_return_for_result(env, typ, func_name, configured_functions) { ThrowFunctionReturnStrategy::ReturnResult } else { ThrowFunctionReturnStrategy::CheckError } } pub fn use_function_return_for_result( env: &Env, typ: TypeId, func_name: &str, configured_functions: &[&config::functions::Function], ) -> bool { // Configuration takes precedence over everything. let use_return_for_result = configured_functions .iter() .find_map(|f| f.ret.use_return_for_result.as_ref()); if let Some(use_return_for_result) = use_return_for_result { if typ == Default::default() { error!("Function \"{}\": use_return_for_result set to true, but function has no return value", func_name); return false; } return *use_return_for_result; } if typ == Default::default() { return false; } if typ.ns_id != INTERNAL_NAMESPACE { return true; } let type_ = env.type_(typ); !matches!(&*type_.get_name(), "UInt" | "Boolean" | "Bool") } gir-0.20.5/src/analysis/override_string_type.rs000066400000000000000000000032411475434152100216130ustar00rootroot00000000000000use log::error; use crate::{config, env::Env, library::*}; pub fn override_string_type_parameter( env: &Env, typ: TypeId, configured_parameters: &[&config::functions::Parameter], ) -> TypeId { let string_type = configured_parameters.iter().find_map(|p| p.string_type); apply(env, typ, string_type) } pub fn override_string_type_return( env: &Env, typ: TypeId, configured_functions: &[&config::functions::Function], ) -> TypeId { let string_type = configured_functions.iter().find_map(|f| f.ret.string_type); apply(env, typ, string_type) } fn apply(env: &Env, type_id: TypeId, string_type: Option) -> TypeId { let string_type = if let Some(string_type) = string_type { string_type } else { return type_id; }; let replace = { use crate::config::StringType::*; match string_type { Utf8 => TypeId::tid_utf8(), Filename => TypeId::tid_filename(), OsString => TypeId::tid_os_string(), } }; match *env.library.type_(type_id) { Type::Basic(Basic::Filename | Basic::OsString | Basic::Utf8) => replace, Type::CArray(inner_tid) if can_overridden_basic(env, inner_tid) => { Type::find_c_array(&env.library, replace, None) } _ => { error!( "Bad type {0} for string_type override", type_id.full_name(&env.library) ); type_id } } } fn can_overridden_basic(env: &Env, type_id: TypeId) -> bool { matches!( *env.library.type_(type_id), Type::Basic(Basic::Filename | Basic::OsString | Basic::Utf8) ) } gir-0.20.5/src/analysis/properties.rs000066400000000000000000000361541475434152100175520ustar00rootroot00000000000000use log::warn; use crate::{ analysis::{ bounds::{Bounds, PropertyBound}, imports::Imports, ref_mode::RefMode, rust_type::RustType, signals, signatures::{Signature, Signatures}, trampolines, }, config::{self, gobjects::GStatus, GObject, PropertyGenerateFlags}, env::Env, library, nameutil, traits::*, version::Version, }; #[derive(Debug)] pub struct Property { pub name: String, pub var_name: String, pub typ: library::TypeId, pub is_get: bool, pub func_name: String, pub func_name_alias: Option, pub nullable: library::Nullable, pub get_out_ref_mode: RefMode, pub set_in_ref_mode: RefMode, pub bounds: Bounds, pub set_bound: Option, pub version: Option, pub deprecated_version: Option, } pub fn analyze( env: &Env, props: &[library::Property], supertypes_props: &[&library::Property], type_tid: library::TypeId, generate_trait: bool, is_fundamental: bool, obj: &GObject, imports: &mut Imports, signatures: &Signatures, deps: &[library::TypeId], functions: &[crate::analysis::functions::Info], ) -> (Vec, Vec) { let mut properties = Vec::new(); let mut notify_signals = Vec::new(); for prop in props { let configured_properties = obj.properties.matched(&prop.name); if !configured_properties .iter() .all(|f| f.status.need_generate()) { continue; } if env.is_totally_deprecated(Some(type_tid.ns_id), prop.deprecated_version) { continue; } if supertypes_props .iter() .any(|p| p.name == prop.name && p.typ == prop.typ) { continue; } let (getter, setter, notify_signal) = analyze_property( env, prop, type_tid, &configured_properties, generate_trait, is_fundamental, obj, imports, signatures, deps, functions, ); if let Some(notify_signal) = notify_signal { notify_signals.push(notify_signal); } if let Some(prop) = getter { properties.push(prop); } if let Some(prop) = setter { properties.push(prop); } } (properties, notify_signals) } fn analyze_property( env: &Env, prop: &library::Property, type_tid: library::TypeId, configured_properties: &[&config::properties::Property], generate_trait: bool, is_fundamental: bool, obj: &GObject, imports: &mut Imports, signatures: &Signatures, deps: &[library::TypeId], functions: &[crate::analysis::functions::Info], ) -> (Option, Option, Option) { let type_name = type_tid.full_name(&env.library); let name = prop.name.clone(); let prop_version = configured_properties .iter() .filter_map(|f| f.version) .min() .or(prop.version) .or(Some(env.config.min_cfg_version)); let generate = configured_properties.iter().find_map(|f| f.generate); let generate_set = generate.is_some(); let generate = generate.unwrap_or_else(PropertyGenerateFlags::all); let imports = &mut imports.with_defaults(prop_version, &None); imports.add("glib::translate::*"); let type_string = RustType::try_new(env, prop.typ); let name_for_func = nameutil::signal_to_snake(&name); let mut get_prop_name = Some(format!("get_property_{name_for_func}")); let bypass_auto_rename = configured_properties.iter().any(|f| f.bypass_auto_rename); let (check_get_func_names, mut get_func_name) = if bypass_auto_rename { ( vec![format!("get_{name_for_func}")], get_prop_name.take().expect("defined 10 lines above"), ) } else { get_func_name(&name_for_func, prop.typ == library::TypeId::tid_bool()) }; let mut set_func_name = format!("set_{name_for_func}"); let mut set_prop_name = Some(format!("set_property_{name_for_func}")); let mut readable = prop.readable; let mut writable = if prop.construct_only { false } else { prop.writable }; let mut notifiable = !prop.construct_only; if generate_set && generate.contains(PropertyGenerateFlags::GET) && !readable { warn!( "Attempt to generate getter for notreadable property \"{}.{}\"", type_name, name ); } if generate_set && generate.contains(PropertyGenerateFlags::SET) && !writable { warn!( "Attempt to generate setter for nonwritable property \"{}.{}\"", type_name, name ); } readable &= generate.contains(PropertyGenerateFlags::GET); writable &= generate.contains(PropertyGenerateFlags::SET); if generate_set { notifiable = generate.contains(PropertyGenerateFlags::NOTIFY); } if readable { for check_get_func_name in check_get_func_names { let (has, version) = Signature::has_for_property( env, &check_get_func_name, true, prop.typ, signatures, deps, ); if has { // There is a matching get func if env.is_totally_deprecated(Some(type_tid.ns_id), version) || version <= prop_version { // And its availability covers the property's availability // => don't generate the get property. readable = false; } else { // The property is required in earlier versions than the getter // => we need both, but there will be a name clash due to auto renaming // => keep the get_property name. if let Some(get_prop_name) = get_prop_name.take() { get_func_name = get_prop_name; } } } } } if writable { let (has, version) = Signature::has_for_property(env, &set_func_name, false, prop.typ, signatures, deps); if has { // There is a matching set func if env.is_totally_deprecated(Some(type_tid.ns_id), version) || version <= prop_version { // And its availability covers the property's availability // => don't generate the set property. writable = false; } else { // The property is required in earlier versions than the setter // => we need both, but there will be a name clash due to auto renaming // => keep the set_property name. if let Some(set_prop_name) = set_prop_name.take() { set_func_name = set_prop_name; } } } } let (get_out_ref_mode, set_in_ref_mode, nullable) = get_property_ref_modes(env, prop); let getter_func = functions.iter().find(|f| { f.get_property.as_ref() == Some(&prop.name) && Some(&f.name) == prop.getter.as_ref() }); let setter_func = functions.iter().find(|f| { f.set_property.as_ref() == Some(&prop.name) && Some(&f.name) == prop.setter.as_ref() }); let has_getter = getter_func.is_some_and(|g| matches!(g.status, GStatus::Generate | GStatus::Manual)); let has_setter = setter_func.is_some_and(|s| matches!(s.status, GStatus::Generate | GStatus::Manual)); let getter = if readable && (!has_getter || prop.version < getter_func.and_then(|g| g.version)) { if let Ok(rust_type) = RustType::builder(env, prop.typ) .direction(library::ParameterDirection::Out) .try_build() { imports.add_used_types(rust_type.used_types()); } if type_string.is_ok() { imports.add("glib::prelude::*"); } let mut getter_version = prop_version; if has_getter { let getter = getter_func.unwrap(); get_func_name = getter.new_name.as_ref().unwrap_or(&getter.name).to_string(); get_prop_name = Some(getter.name.clone()); getter_version = getter.version.map(|mut g| { g.as_opposite(); g }); } Some(Property { name: name.clone(), var_name: nameutil::mangle_keywords(&*name_for_func).into_owned(), typ: prop.typ, is_get: true, func_name: get_func_name, func_name_alias: get_prop_name, nullable, get_out_ref_mode, set_in_ref_mode, set_bound: None, bounds: Bounds::default(), version: getter_version, deprecated_version: prop.deprecated_version, }) } else { None }; let setter = if writable && (!has_setter || prop.version < setter_func.and_then(|s| s.version)) { if let Ok(rust_type) = RustType::builder(env, prop.typ) .direction(library::ParameterDirection::In) .try_build() { imports.add_used_types(rust_type.used_types()); } if type_string.is_ok() { imports.add("glib::prelude::*"); } let set_bound = PropertyBound::get(env, prop.typ); if type_string.is_ok() && set_bound.is_some() { imports.add("glib::prelude::*"); if !*nullable { // TODO: support non-nullable setter if found any warn!( "Non nullable setter for property generated as nullable \"{}.{}\"", type_name, name ); } } let mut setter_version = prop_version; if has_setter { let setter = setter_func.unwrap(); set_func_name = setter.new_name.as_ref().unwrap_or(&setter.name).to_string(); set_prop_name = Some(setter.name.clone()); setter_version = setter.version.map(|mut s| { s.as_opposite(); s }); } Some(Property { name: name.clone(), var_name: nameutil::mangle_keywords(&*name_for_func).into_owned(), typ: prop.typ, is_get: false, func_name: set_func_name, func_name_alias: set_prop_name, nullable, get_out_ref_mode, set_in_ref_mode, set_bound, bounds: Bounds::default(), version: setter_version, deprecated_version: prop.deprecated_version, }) } else { None }; if !generate_trait && (writable || readable || notifiable) { imports.add("glib::prelude::*"); } let notify_signal = if notifiable { let mut used_types: Vec = Vec::with_capacity(4); let trampoline = trampolines::analyze( env, &library::Signal { name: format!("notify::{name}"), parameters: Vec::new(), ret: library::Parameter { name: String::new(), typ: env .library .find_type(library::INTERNAL_NAMESPACE, "none") .unwrap(), c_type: "none".into(), instance_parameter: false, direction: library::ParameterDirection::Return, transfer: library::Transfer::None, caller_allocates: false, nullable: library::Nullable(false), array_length: None, is_error: false, doc: None, scope: library::ParameterScope::None, closure: None, destroy: None, }, is_action: false, is_detailed: false, /* well, technically this *is* an instance of a detailed * signal, but we "pre-detailed" it */ version: prop_version, deprecated_version: prop.deprecated_version, doc: None, doc_deprecated: None, }, type_tid, generate_trait, is_fundamental, &[], obj, &mut used_types, prop_version, ); if trampoline.is_ok() { imports.add_used_types(&used_types); if generate_trait { imports.add("glib::prelude::*"); } imports.add("glib::signal::{connect_raw, SignalHandlerId}"); imports.add("std::boxed::Box as Box_"); Some(signals::Info { connect_name: format!("connect_{name_for_func}_notify"), signal_name: format!("notify::{name}"), trampoline, action_emit_name: None, version: prop_version, deprecated_version: prop.deprecated_version, doc_hidden: false, is_detailed: false, // see above comment generate_doc: obj.generate_doc, }) } else { None } } else { None }; (getter, setter, notify_signal) } /// Returns (the list of get functions to check, the desired get function name). fn get_func_name(prop_name: &str, is_bool_getter: bool) -> (Vec, String) { let get_rename_res = getter_rules::try_rename_getter_suffix(prop_name, is_bool_getter); match get_rename_res { Ok(new_name) => { let new_name = new_name.unwrap(); let mut check_get_func_names = vec![ format!("get_{prop_name}"), prop_name.to_string(), format!("get_{new_name}"), new_name.clone(), ]; if is_bool_getter { check_get_func_names.push(format!("is_{prop_name}")); check_get_func_names.push(format!("is_{new_name}")); } (check_get_func_names, new_name) } Err(_) => { let mut check_get_func_names = vec![format!("get_{prop_name}"), prop_name.to_string()]; // Name is reserved let get_func_name = if is_bool_getter { let get_func_name = format!("is_{prop_name}"); check_get_func_names.push(get_func_name.clone()); get_func_name } else { format!("get_{prop_name}") }; (check_get_func_names, get_func_name) } } } pub fn get_property_ref_modes( env: &Env, prop: &library::Property, ) -> (RefMode, RefMode, library::Nullable) { let get_out_ref_mode = RefMode::of(env, prop.typ, library::ParameterDirection::Return); let mut set_in_ref_mode = RefMode::of(env, prop.typ, library::ParameterDirection::In); if set_in_ref_mode == RefMode::ByRefMut { set_in_ref_mode = RefMode::ByRef; } let nullable = library::Nullable(set_in_ref_mode.is_ref()); (get_out_ref_mode, set_in_ref_mode, nullable) } gir-0.20.5/src/analysis/record.rs000066400000000000000000000151541475434152100166310ustar00rootroot00000000000000use std::ops::Deref; use log::info; use super::{imports::Imports, info_base::InfoBase, record_type::RecordType, *}; use crate::{ config::{ derives::{Derive, Derives}, gobjects::GObject, }, env::Env, library, nameutil::*, traits::*, version::Version, }; #[derive(Debug, Default)] pub struct Info { pub base: InfoBase, pub glib_get_type: Option<(String, Option)>, pub is_boxed: bool, pub derives: Derives, pub boxed_inline: bool, pub init_function_expression: Option, pub copy_into_function_expression: Option, pub clear_function_expression: Option, } impl Deref for Info { type Target = InfoBase; fn deref(&self) -> &InfoBase { &self.base } } impl Info { // TODO: add test in tests/ for panic pub fn type_<'a>(&self, library: &'a library::Library) -> &'a library::Record { let type_ = library .type_(self.type_id) .maybe_ref() .unwrap_or_else(|| panic!("{} is not a record.", self.full_name)); type_ } } fn filter_derives(derives: &[Derive], names: &[&str]) -> Derives { derives .iter() .filter_map(|derive| { let new_names = derive .names .iter() .filter(|n| !names.contains(&n.as_str())) .map(Clone::clone) .collect::>(); if !new_names.is_empty() { Some(Derive { names: new_names, cfg_condition: derive.cfg_condition.clone(), }) } else { None } }) .collect() } pub fn new(env: &Env, obj: &GObject) -> Option { info!("Analyzing record {}", obj.name); let full_name = obj.name.clone(); let record_tid = env.library.find_type(0, &full_name)?; let type_ = env.type_(record_tid); let name: String = split_namespace_name(&full_name).1.into(); let record: &library::Record = type_.maybe_ref()?; let is_boxed = matches!( RecordType::of(record), RecordType::Boxed | RecordType::AutoBoxed ); let boxed_inline = obj.boxed_inline; let mut imports = Imports::with_defined(&env.library, &name); imports.add("crate::ffi"); let mut functions = functions::analyze( env, &record.functions, Some(record_tid), false, is_boxed, obj, &mut imports, None, None, ); let specials = special_functions::extract(&mut functions, type_, obj); let version = obj.version.or(record.version); let deprecated_version = record.deprecated_version; let is_shared = specials.has_trait(special_functions::Type::Ref) && specials.has_trait(special_functions::Type::Unref); if is_shared { // `copy` will duplicate a struct while `clone` just adds a reference special_functions::unhide(&mut functions, &specials, special_functions::Type::Copy); }; let mut derives = if let Some(ref derives) = obj.derives { if boxed_inline && !derives.is_empty() && !derives .iter() .all(|ds| ds.names.is_empty() || ds.names.iter().all(|n| n == "Debug")) { panic!("Can't automatically derive traits other than `Debug` for BoxedInline records"); } derives.clone() } else if !boxed_inline { let derives = vec![Derive { names: vec![ "Debug".into(), "PartialEq".into(), "Eq".into(), "PartialOrd".into(), "Ord".into(), "Hash".into(), ], cfg_condition: None, }]; derives } else { // boxed_inline vec![] }; for special in specials.traits().keys() { match special { special_functions::Type::Compare => { derives = filter_derives(&derives, &["PartialOrd", "Ord", "PartialEq", "Eq"]); } special_functions::Type::Equal => { derives = filter_derives(&derives, &["PartialEq", "Eq"]); } special_functions::Type::Hash => { derives = filter_derives(&derives, &["Hash"]); } _ => (), } } special_functions::analyze_imports(&specials, &mut imports); let glib_get_type = if let Some(ref glib_get_type) = record.glib_get_type { let configured_functions = obj.functions.matched("get_type"); let get_type_version = configured_functions .iter() .map(|f| f.version) .max() .flatten(); Some((glib_get_type.clone(), get_type_version)) } else { None }; // Check if we have to make use of the GType and the generic // boxed functions. if !is_shared && (!specials.has_trait(special_functions::Type::Copy) || !specials.has_trait(special_functions::Type::Free)) { if let Some((_, get_type_version)) = glib_get_type { // FIXME: Ideally we would update it here but that requires fixing *all* the // versions of functions in this and other types that use this type somewhere in // the signature. Similar code exists for before the analysis already but that // doesn't apply directly here. // // As the get_type function only has a version if explicitly configured let's // just panic here. It's easy enough for the user to move the // version configuration from the function to the type. assert!( get_type_version <= version, "Have to use get_type function for {full_name} but version is higher than for the type ({get_type_version:?} > {version:?})" ); } else { error!("Missing memory management functions for {}", full_name); } } let base = InfoBase { full_name, type_id: record_tid, name, functions, specials, imports, version, deprecated_version, cfg_condition: obj.cfg_condition.clone(), concurrency: obj.concurrency, visibility: obj.visibility, }; let info = Info { base, glib_get_type, derives, is_boxed, boxed_inline, init_function_expression: obj.init_function_expression.clone(), copy_into_function_expression: obj.copy_into_function_expression.clone(), clear_function_expression: obj.clear_function_expression.clone(), }; Some(info) } gir-0.20.5/src/analysis/record_type.rs000066400000000000000000000012471475434152100176700ustar00rootroot00000000000000use crate::library; #[derive(PartialEq, Eq)] pub enum RecordType { /// Boxed record that use g_boxed_copy, g_boxed_free. /// Must have glib_get_type function AutoBoxed, /// Boxed record with custom copy/free functions Boxed, /// Referencecounted record Refcounted, // TODO: detect and generate direct records // Direct, } impl RecordType { pub fn of(record: &library::Record) -> RecordType { if record.has_ref() && record.has_unref() { RecordType::Refcounted } else if record.has_copy() && record.has_free() { RecordType::Boxed } else { RecordType::AutoBoxed } } } gir-0.20.5/src/analysis/ref_mode.rs000066400000000000000000000065041475434152100171320ustar00rootroot00000000000000use std::str::FromStr; use super::{c_type::is_mut_ptr, record_type::RecordType}; use crate::{config::gobjects::GObject, env, library}; #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub enum RefMode { None, ByRef, ByRefMut, ByRefImmut, // immutable reference with mutable pointer in sys ByRefConst, // instance parameters in trait function with const pointer in sys ByRefFake, } impl FromStr for RefMode { type Err = String; fn from_str(s: &str) -> Result { match s { "none" => Ok(Self::None), "ref" => Ok(Self::ByRef), "ref-mut" => Ok(Self::ByRefMut), "ref-immut" => Ok(Self::ByRefImmut), "ref-fake" => Ok(Self::ByRefFake), name => Err(format!("Unknown reference mode '{name}'")), } } } impl RefMode { #[inline] pub fn of( env: &env::Env, tid: library::TypeId, direction: library::ParameterDirection, ) -> Self { use crate::library::Type::*; let library = &env.library; if let Some(&GObject { ref_mode: Some(ref_mode), .. }) = env.config.objects.get(&tid.full_name(library)) { if direction == library::ParameterDirection::In { return ref_mode; } else { return Self::None; } } match library.type_(tid) { Basic(library::Basic::Utf8 | library::Basic::Filename | library::Basic::OsString) | Class(..) | Interface(..) | List(..) | SList(..) | PtrArray(..) | CArray(..) => { if direction == library::ParameterDirection::In { Self::ByRef } else { Self::None } } Record(record) => { if direction == library::ParameterDirection::In { if let RecordType::Refcounted = RecordType::of(record) { Self::ByRef } else { Self::ByRefMut } } else { Self::None } } Union(..) => { if direction == library::ParameterDirection::In { Self::ByRefMut } else { Self::None } } Alias(alias) => Self::of(env, alias.typ, direction), _ => Self::None, } } pub fn without_unneeded_mut( env: &env::Env, par: &library::Parameter, immutable: bool, self_in_trait: bool, ) -> Self { let ref_mode = Self::of(env, par.typ, par.direction); match ref_mode { Self::ByRefMut if !is_mut_ptr(&par.c_type) => Self::ByRef, Self::ByRefMut if immutable => Self::ByRefImmut, Self::ByRef if self_in_trait && !is_mut_ptr(&par.c_type) => Self::ByRefConst, ref_mode => ref_mode, } } pub fn is_ref(self) -> bool { match self { Self::None => false, Self::ByRef => true, Self::ByRefMut => true, Self::ByRefImmut => true, Self::ByRefConst => true, Self::ByRefFake => true, } } } gir-0.20.5/src/analysis/return_value.rs000066400000000000000000000120401475434152100200550ustar00rootroot00000000000000use log::error; use crate::{ analysis::{ self, imports::Imports, namespaces, override_string_type::override_string_type_return, rust_type::RustType, }, config, env::Env, library::{self, Nullable, TypeId}, }; #[derive(Clone, Debug, Default)] pub struct Info { pub parameter: Option, pub base_tid: Option, // Some only if need downcast pub commented: bool, pub bool_return_is_error: Option, pub nullable_return_is_error: Option, } pub fn analyze( env: &Env, obj: &config::gobjects::GObject, func: &library::Function, type_tid: library::TypeId, configured_functions: &[&config::functions::Function], used_types: &mut Vec, imports: &mut Imports, ) -> Info { let typ = configured_functions .iter() .find_map(|f| f.ret.type_name.as_ref()) .and_then(|typ| env.library.find_type(0, typ)) .unwrap_or_else(|| override_string_type_return(env, func.ret.typ, configured_functions)); let mut parameter = if typ == Default::default() { None } else { let mut nullable = func.ret.nullable; if !obj.trust_return_value_nullability { // Since GIRs are bad at specifying return value nullability, assume // any returned pointer is nullable unless overridden by the config. if !*nullable && can_be_nullable_return(env, typ) { *nullable = true; } } let nullable_override = configured_functions.iter().find_map(|f| f.ret.nullable); if let Some(val) = nullable_override { nullable = val; } Some(library::Parameter { typ, nullable, ..func.ret.clone() }) }; let mut commented = false; let bool_return_is_error = configured_functions .iter() .find_map(|f| f.ret.bool_return_is_error.as_ref()); let bool_return_error_message = bool_return_is_error.and_then(|m| { if typ != TypeId::tid_bool() && typ != TypeId::tid_c_bool() { error!( "Ignoring bool_return_is_error configuration for non-bool returning function {}", func.name ); None } else { let ns = if env.namespaces.glib_ns_id == namespaces::MAIN { "error" } else { "glib" }; imports.add(ns); Some(m.clone()) } }); let nullable_return_is_error = configured_functions .iter() .find_map(|f| f.ret.nullable_return_is_error.as_ref()); let nullable_return_error_message = nullable_return_is_error.and_then(|m| { if let Some(library::Parameter { nullable: Nullable(false), ..}) = parameter { error!( "Ignoring nullable_return_is_error configuration for non-none returning function {}", func.name ); None } else { let ns = if env.namespaces.glib_ns_id == namespaces::MAIN { "crate::BoolError" } else { "glib" }; imports.add(ns); Some(m.clone()) } }); let mut base_tid = None; if func.kind == library::FunctionKind::Constructor { if let Some(par) = parameter { let nullable_override = configured_functions.iter().find_map(|f| f.ret.nullable); if par.typ != type_tid { base_tid = Some(par.typ); } parameter = Some(library::Parameter { typ: type_tid, nullable: nullable_override.unwrap_or(func.ret.nullable), ..par }); } } let parameter = parameter.as_ref().map(|lib_par| { let par = analysis::Parameter::from_return_value(env, lib_par, configured_functions); if let Ok(rust_type) = RustType::builder(env, typ) .direction(par.lib_par.direction) .try_from_glib(&par.try_from_glib) .try_build() { used_types.extend(rust_type.into_used_types()); } commented = RustType::builder(env, typ) .direction(func.ret.direction) .try_from_glib(&par.try_from_glib) .try_build_param() .is_err(); par }); Info { parameter, base_tid, commented, bool_return_is_error: bool_return_error_message, nullable_return_is_error: nullable_return_error_message, } } fn can_be_nullable_return(env: &Env, type_id: library::TypeId) -> bool { use crate::library::{Basic::*, Type::*}; match env.library.type_(type_id) { Basic(fund) => matches!(fund, Pointer | Utf8 | Filename | OsString), Alias(alias) => can_be_nullable_return(env, alias.typ), Enumeration(_) => false, Bitfield(_) => false, Record(_) => true, Union(_) => true, Function(_) => true, Interface(_) => true, Class(_) => true, _ => true, } } gir-0.20.5/src/analysis/rust_type.rs000066400000000000000000000640271475434152100174140ustar00rootroot00000000000000use std::{borrow::Borrow, result}; use super::conversion_type::ConversionType; use crate::{ analysis::{record_type::RecordType, ref_mode::RefMode, try_from_glib::TryFromGlib}, config::functions::{CallbackParameter, CallbackParameters}, env::Env, library::{self, Nullable, ParameterDirection, ParameterScope}, nameutil::{is_gstring, use_glib_type}, traits::*, }; #[derive(Clone, Debug, PartialEq, Eq)] pub enum TypeError { Ignored(String), Mismatch(String), Unimplemented(String), } /// A `RustType` definition and its associated types to be `use`d. #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct RustType { inner: String, used_types: Vec, } impl RustType { /// Try building the `RustType` with no specific additional configuration. pub fn try_new(env: &Env, type_id: library::TypeId) -> Result { RustTypeBuilder::new(env, type_id).try_build() } /// Create a `RustTypeBuilder` which allows specifying additional /// configuration. pub fn builder(env: &Env, type_id: library::TypeId) -> RustTypeBuilder<'_> { RustTypeBuilder::new(env, type_id) } fn new_and_use(rust_type: &impl ToString) -> Self { RustType { inner: rust_type.to_string(), used_types: vec![rust_type.to_string()], } } fn new_with_uses(rust_type: &impl ToString, uses: &[impl ToString]) -> Self { RustType { inner: rust_type.to_string(), used_types: uses.iter().map(ToString::to_string).collect(), } } fn check( env: &Env, type_id: library::TypeId, type_name: &impl ToString, ) -> result::Result { let mut type_name = type_name.to_string(); if type_id.ns_id != library::MAIN_NAMESPACE && type_id.ns_id != library::INTERNAL_NAMESPACE && type_id.full_name(&env.library) != "GLib.DestroyNotify" && type_id.full_name(&env.library) != "GObject.Callback" { type_name = format!( "{}::{}", env.namespaces[type_id.ns_id].higher_crate_name, type_name.as_str() ); if env.type_status(&type_id.full_name(&env.library)).ignored() { return Err(TypeError::Ignored(type_name)); } } Ok(type_name) } fn try_new_and_use(env: &Env, type_id: library::TypeId) -> Result { Self::check(env, type_id, &env.library.type_(type_id).get_name()).map(|type_name| { RustType { inner: type_name.clone(), used_types: vec![type_name], } }) } fn try_new_and_use_with_name( env: &Env, type_id: library::TypeId, type_name: impl ToString, ) -> Result { Self::check(env, type_id, &type_name).map(|type_name| RustType { inner: type_name.clone(), used_types: vec![type_name], }) } pub fn used_types(&self) -> &Vec { &self.used_types } pub fn into_used_types(self) -> Vec { self.used_types } pub fn as_str(&self) -> &str { self.inner.as_str() } #[inline] pub fn alter_type(mut self, op: impl FnOnce(String) -> String) -> Self { self.inner = op(self.inner); self } #[inline] fn format_parameter(self, direction: ParameterDirection) -> Self { if direction.is_out() { self.alter_type(|type_| format!("&mut {type_}")) } else { self } } #[inline] fn apply_ref_mode(self, ref_mode: RefMode) -> Self { match ref_mode.for_rust_type() { "" => self, ref_mode => self.alter_type(|typ_| format!("{ref_mode}{typ_}")), } } } impl From for RustType { fn from(rust_type: T) -> Self { RustType { inner: rust_type.to_string(), used_types: Vec::new(), } } } impl IntoString for RustType { fn into_string(self) -> String { self.inner } } pub type Result = result::Result; fn into_inner(res: Result) -> String { use self::TypeError::*; match res { Ok(rust_type) => rust_type.into_string(), Err(Ignored(s) | Mismatch(s) | Unimplemented(s)) => s, } } impl IntoString for Result { fn into_string(self) -> String { use self::TypeError::*; match self { Ok(s) => s.into_string(), Err(Ignored(s)) => format!("/*Ignored*/{s}"), Err(Mismatch(s)) => format!("/*Metadata mismatch*/{s}"), Err(Unimplemented(s)) => format!("/*Unimplemented*/{s}"), } } } impl MapAny for Result { fn map_any RustType>(self, op: F) -> Result { use self::TypeError::*; match self { Ok(rust_type) => Ok(op(rust_type)), Err(Ignored(s)) => Err(Ignored(op(s.into()).into_string())), Err(Mismatch(s)) => Err(Mismatch(op(s.into()).into_string())), Err(Unimplemented(s)) => Err(Unimplemented(op(s.into()).into_string())), } } } pub struct RustTypeBuilder<'env> { env: &'env Env, type_id: library::TypeId, direction: ParameterDirection, nullable: Nullable, ref_mode: RefMode, scope: ParameterScope, concurrency: library::Concurrency, try_from_glib: TryFromGlib, callback_parameters_config: CallbackParameters, } impl<'env> RustTypeBuilder<'env> { fn new(env: &'env Env, type_id: library::TypeId) -> Self { Self { env, type_id, direction: ParameterDirection::None, nullable: Nullable(false), ref_mode: RefMode::None, scope: ParameterScope::None, concurrency: library::Concurrency::None, try_from_glib: TryFromGlib::default(), callback_parameters_config: Vec::new(), } } pub fn direction(mut self, direction: ParameterDirection) -> Self { self.direction = direction; self } pub fn nullable(mut self, nullable: Nullable) -> Self { self.nullable = nullable; self } pub fn ref_mode(mut self, ref_mode: RefMode) -> Self { self.ref_mode = ref_mode; self } pub fn scope(mut self, scope: ParameterScope) -> Self { self.scope = scope; self } pub fn concurrency(mut self, concurrency: library::Concurrency) -> Self { self.concurrency = concurrency; self } pub fn try_from_glib(mut self, try_from_glib: &TryFromGlib) -> Self { self.try_from_glib = try_from_glib.clone(); self } pub fn callback_parameters_config( mut self, callback_parameters_config: &[CallbackParameter], ) -> Self { self.callback_parameters_config = callback_parameters_config.to_owned(); self } pub fn try_build(self) -> Result { use crate::library::{Basic::*, Type::*}; let ok = |s: &str| Ok(RustType::from(s)); let ok_and_use = |s: &str| Ok(RustType::new_and_use(&s)); let err = |s: &str| Err(TypeError::Unimplemented(s.into())); let mut skip_option = false; let type_ = self.env.library.type_(self.type_id); let mut rust_type = match *type_ { Basic(fund) => { match fund { None => err("()"), Boolean | Bool => ok("bool"), Int8 => ok("i8"), UInt8 => ok("u8"), Int16 => ok("i16"), UInt16 => ok("u16"), Int32 => ok("i32"), UInt32 => ok("u32"), Int64 => ok("i64"), UInt64 => ok("u64"), Int => ok("i32"), // maybe dependent on target system UInt => ok("u32"), // maybe dependent on target system Short => ok_and_use("libc::c_short"), // depends of target system UShort => ok_and_use("libc::c_ushort"), // depends o f target system Long => ok_and_use("libc::c_long"), // depends of target system ULong => ok_and_use("libc::c_ulong"), // depends of target system TimeT => ok_and_use("libc::time_t"), // depends of target system OffT => ok_and_use("libc::off_t"), // depends of target system DevT => ok_and_use("libc::dev_t"), // depends of target system GidT => ok_and_use("libc::gid_t"), // depends of target system PidT => ok_and_use("libc::pid_t"), // depends of target system SockLenT => ok_and_use("libc::socklen_t"), // depends of target system UidT => ok_and_use("libc::uid_t"), // depends of target system Size => ok("usize"), // depends of target system SSize => ok("isize"), // depends of target system Float => ok("f32"), Double => ok("f64"), UniChar => ok("char"), Utf8 => { if self.ref_mode.is_ref() { ok("str") } else { ok_and_use(&use_glib_type(self.env, "GString")) } } Filename => { if self.ref_mode.is_ref() { ok_and_use("std::path::Path") } else { ok_and_use("std::path::PathBuf") } } OsString => { if self.ref_mode.is_ref() { ok_and_use("std::ffi::OsStr") } else { ok_and_use("std::ffi::OsString") } } Type => ok_and_use(&use_glib_type(self.env, "types::Type")), Char => ok_and_use(&use_glib_type(self.env, "Char")), UChar => ok_and_use(&use_glib_type(self.env, "UChar")), Unsupported => err("Unsupported"), _ => err(&format!("Basic: {fund:?}")), } } Alias(ref alias) => { RustType::try_new_and_use(self.env, self.type_id).and_then(|alias_rust_type| { RustType::builder(self.env, alias.typ) .direction(self.direction) .nullable(self.nullable) .ref_mode(self.ref_mode) .scope(self.scope) .concurrency(self.concurrency) .try_from_glib(&self.try_from_glib) .try_build() .map_any(|_| alias_rust_type) }) } Record(library::Record { ref c_type, .. }) if c_type == "GVariantType" => { let type_name = if self.ref_mode.is_ref() { "VariantTy" } else { "VariantType" }; RustType::try_new_and_use_with_name(self.env, self.type_id, type_name) } Enumeration(..) | Bitfield(..) | Record(..) | Union(..) | Class(..) | Interface(..) => { RustType::try_new_and_use(self.env, self.type_id).and_then(|rust_type| { if self .env .type_status(&self.type_id.full_name(&self.env.library)) .ignored() { Err(TypeError::Ignored(rust_type.into_string())) } else { Ok(rust_type) } }) } List(inner_tid) | SList(inner_tid) | CArray(inner_tid) | PtrArray(inner_tid) if ConversionType::of(self.env, inner_tid) == ConversionType::Pointer => { skip_option = true; let inner_ref_mode = match self.env.type_(inner_tid) { Class(..) | Interface(..) => RefMode::None, Record(record) => match RecordType::of(record) { RecordType::Boxed => RefMode::None, RecordType::AutoBoxed => { if !record.has_copy() { RefMode::None } else { self.ref_mode } } _ => self.ref_mode, }, _ => self.ref_mode, }; RustType::builder(self.env, inner_tid) .ref_mode(inner_ref_mode) .scope(self.scope) .concurrency(self.concurrency) .try_build() .map_any(|rust_type| { rust_type.alter_type(|typ| { if self.ref_mode.is_ref() { format!("[{typ}]") } else { format!("Vec<{typ}>") } }) }) } CArray(inner_tid) if ConversionType::of(self.env, inner_tid) == ConversionType::Direct => { if let Basic(fund) = self.env.type_(inner_tid) { let array_type = match fund { Int8 => Some("i8"), UInt8 => Some("u8"), Int16 => Some("i16"), UInt16 => Some("u16"), Int32 => Some("i32"), UInt32 => Some("u32"), Int64 => Some("i64"), UInt64 => Some("u64"), Int => Some("i32"), // maybe dependent on target system UInt => Some("u32"), // maybe dependent on target system Float => Some("f32"), Double => Some("f64"), _ => Option::None, }; if let Some(s) = array_type { skip_option = true; if self.ref_mode.is_ref() { Ok(format!("[{s}]").into()) } else { Ok(format!("Vec<{s}>").into()) } } else { Err(TypeError::Unimplemented(type_.get_name())) } } else { Err(TypeError::Unimplemented(type_.get_name())) } } Custom(library::Custom { ref name, .. }) => { RustType::try_new_and_use_with_name(self.env, self.type_id, name) } Function(ref f) => { let concurrency = match self.concurrency { _ if self.scope.is_call() => "", library::Concurrency::Send => " + Send", // If an object is Sync, it can be shared between threads, and as // such our callback can be called from arbitrary threads and needs // to be Send *AND* Sync library::Concurrency::SendSync => " + Send + Sync", library::Concurrency::None => "", }; let full_name = self.type_id.full_name(&self.env.library); if full_name == "Gio.AsyncReadyCallback" { // FIXME need to use the result from use_glib_type(&self.env, "Error")? return Ok(format!( "FnOnce(Result<(), {}>) + 'static", use_glib_type(self.env, "Error"), ) .into()); } else if full_name == "GLib.DestroyNotify" { return Ok(format!("Fn(){concurrency} + 'static").into()); } let mut params = Vec::with_capacity(f.parameters.len()); let mut err = false; for p in &f.parameters { if p.closure.is_some() { continue; } let nullable = self .callback_parameters_config .iter() .find(|cp| cp.ident.is_match(&p.name)) .and_then(|c| c.nullable) .unwrap_or(p.nullable); let p_res = RustType::builder(self.env, p.typ) .direction(p.direction) .nullable(nullable) .try_build(); match p_res { Ok(p_rust_type) => { let is_basic = p.typ.is_basic_type(self.env); let y = RustType::try_new(self.env, p.typ) .unwrap_or_else(|_| RustType::default()); params.push(format!( "{}{}", if is_basic || *nullable { "" } else { "&" }, if !is_gstring(y.as_str()) { if !is_basic && *nullable { p_rust_type.into_string().replace("Option<", "Option<&") } else { p_rust_type.into_string() } } else if *nullable { "Option<&str>".to_owned() } else { "&str".to_owned() } )); } e => { err = true; params.push(e.into_string()); } } } let closure_kind = if self.scope.is_call() { "FnMut" } else if self.scope.is_async() { "FnOnce" } else { "Fn" }; let ret_res = RustType::builder(self.env, f.ret.typ) .direction(f.ret.direction) .nullable(f.ret.nullable) .try_build(); let ret = match ret_res { Ok(ret_rust_type) => { let y = RustType::try_new(self.env, f.ret.typ) .unwrap_or_else(|_| RustType::default()); format!( "{}({}) -> {}{}", closure_kind, params.join(", "), if !is_gstring(y.as_str()) { ret_rust_type.as_str() } else if *f.ret.nullable { "Option" } else { "String" }, concurrency ) } Err(TypeError::Unimplemented(ref x)) if x == "()" => { format!("{}({}){}", closure_kind, params.join(", "), concurrency) } e => { err = true; format!( "{}({}) -> {}{}", closure_kind, params.join(", "), e.into_string(), concurrency ) } }; if err { return Err(TypeError::Unimplemented(ret)); } Ok(if *self.nullable { if self.scope.is_call() { format!("Option<&mut dyn ({ret})>") } else { format!("Option>") } } else { format!( "{}{}", ret, if self.scope.is_call() { "" } else { " + 'static" } ) } .into()) } _ => Err(TypeError::Unimplemented(type_.get_name())), }; match self .try_from_glib .or_type_defaults(self.env, self.type_id) .borrow() { TryFromGlib::Option => { rust_type = rust_type.map_any(|rust_type| { rust_type .alter_type(|typ_| { let mut opt = format!("Option<{typ_}>"); if self.direction == ParameterDirection::In { opt = format!("impl Into<{opt}>"); } opt }) .apply_ref_mode(self.ref_mode) }); } TryFromGlib::Result { ok_type, err_type } => { if self.direction == ParameterDirection::In { rust_type = rust_type.map_any(|rust_type| { RustType::new_with_uses( &format!("impl Into<{}>", &rust_type.as_str()), &[&rust_type.as_str()], ) }); } else { rust_type = rust_type.map_any(|_| { RustType::new_with_uses( &format!("Result<{}, {}>", &ok_type, &err_type), &[ok_type, err_type], ) }); } } TryFromGlib::ResultInfallible { ok_type } => { let new_rust_type = RustType::new_and_use(ok_type).apply_ref_mode(self.ref_mode); rust_type = rust_type.map_any(|_| new_rust_type); } _ => { rust_type = rust_type.map_any(|rust_type| rust_type.apply_ref_mode(self.ref_mode)); } } if *self.nullable && !skip_option { match ConversionType::of(self.env, self.type_id) { ConversionType::Pointer | ConversionType::Scalar => { rust_type = rust_type.map_any(|rust_type| { rust_type.alter_type(|typ_| format!("Option<{typ_}>")) }); } _ => (), } } rust_type } pub fn try_build_param(self) -> Result { use crate::library::Type::*; let type_ = self.env.library.type_(self.type_id); assert!( self.direction != ParameterDirection::None, "undefined direction for parameter with type {type_:?}" ); let rust_type = RustType::builder(self.env, self.type_id) .direction(self.direction) .nullable(self.nullable) .ref_mode(self.ref_mode) .scope(self.scope) .try_from_glib(&self.try_from_glib) .try_build(); match type_ { Basic(library::Basic::Utf8 | library::Basic::OsString | library::Basic::Filename) if (self.direction == ParameterDirection::InOut || (self.direction == ParameterDirection::Out && self.ref_mode == RefMode::ByRefMut)) => { Err(TypeError::Unimplemented(into_inner(rust_type))) } Basic(_) => rust_type.map_any(|rust_type| rust_type.format_parameter(self.direction)), Alias(alias) => rust_type .and_then(|rust_type| { RustType::builder(self.env, alias.typ) .direction(self.direction) .nullable(self.nullable) .ref_mode(self.ref_mode) .scope(self.scope) .try_from_glib(&self.try_from_glib) .try_build_param() .map_any(|_| rust_type) }) .map_any(|rust_type| rust_type.format_parameter(self.direction)), Enumeration(..) | Union(..) | Bitfield(..) => { rust_type.map_any(|rust_type| rust_type.format_parameter(self.direction)) } Record(..) => { if self.direction == ParameterDirection::InOut { Err(TypeError::Unimplemented(into_inner(rust_type))) } else { rust_type } } Class(..) | Interface(..) => match self.direction { ParameterDirection::In | ParameterDirection::Out | ParameterDirection::Return => { rust_type } _ => Err(TypeError::Unimplemented(into_inner(rust_type))), }, List(..) | SList(..) => match self.direction { ParameterDirection::In | ParameterDirection::Return => rust_type, _ => Err(TypeError::Unimplemented(into_inner(rust_type))), }, CArray(..) | PtrArray(..) => match self.direction { ParameterDirection::In | ParameterDirection::Out | ParameterDirection::Return => { rust_type } _ => Err(TypeError::Unimplemented(into_inner(rust_type))), }, Function(ref func) if func.name == "AsyncReadyCallback" => { Ok("AsyncReadyCallback".into()) } Function(_) => rust_type, Custom(..) => rust_type.map(|rust_type| rust_type.format_parameter(self.direction)), _ => Err(TypeError::Unimplemented(type_.get_name())), } } } gir-0.20.5/src/analysis/safety_assertion_mode.rs000066400000000000000000000037241475434152100217410ustar00rootroot00000000000000use std::str::FromStr; use crate::{analysis::function_parameters::Parameters, env::Env, library}; #[derive(Default, Clone, Copy, Debug, Eq, PartialEq)] pub enum SafetyAssertionMode { #[default] None, Skip, NotInitialized, InMainThread, } impl std::fmt::Display for SafetyAssertionMode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { SafetyAssertionMode::None => f.write_str(""), SafetyAssertionMode::NotInitialized => f.write_str("assert_not_initialized!();"), SafetyAssertionMode::Skip => f.write_str("skip_assert_initialized!();"), SafetyAssertionMode::InMainThread => f.write_str("assert_initialized_main_thread!();"), } } } impl FromStr for SafetyAssertionMode { type Err = String; fn from_str(name: &str) -> Result { match name { "none" => Ok(Self::None), "skip" => Ok(Self::Skip), "not-initialized" => Ok(Self::NotInitialized), "in-main-thread" => Ok(Self::InMainThread), _ => Err(format!("Unknown safety assertion mode '{name}'")), } } } impl SafetyAssertionMode { pub fn of(env: &Env, is_method: bool, params: &Parameters) -> Self { use crate::library::Type::*; if !env.config.generate_safety_asserts { return Self::None; } if is_method { return Self::None; } for par in ¶ms.rust_parameters { let c_par = ¶ms.c_parameters[par.ind_c]; match env.library.type_(c_par.typ) { Class(..) | Interface(..) if !*c_par.nullable && c_par.typ.ns_id == library::MAIN_NAMESPACE => { return Self::Skip } _ => (), } } Self::InMainThread } pub fn is_none(self) -> bool { matches!(self, Self::None) } } gir-0.20.5/src/analysis/signals.rs000066400000000000000000000061741475434152100170150ustar00rootroot00000000000000use super::{imports::Imports, trampolines}; use crate::{ analysis::trampolines::Trampoline, config::{self, gobjects::GObject}, env::Env, library, nameutil, traits::*, version::Version, }; #[derive(Debug)] pub struct Info { pub connect_name: String, pub signal_name: String, pub action_emit_name: Option, pub trampoline: Result>, pub version: Option, pub deprecated_version: Option, pub doc_hidden: bool, pub is_detailed: bool, pub generate_doc: bool, } pub fn analyze( env: &Env, signals: &[library::Signal], type_tid: library::TypeId, in_trait: bool, is_fundamental: bool, obj: &GObject, imports: &mut Imports, ) -> Vec { let mut sns = Vec::new(); for signal in signals { let configured_signals = obj.signals.matched(&signal.name); if !configured_signals.iter().all(|f| f.status.need_generate()) { continue; } if env.is_totally_deprecated(Some(type_tid.ns_id), signal.deprecated_version) { continue; } let info = analyze_signal( env, signal, type_tid, in_trait, is_fundamental, &configured_signals, obj, imports, ); sns.push(info); } sns } fn analyze_signal( env: &Env, signal: &library::Signal, type_tid: library::TypeId, in_trait: bool, is_fundamental: bool, configured_signals: &[&config::signals::Signal], obj: &GObject, imports: &mut Imports, ) -> Info { let mut used_types: Vec = Vec::with_capacity(4); let version = configured_signals .iter() .filter_map(|f| f.version) .min() .or(signal.version); let deprecated_version = signal.deprecated_version; let doc_hidden = configured_signals.iter().any(|f| f.doc_hidden); let imports = &mut imports.with_defaults(version, &None); imports.add("glib::translate::*"); let connect_name = format!("connect_{}", nameutil::signal_to_snake(&signal.name)); let trampoline = trampolines::analyze( env, signal, type_tid, in_trait, is_fundamental, configured_signals, obj, &mut used_types, version, ); let action_emit_name = if signal.is_action { imports.add("glib::prelude::*"); Some(format!("emit_{}", nameutil::signal_to_snake(&signal.name))) } else { None }; if trampoline.is_ok() { imports.add_used_types(&used_types); imports.add("glib::prelude::*"); imports.add("glib::object::ObjectType as _"); imports.add("glib::signal::{connect_raw, SignalHandlerId}"); imports.add("std::boxed::Box as Box_"); } let generate_doc = configured_signals.iter().all(|f| f.generate_doc); Info { connect_name, signal_name: signal.name.clone(), trampoline, action_emit_name, version, deprecated_version, doc_hidden, is_detailed: signal.is_detailed, generate_doc, } } gir-0.20.5/src/analysis/signatures.rs000066400000000000000000000045571475434152100175440ustar00rootroot00000000000000use std::collections::HashMap; use crate::{env::Env, library, version::Version}; #[derive(Debug)] pub struct Signature(Vec, library::TypeId, Option); impl Signature { pub fn new(func: &library::Function) -> Self { let params = func.parameters.iter().map(|p| p.typ).collect(); Self(params, func.ret.typ, func.version) } fn from_property(is_get: bool, typ: library::TypeId) -> Self { if is_get { Self(vec![Default::default()], typ, None) } else { Self(vec![Default::default(), typ], Default::default(), None) } } pub fn has_in_deps( &self, env: &Env, name: &str, deps: &[library::TypeId], ) -> (bool, Option) { for tid in deps { let full_name = tid.full_name(&env.library); if let Some(info) = env.analysis.objects.get(&full_name) { if let Some(signature) = info.signatures.get(name) { if self.eq(signature) { return (true, signature.2); } } } } (false, None) } pub fn has_for_property( env: &Env, name: &str, is_get: bool, typ: library::TypeId, signatures: &Signatures, deps: &[library::TypeId], ) -> (bool, Option) { if let Some(params) = signatures.get(name) { return (true, params.2); } let this = Signature::from_property(is_get, typ); for tid in deps { let full_name = tid.full_name(&env.library); if let Some(info) = env.analysis.objects.get(&full_name) { if let Some(signature) = info.signatures.get(name) { if this.property_eq(signature, is_get) { return (true, signature.2); } } } } (false, None) } fn eq(&self, other: &Signature) -> bool { other.1 == self.1 && other.0[1..] == self.0[1..] } fn property_eq(&self, other: &Signature, is_get: bool) -> bool { if self.eq(other) { true } else { // For getters for types like GdkRGBA is_get && other.0.len() == 2 && other.0[1] == self.1 } } } pub type Signatures = HashMap; gir-0.20.5/src/analysis/special_functions.rs000066400000000000000000000203531475434152100210600ustar00rootroot00000000000000use std::{collections::BTreeMap, str::FromStr}; use crate::{ analysis::{functions::Info as FuncInfo, imports::Imports}, codegen::Visibility, config::GObject, library::{Type as LibType, TypeId}, version::Version, }; #[derive(Clone, Copy, Eq, Debug, Ord, PartialEq, PartialOrd)] pub enum Type { Compare, Copy, Equal, Free, Ref, Display, Unref, Hash, } impl FromStr for Type { type Err = String; fn from_str(s: &str) -> Result { match s { "compare" => Ok(Self::Compare), "copy" => Ok(Self::Copy), "equal" => Ok(Self::Equal), "free" | "destroy" => Ok(Self::Free), "is_equal" => Ok(Self::Equal), "ref" | "ref_" => Ok(Self::Ref), "unref" => Ok(Self::Unref), "hash" => Ok(Self::Hash), _ => Err(format!("Unknown type '{s}'")), } } } #[derive(Debug, Clone)] pub struct TraitInfo { pub glib_name: String, pub version: Option, pub first_parameter_mut: bool, } type TraitInfos = BTreeMap; #[derive(Clone, Copy, Eq, Debug, Ord, PartialEq, PartialOrd)] pub enum FunctionType { StaticStringify, } #[derive(Debug, Clone)] pub struct FunctionInfo { pub type_: FunctionType, pub version: Option, } type FunctionInfos = BTreeMap; #[derive(Debug, Default)] pub struct Infos { traits: TraitInfos, functions: FunctionInfos, } impl Infos { pub fn traits(&self) -> &TraitInfos { &self.traits } pub fn traits_mut(&mut self) -> &mut TraitInfos { &mut self.traits } pub fn has_trait(&self, type_: Type) -> bool { self.traits.contains_key(&type_) } pub fn functions(&self) -> &FunctionInfos { &self.functions } } /// Returns true on functions that take an instance as single argument and /// return a string as result. fn is_stringify(func: &mut FuncInfo, parent_type: &LibType, obj: &GObject) -> bool { if func.parameters.c_parameters.len() != 1 { return false; } if !func.parameters.c_parameters[0].instance_parameter { return false; } if let Some(ret) = func.ret.parameter.as_mut() { if ret.lib_par.typ != TypeId::tid_utf8() { return false; } if func.name == "to_string" { // Rename to to_str to make sure it doesn't clash with ToString::to_string assert!(func.new_name.is_none(), "A `to_string` function can't be renamed manually. It's automatically renamed to `to_str`"); func.new_name = Some("to_str".to_owned()); // As to not change old code behaviour, assume non-nullability outside // enums and flags only, and exclusively for to_string. Function inside // enums and flags have been appropriately marked in Gir. if !obj.trust_return_value_nullability && !matches!(parent_type, LibType::Enumeration(_) | LibType::Bitfield(_)) { *ret.lib_par.nullable = false; } } // Cannot generate Display implementation for Option<> !*ret.lib_par.nullable } else { false } } fn update_func(func: &mut FuncInfo, type_: Type) -> bool { if !func.commented { use self::Type::*; match type_ { Copy | Free | Ref | Unref => func.hidden = true, Hash | Compare | Equal => func.visibility = Visibility::Private, Display => func.visibility = Visibility::Public, }; } true } pub fn extract(functions: &mut [FuncInfo], parent_type: &LibType, obj: &GObject) -> Infos { let mut specials = Infos::default(); let mut has_copy = false; let mut has_free = false; let mut destroy = None; for (pos, func) in functions.iter_mut().enumerate() { if is_stringify(func, parent_type, obj) { let return_transfer_none = func .ret .parameter .as_ref() .is_some_and(|ret| ret.lib_par.transfer == crate::library::Transfer::None); // Assume only enumerations and bitfields can return static strings let returns_static_ref = return_transfer_none && matches!(parent_type, LibType::Enumeration(_) | LibType::Bitfield(_)) // We cannot mandate returned lifetime if this is not generated. // (And this prevents an unused glib::GStr from being emitted below) && func.status.need_generate(); if returns_static_ref { // Override the function with a &'static (non allocating) -returning string // if the transfer type is none and it matches the above heuristics. specials.functions.insert( func.glib_name.clone(), FunctionInfo { type_: FunctionType::StaticStringify, version: func.version, }, ); } // Some stringifying functions can serve as Display implementation if matches!( func.name.as_str(), "to_string" | "to_str" | "name" | "get_name" ) { // FUTURE: Decide which function gets precedence if multiple Display prospects // exist. specials.traits.insert( Type::Display, TraitInfo { glib_name: func.glib_name.clone(), version: func.version, first_parameter_mut: false, }, ); } } else if let Ok(type_) = func.name.parse() { if func.name == "destroy" { destroy = Some((func.glib_name.clone(), pos)); continue; } if !update_func(func, type_) { continue; } if func.name == "copy" { has_copy = true; } else if func.name == "free" { has_free = true; } let first_parameter_mut = func .parameters .c_parameters .first() .is_some_and(|p| p.ref_mode == super::ref_mode::RefMode::ByRefMut); specials.traits.insert( type_, TraitInfo { glib_name: func.glib_name.clone(), version: func.version, first_parameter_mut, }, ); } } if has_copy && !has_free { if let Some((glib_name, pos)) = destroy { let ty_ = Type::from_str("destroy").unwrap(); let func = &mut functions[pos]; update_func(func, ty_); specials.traits.insert( ty_, TraitInfo { glib_name, version: func.version, first_parameter_mut: true, }, ); } } specials } // Some special functions (e.g. `copy` on refcounted types) should be exposed pub fn unhide(functions: &mut [FuncInfo], specials: &Infos, type_: Type) { if let Some(func) = specials.traits().get(&type_) { let func = functions .iter_mut() .find(|f| f.glib_name == func.glib_name && !f.commented); if let Some(func) = func { func.visibility = Visibility::Public; func.hidden = false; } } } pub fn analyze_imports(specials: &Infos, imports: &mut Imports) { for (type_, info) in specials.traits() { use self::Type::*; match type_ { Copy if info.first_parameter_mut => { imports.add_with_version("glib::translate::*", info.version); } Compare => { imports.add_with_version("glib::translate::*", info.version); } Equal => imports.add_with_version("glib::translate::*", info.version), _ => {} } } for info in specials.functions().values() { match info.type_ { FunctionType::StaticStringify => { imports.add_with_version("glib::GStr", info.version); } } } } gir-0.20.5/src/analysis/supertypes.rs000066400000000000000000000054101475434152100175700ustar00rootroot00000000000000use super::{general::StatusedTypeId, imports::Imports}; use crate::{ analysis::{namespaces, rust_type::RustType}, env::Env, library::TypeId, version::Version, }; pub fn analyze( env: &Env, type_id: TypeId, version: Option, imports: &mut Imports, add_parent_types_import: bool, ) -> Vec { let mut parents = Vec::new(); let gobject_id = env.library.find_type(0, "GObject.Object").unwrap(); for &super_tid in env.class_hierarchy.supertypes(type_id) { // skip GObject, it's inherited implicitly if super_tid == gobject_id { continue; } let status = env.type_status(&super_tid.full_name(&env.library)); parents.push(StatusedTypeId { type_id: super_tid, name: env.library.type_(super_tid).get_name(), status, }); if !status.ignored() && super_tid.ns_id == namespaces::MAIN && !add_parent_types_import { if let Ok(rust_type) = RustType::try_new(env, super_tid) { let full_name = super_tid.full_name(&env.library); if let Some(parent_version) = env .analysis .objects .get(&full_name) .and_then(|info| info.version) { if Some(parent_version) > version && parent_version > env.config.min_cfg_version { for import in rust_type.into_used_types() { imports.add_with_version( &format!("crate::{import}"), Some(parent_version), ); } } else { for import in rust_type.into_used_types() { imports.add(&format!("crate::{import}")); } } } else { for import in rust_type.into_used_types() { imports.add(&format!("crate::{import}")); } } } } } parents } pub fn dependencies(env: &Env, type_id: TypeId) -> Vec { let mut parents = Vec::new(); let gobject_id = match env.library.find_type(0, "GObject.Object") { Some(gobject_id) => gobject_id, None => TypeId::tid_none(), }; for &super_tid in env.class_hierarchy.supertypes(type_id) { // skip GObject, it's inherited implicitly if super_tid == gobject_id { continue; } let status = env.type_status(&super_tid.full_name(&env.library)); if status.need_generate() { parents.push(super_tid); } } parents } gir-0.20.5/src/analysis/symbols.rs000066400000000000000000000156341475434152100170460ustar00rootroot00000000000000use std::collections::HashMap; use crate::{ analysis::namespaces::{self, NsId}, case::CaseExt, library::*, }; #[derive(Clone, Debug, Default)] pub struct Symbol { crate_name: Option, module_name: Option, owner_name: Option, name: String, rust_prelude: bool, } impl Symbol { pub fn parent(&self) -> String { let mut ret = String::new(); if Some("gobject") == self.crate_name() { ret.push_str("glib::"); } else { if let Some(ref s) = self.crate_name { ret.push_str(s); ret.push_str("::"); } if let Some(ref module) = self.module_name { ret.push_str(module); ret.push_str("::"); } } if let Some(ref s) = self.owner_name { ret.push_str(s); ret.push_str("::"); } ret } pub fn full_rust_name(&self) -> String { let mut ret = self.parent(); ret.push_str(&self.name); ret } fn make_in_prelude(&mut self) { assert!( self.module_name.replace("prelude".to_string()).is_none(), "{self:?} already had a module name set!" ); } /// Convert this symbol into a trait pub fn make_trait(&mut self, trait_name: &str) { self.make_in_prelude(); self.name = trait_name.into(); } /// Convert this into a method of a trait pub fn make_trait_method(&mut self, trait_name: &str) { self.make_in_prelude(); self.owner_name = Some(trait_name.into()); } pub fn crate_name(&self) -> Option<&str> { self.crate_name.as_deref() } pub fn owner_name(&self) -> Option<&str> { self.owner_name.as_deref() } pub fn name(&self) -> &str { &self.name } pub fn is_rust_prelude(&self) -> bool { self.rust_prelude } } #[derive(Debug)] pub struct Info { symbols: Vec, c_name_index: HashMap, tid_index: HashMap, u32>, } pub fn run(library: &Library, namespaces: &namespaces::Info) -> Info { let mut info = Info { symbols: Vec::new(), c_name_index: HashMap::new(), tid_index: HashMap::new(), }; info.insert( "NULL", Symbol { name: "None".into(), rust_prelude: true, ..Default::default() }, None, ); info.insert( "FALSE", Symbol { name: "false".into(), rust_prelude: true, ..Default::default() }, None, ); info.insert( "TRUE", Symbol { name: "true".into(), rust_prelude: true, ..Default::default() }, None, ); for (ns_id, ns) in library.namespaces.iter().enumerate() { let ns_id = ns_id as NsId; if ns_id == namespaces::INTERNAL { continue; } let crate_name = if ns_id == namespaces::MAIN { None } else { Some(&namespaces[ns_id].crate_name) }; for (pos, typ) in ns.types.iter().map(|t| t.as_ref().unwrap()).enumerate() { let symbol = Symbol { crate_name: crate_name.cloned(), name: typ.get_name(), ..Default::default() }; let tid = TypeId { ns_id, id: pos as u32, }; match typ { Type::Alias(Alias { c_identifier, .. }) => { info.insert(c_identifier, symbol, Some(tid)); } Type::Enumeration(Enumeration { name, c_type, members, functions, .. }) | Type::Bitfield(Bitfield { name, c_type, members, functions, .. }) => { info.insert(c_type, symbol, Some(tid)); for member in members { let symbol = Symbol { crate_name: crate_name.cloned(), owner_name: Some(name.clone()), name: member.name.to_camel(), ..Default::default() }; info.insert(&member.c_identifier, symbol, None); } for func in functions { let symbol = Symbol { crate_name: crate_name.cloned(), owner_name: Some(name.clone()), name: func.name.clone(), ..Default::default() }; info.insert(func.c_identifier.as_ref().unwrap(), symbol, None); } } Type::Record(Record { name, c_type, functions, .. }) | Type::Class(Class { name, c_type, functions, .. }) | Type::Interface(Interface { name, c_type, functions, .. }) => { info.insert(c_type, symbol, Some(tid)); for func in functions { let symbol = Symbol { crate_name: crate_name.cloned(), owner_name: Some(name.clone()), name: func.name.clone(), ..Default::default() }; info.insert(func.c_identifier.as_ref().unwrap(), symbol, None); } } _ => {} } } } info } impl Info { pub fn by_c_name(&self, name: &str) -> Option<&Symbol> { self.c_name_index .get(name) .map(|&id| &self.symbols[id as usize]) } pub fn by_c_name_mut(&mut self, name: &str) -> Option<&mut Symbol> { if let Some(&id) = self.c_name_index.get(name) { Some(&mut self.symbols[id as usize]) } else { None } } pub fn by_tid(&self, tid: TypeId) -> Option<&Symbol> { self.tid_index .get(&Some(tid)) .map(|&id| &self.symbols[id as usize]) } fn insert(&mut self, name: &str, symbol: Symbol, tid: Option) { let id = self.symbols.len(); self.symbols.push(symbol); self.c_name_index.insert(name.to_owned(), id as u32); if tid.is_some() { self.tid_index.insert(tid, id as u32); } } } gir-0.20.5/src/analysis/trampoline_parameters.rs000066400000000000000000000165001475434152100217440ustar00rootroot00000000000000use log::error; use super::{conversion_type::ConversionType, ref_mode::RefMode, try_from_glib::TryFromGlib}; pub use crate::config::signals::TransformationType; use crate::{ analysis::{is_gpointer, rust_type::RustType}, config::{self, parameter_matchable::ParameterMatchable}, env::Env, library, nameutil, }; #[derive(Clone, Debug)] pub struct RustParameter { pub name: String, pub typ: library::TypeId, pub direction: library::ParameterDirection, pub nullable: library::Nullable, pub ref_mode: RefMode, pub try_from_glib: TryFromGlib, } #[derive(Clone, Debug)] pub struct CParameter { pub name: String, pub typ: library::TypeId, pub c_type: String, } impl CParameter { pub fn is_real_gpointer(&self, env: &Env) -> bool { is_gpointer(&self.c_type) && RustType::try_new(env, self.typ).is_err() } } #[derive(Clone, Debug)] pub struct Transformation { pub ind_c: usize, // index in `Vec` pub ind_rust: usize, // index in `Vec` pub transformation: TransformationType, pub name: String, pub typ: library::TypeId, pub transfer: library::Transfer, pub ref_mode: RefMode, pub conversion_type: ConversionType, } #[derive(Clone, Default, Debug)] pub struct Parameters { pub rust_parameters: Vec, pub c_parameters: Vec, pub transformations: Vec, } impl Parameters { pub fn new(capacity: usize) -> Self { Self { rust_parameters: Vec::with_capacity(capacity), c_parameters: Vec::with_capacity(capacity), transformations: Vec::with_capacity(capacity), } } pub fn prepare_transformation( &mut self, env: &Env, type_tid: library::TypeId, name: String, c_type: String, direction: library::ParameterDirection, transfer: library::Transfer, nullable: library::Nullable, ref_mode: RefMode, conversion_type: ConversionType, ) -> Transformation { let c_par = CParameter { name: name.clone(), typ: type_tid, c_type, }; let ind_c = self.c_parameters.len(); self.c_parameters.push(c_par); let rust_par = RustParameter { name: name.clone(), typ: type_tid, direction, nullable, ref_mode, try_from_glib: TryFromGlib::from_type_defaults(env, type_tid), }; let ind_rust = self.rust_parameters.len(); self.rust_parameters.push(rust_par); Transformation { ind_c, ind_rust, transformation: TransformationType::None, name, typ: type_tid, transfer, ref_mode, conversion_type, } } pub fn get(&self, ind_rust: usize) -> Option<&Transformation> { self.transformations .iter() .find(|tr| tr.ind_rust == ind_rust) } } pub fn analyze( env: &Env, signal_parameters: &[library::Parameter], type_tid: library::TypeId, configured_signals: &[&config::signals::Signal], callback_parameters_config: Option<&config::functions::CallbackParameters>, ) -> Parameters { let mut parameters = Parameters::new(signal_parameters.len() + 1); let owner = env.type_(type_tid); let c_type = format!("{}*", owner.get_glib_name().unwrap()); let transform = parameters.prepare_transformation( env, type_tid, "this".to_owned(), c_type, library::ParameterDirection::In, library::Transfer::None, library::Nullable(false), RefMode::ByRef, ConversionType::Borrow, ); parameters.transformations.push(transform); for par in signal_parameters { let name = nameutil::mangle_keywords(&*par.name).into_owned(); let ref_mode = RefMode::without_unneeded_mut(env, par, false, false); let nullable_override = configured_signals .matched_parameters(&name) .iter() .find_map(|p| p.nullable) .or_else(|| { callback_parameters_config.and_then(|cp| { cp.iter() .find(|cp| cp.ident.is_match(&par.name)) .and_then(|c| c.nullable) }) }); let nullable = nullable_override.unwrap_or(par.nullable); let conversion_type = { match env.library.type_(par.typ) { library::Type::Basic(library::Basic::Utf8) | library::Type::Record(..) | library::Type::Interface(..) | library::Type::Class(..) => ConversionType::Borrow, _ => ConversionType::of(env, par.typ), } }; let new_name = configured_signals .matched_parameters(&name) .iter() .find_map(|p| p.new_name.clone()); let transformation_override = configured_signals .matched_parameters(&name) .iter() .find_map(|p| p.transformation); let mut transform = parameters.prepare_transformation( env, par.typ, name, par.c_type.clone(), par.direction, par.transfer, nullable, ref_mode, conversion_type, ); if let Some(new_name) = new_name { transform.name = new_name; } if let Some(transformation_type) = transformation_override { apply_transformation_type(env, &mut parameters, &mut transform, transformation_type); } parameters.transformations.push(transform); } parameters } fn apply_transformation_type( env: &Env, parameters: &mut Parameters, transform: &mut Transformation, transformation_type: TransformationType, ) { transform.transformation = transformation_type; match transformation_type { TransformationType::None => (), TransformationType::Borrow => { if transform.conversion_type == ConversionType::Pointer { transform.conversion_type = ConversionType::Borrow; } else if transform.conversion_type != ConversionType::Borrow { error!( "Wrong conversion_type for borrow transformation {:?}", transform.conversion_type ); } } TransformationType::TreePath => { let type_ = env.type_(transform.typ); if let library::Type::Basic(library::Basic::Utf8) = type_ { if let Some(type_tid) = env.library.find_type(0, "Gtk.TreePath") { transform.typ = type_tid; transform.conversion_type = ConversionType::Direct; if let Some(rust_par) = parameters.rust_parameters.get_mut(transform.ind_rust) { rust_par.typ = type_tid; rust_par.ref_mode = RefMode::None; } } else { error!("Type Gtk.TreePath not found for treepath transformation"); } } else { error!( "Wrong parameter type for treepath transformation {:?}", transform.typ ); } } } } gir-0.20.5/src/analysis/trampolines.rs000066400000000000000000000167601475434152100177140ustar00rootroot00000000000000use log::error; use super::{ bounds::{BoundType, Bounds}, conversion_type::ConversionType, ffi_type::used_ffi_type, ref_mode::RefMode, rust_type::RustType, trampoline_parameters::{self, Parameters}, }; use crate::{ config::{self, gobjects::GObject}, env::Env, library, nameutil::signal_to_snake, parser::is_empty_c_type, traits::IntoString, version::Version, }; #[derive(Debug, Clone)] pub struct Trampoline { pub name: String, pub parameters: Parameters, pub ret: library::Parameter, // This field is used for user callbacks in `codegen::function_body_chunk` when generating // inner C functions. We need to have the bound name in order to create variables and also to // pass to the C function bounds (otherwise it won't compile because it doesn't know how to // infer the bounds). pub bound_name: String, pub bounds: Bounds, pub version: Option, pub inhibit: bool, pub concurrency: library::Concurrency, pub is_notify: bool, pub scope: library::ParameterScope, /// It's used to group callbacks pub user_data_index: usize, pub destroy_index: usize, pub nullable: library::Nullable, /// This field is used to give the type name when generating the "IsA" /// part. pub type_name: String, } pub type Trampolines = Vec; pub fn analyze( env: &Env, signal: &library::Signal, type_tid: library::TypeId, in_trait: bool, fundamental_type: bool, configured_signals: &[&config::signals::Signal], obj: &GObject, used_types: &mut Vec, version: Option, ) -> Result> { let errors = closure_errors(env, signal); if !errors.is_empty() { warn_main!( type_tid, "Can't generate {} trampoline for signal '{}'", type_tid.full_name(&env.library), signal.name ); return Err(errors); } let is_notify = signal.name.starts_with("notify::"); let name = format!("{}_trampoline", signal_to_snake(&signal.name)); // TODO: move to object.signal.return config let inhibit = configured_signals.iter().any(|f| f.inhibit); if inhibit && signal.ret.typ != library::TypeId::tid_bool() { error!("Wrong return type for Inhibit for signal '{}'", signal.name); } let mut bounds: Bounds = Default::default(); if in_trait || fundamental_type { let type_name = RustType::builder(env, type_tid) .ref_mode(RefMode::ByRefFake) .try_build(); if fundamental_type { bounds.add_parameter( "this", &type_name.into_string(), BoundType::AsRef(None), false, ); } else { bounds.add_parameter( "this", &type_name.into_string(), BoundType::IsA(None), false, ); } } let parameters = if is_notify { let mut parameters = trampoline_parameters::Parameters::new(1); let owner = env.type_(type_tid); let c_type = format!("{}*", owner.get_glib_name().unwrap()); let transform = parameters.prepare_transformation( env, type_tid, "this".to_owned(), c_type, library::ParameterDirection::In, library::Transfer::None, library::Nullable(false), crate::analysis::ref_mode::RefMode::ByRef, ConversionType::Borrow, ); parameters.transformations.push(transform); parameters } else { trampoline_parameters::analyze(env, &signal.parameters, type_tid, configured_signals, None) }; if in_trait || fundamental_type { let type_name = RustType::builder(env, type_tid) .ref_mode(RefMode::ByRefFake) .try_build(); if fundamental_type { bounds.add_parameter( "this", &type_name.into_string(), BoundType::AsRef(None), false, ); } else { bounds.add_parameter( "this", &type_name.into_string(), BoundType::IsA(None), false, ); } } for par in ¶meters.rust_parameters { if let Ok(rust_type) = RustType::builder(env, par.typ) .direction(par.direction) .try_from_glib(&par.try_from_glib) .try_build() { used_types.extend(rust_type.into_used_types()); } } for par in ¶meters.c_parameters { if let Some(ffi_type) = used_ffi_type(env, par.typ, &par.c_type) { used_types.push(ffi_type); } } let mut ret_nullable = signal.ret.nullable; if signal.ret.typ != Default::default() { if let Ok(rust_type) = RustType::builder(env, signal.ret.typ) .direction(library::ParameterDirection::Out) .try_build() { // No GString used_types.extend(rust_type.into_used_types()); } if let Some(ffi_type) = used_ffi_type(env, signal.ret.typ, &signal.ret.c_type) { used_types.push(ffi_type); } let nullable_override = configured_signals.iter().find_map(|f| f.ret.nullable); if let Some(nullable) = nullable_override { ret_nullable = nullable; } } let concurrency = configured_signals .iter() .map(|f| f.concurrency) .next() .unwrap_or(obj.concurrency); let ret = library::Parameter { nullable: ret_nullable, ..signal.ret.clone() }; let trampoline = Trampoline { name, parameters, ret, bounds, version, inhibit, concurrency, is_notify, bound_name: String::new(), scope: library::ParameterScope::None, user_data_index: 0, destroy_index: 0, nullable: library::Nullable(false), type_name: env.library.type_(type_tid).get_name(), }; Ok(trampoline) } fn closure_errors(env: &Env, signal: &library::Signal) -> Vec { let mut errors: Vec = Vec::new(); for par in &signal.parameters { if let Some(error) = type_error(env, par) { errors.push(format!( "{} {}: {}", error, par.name, par.typ.full_name(&env.library) )); } } if signal.ret.typ != Default::default() { if let Some(error) = type_error(env, &signal.ret) { errors.push(format!( "{} return value {}", error, signal.ret.typ.full_name(&env.library) )); } } errors } pub fn type_error(env: &Env, par: &library::Parameter) -> Option<&'static str> { use super::rust_type::TypeError::*; if par.direction == library::ParameterDirection::Out { Some("Out") } else if par.direction == library::ParameterDirection::InOut { Some("InOut") } else if is_empty_c_type(&par.c_type) { Some("Empty ctype") } else if ConversionType::of(env, par.typ) == ConversionType::Unknown { Some("Unknown conversion") } else { match RustType::try_new(env, par.typ) { Err(Ignored(_)) => Some("Ignored"), Err(Mismatch(_)) => Some("Mismatch"), Err(Unimplemented(_)) => Some("Unimplemented"), Ok(_) => None, } } } gir-0.20.5/src/analysis/try_from_glib.rs000066400000000000000000000053621475434152100202110ustar00rootroot00000000000000use std::{borrow::Cow, sync::Arc}; use crate::{ analysis::conversion_type::ConversionType, config, library::{self, Infallible, Mandatory}, Env, }; #[derive(Default, Clone, Debug)] pub enum TryFromGlib { #[default] Default, NotImplemented, Option, OptionMandatory, Result { ok_type: Arc, err_type: Arc, }, ResultInfallible { ok_type: Arc, }, } impl TryFromGlib { fn _new( env: &Env, type_id: library::TypeId, mut config_mandatory: impl Iterator, mut config_infallible: impl Iterator, ) -> Self { let conversion_type = ConversionType::of(env, type_id); match conversion_type { ConversionType::Option => { if *config_mandatory.next().unwrap_or(Mandatory(false)) { TryFromGlib::OptionMandatory } else { TryFromGlib::Option } } ConversionType::Result { ok_type, err_type } => { if *config_infallible.next().unwrap_or(Infallible(false)) { TryFromGlib::ResultInfallible { ok_type: Arc::clone(&ok_type), } } else { TryFromGlib::Result { ok_type: Arc::clone(&ok_type), err_type: Arc::clone(&err_type), } } } _ => TryFromGlib::NotImplemented, } } pub fn from_type_defaults(env: &Env, type_id: library::TypeId) -> Self { Self::_new(env, type_id, None.into_iter(), None.into_iter()) } pub fn or_type_defaults(&self, env: &Env, type_id: library::TypeId) -> Cow<'_, Self> { match self { TryFromGlib::Default => Cow::Owned(Self::from_type_defaults(env, type_id)), other => Cow::Borrowed(other), } } pub fn from_parameter( env: &Env, type_id: library::TypeId, configured_parameters: &[&config::functions::Parameter], ) -> Self { Self::_new( env, type_id, configured_parameters.iter().filter_map(|par| par.mandatory), configured_parameters .iter() .filter_map(|par| par.infallible), ) } pub fn from_return_value( env: &Env, type_id: library::TypeId, configured_functions: &[&config::functions::Function], ) -> Self { Self::_new( env, type_id, configured_functions.iter().filter_map(|f| f.ret.mandatory), configured_functions.iter().filter_map(|f| f.ret.infallible), ) } } gir-0.20.5/src/analysis/types.rs000066400000000000000000000154041475434152100165150ustar00rootroot00000000000000use crate::library::*; /// Array size limit above which Rust no longer automatically derives traits. const RUST_DERIVE_ARRAY_SIZE_LIMIT: u16 = 32; /// Number of parameters above which Rust no longer automatically derives traits /// in functions. const RUST_DERIVE_PARAM_SIZE_LIMIT: usize = 12; /// Checks if given type is some kind of pointer. pub trait IsPtr { fn is_ptr(&self) -> bool; } impl IsPtr for Field { fn is_ptr(&self) -> bool { if let Some(ref c_type) = self.c_type { c_type.contains('*') } else { // After library post processing phase // only types without c:type should be // function pointers, we need check their parameters. false } } } impl IsPtr for Alias { fn is_ptr(&self) -> bool { self.target_c_type.contains('*') } } /// Checks if given type has volatile qualifier. pub trait IsVolatile { fn is_volatile(&self) -> bool; } impl IsVolatile for Field { fn is_volatile(&self) -> bool { if let Some(ref c_type) = self.c_type { c_type.starts_with("volatile") } else { false } } } /// Checks if given type is incomplete, i.e., its size is unknown. pub trait IsIncomplete { fn is_incomplete(&self, lib: &Library) -> bool; } impl IsIncomplete for Basic { fn is_incomplete(&self, _lib: &Library) -> bool { matches!(*self, Self::None | Self::Unsupported | Self::VarArgs) } } impl IsIncomplete for Alias { fn is_incomplete(&self, lib: &Library) -> bool { if self.is_ptr() { false } else { lib.type_(self.typ).is_incomplete(lib) } } } impl IsIncomplete for Field { fn is_incomplete(&self, lib: &Library) -> bool { if self.is_ptr() { // Pointers are always complete. false } else { lib.type_(self.typ).is_incomplete(lib) } } } impl IsIncomplete for &[Field] { fn is_incomplete(&self, lib: &Library) -> bool { if self.is_empty() { return true; } let mut is_bitfield = false; for field in self.iter() { if field.is_incomplete(lib) { return true; } // Two consequitive bitfields are unrepresentable in Rust, // so from our perspective they are incomplete. if is_bitfield && field.bits.is_some() { return true; } is_bitfield = field.bits.is_some(); } false } } impl IsIncomplete for Class { fn is_incomplete(&self, lib: &Library) -> bool { self.fields.as_slice().is_incomplete(lib) } } impl IsIncomplete for Record { fn is_incomplete(&self, lib: &Library) -> bool { if self.c_type == "GHookList" || self.disguised || self.pointer { // Search for GHookList in sys codegen for rationale. false } else { self.fields.as_slice().is_incomplete(lib) } } } impl IsIncomplete for Union { fn is_incomplete(&self, lib: &Library) -> bool { self.fields.as_slice().is_incomplete(lib) } } impl IsIncomplete for Function { fn is_incomplete(&self, lib: &Library) -> bool { // Checking p.typ.is_incomplete(lib) cause recursive check on GScannerMsgFunc self.parameters.iter().any(|p| { matches!( lib.type_(p.typ), Type::Basic(Basic::Unsupported | Basic::VarArgs) ) }) } } impl IsIncomplete for TypeId { fn is_incomplete(&self, lib: &Library) -> bool { lib.type_(*self).is_incomplete(lib) } } impl IsIncomplete for Type { fn is_incomplete(&self, lib: &Library) -> bool { match self { Type::Basic(basic) => basic.is_incomplete(lib), Type::Alias(alias) => alias.is_incomplete(lib), Type::FixedArray(tid, ..) => tid.is_incomplete(lib), Type::Class(klass) => klass.is_incomplete(lib), Type::Record(record) => record.is_incomplete(lib), Type::Union(union) => union.is_incomplete(lib), Type::Function(function) => function.is_incomplete(lib), Type::Interface(..) => true, Type::Custom(..) | Type::Enumeration(..) | Type::Bitfield(..) | Type::Array(..) | Type::CArray(..) | Type::PtrArray(..) | Type::HashTable(..) | Type::List(..) | Type::SList(..) => false, } } } /// Checks if type is external aka opaque type. pub trait IsExternal { fn is_external(&self, lib: &Library) -> bool; } impl IsExternal for Class { fn is_external(&self, _lib: &Library) -> bool { self.fields.is_empty() } } impl IsExternal for Record { fn is_external(&self, _lib: &Library) -> bool { self.fields.is_empty() } } impl IsExternal for Union { fn is_external(&self, _lib: &Library) -> bool { self.fields.is_empty() } } impl IsExternal for Alias { fn is_external(&self, lib: &Library) -> bool { if self.is_ptr() { false } else { lib.type_(self.typ).is_external(lib) } } } impl IsExternal for Type { fn is_external(&self, lib: &Library) -> bool { match self { Type::Alias(alias) => alias.is_external(lib), Type::Class(klass) => klass.is_external(lib), Type::Record(record) => record.is_external(lib), Type::Union(union) => union.is_external(lib), Type::Interface(..) => true, Type::Custom(..) | Type::Basic(..) | Type::Enumeration(..) | Type::Bitfield(..) | Type::Function(..) | Type::Array(..) | Type::CArray(..) | Type::FixedArray(..) | Type::PtrArray(..) | Type::HashTable(..) | Type::List(..) | Type::SList(..) => false, } } } /// Checks if given type derives Copy trait. pub trait DerivesCopy { fn derives_copy(&self, lib: &Library) -> bool; } impl DerivesCopy for T { fn derives_copy(&self, lib: &Library) -> bool { // Copy is derived for all complete types. !self.is_incomplete(lib) } } /// Checks if given type implements Debug trait. pub trait ImplementsDebug { fn implements_debug(&self, lib: &Library) -> bool; } impl ImplementsDebug for Field { fn implements_debug(&self, lib: &Library) -> bool { match *lib.type_(self.typ) { Type::FixedArray(_, size, _) => size <= RUST_DERIVE_ARRAY_SIZE_LIMIT, Type::Function(Function { ref parameters, .. }) => { parameters.len() <= RUST_DERIVE_PARAM_SIZE_LIMIT } _ => true, } } } gir-0.20.5/src/case.rs000066400000000000000000000061321475434152100144370ustar00rootroot00000000000000pub trait CaseExt { type Owned; /// Changes string to snake_case. /// /// Inserts underscores in-between lowercase and uppercase characters when /// they appear in that order and in-between second last and last /// uppercase character when going from sequence of three or more /// uppercase characters to lowercase. /// /// Changes the whole string to lowercase. fn to_snake(&self) -> Self::Owned; /// Changes string to CamelCase. /// /// Uppercases each character that follows an underscore or is at the /// beginning of the string. /// /// Removes all underscores. fn to_camel(&self) -> Self::Owned; } impl CaseExt for str { type Owned = String; fn to_snake(&self) -> Self::Owned { let mut s = String::new(); let mut upper = true; let mut upper_count = 0; for c in self.chars() { let next_upper = if c.is_uppercase() { true } else if c.is_lowercase() { false } else { upper }; if !upper && next_upper { s.push('_'); } else if upper && !next_upper && upper_count >= 3 { let n = s.len() - s.chars().next_back().unwrap().len_utf8(); s.insert(n, '_'); } s.extend(c.to_lowercase()); if next_upper { upper_count += 1; upper = true; } else { upper_count = 0; upper = false; } } s } fn to_camel(&self) -> Self::Owned { let new_length = self.chars().filter(|c| *c != '_').count(); let mut s = String::with_capacity(new_length); let mut under = true; for c in self.chars() { if under && c.is_lowercase() { s.extend(c.to_uppercase()); } else if c != '_' { s.push(c); } under = c == '_'; } s } } #[cfg(test)] mod tests { use super::*; #[test] fn to_snake() { let cases = [ ("AtkGObjectAccessible", "atk_gobject_accessible"), ("AtkNoOpObject", "atk_no_op_object"), ("DConfClient", "dconf_client"), ("GCabCabinet", "gcab_cabinet"), ("GstA52Dec", "gst_a52_dec"), ("GstLameMP3Enc", "gst_lame_mp3_enc"), ("GstMpg123AudioDec", "gst_mpg123_audio_dec"), ("GstX264Enc", "gst_x264_enc"), ("FileIOStream", "file_io_stream"), ("IOStream", "io_stream"), ("IMContext", "im_context"), ("DBusMessage", "dbus_message"), ("SoupCookieJarDB", "soup_cookie_jar_db"), ("FooBarBaz", "foo_bar_baz"), ("aBcDe", "a_bc_de"), ("aXXbYYc", "a_xxb_yyc"), ]; for &(input, expected) in &cases { assert_eq!(expected, input.to_snake()); } } #[test] fn to_camel() { assert_eq!("foo_bar_baz".to_camel(), "FooBarBaz"); assert_eq!("_foo".to_camel(), "Foo"); } } gir-0.20.5/src/chunk/000077500000000000000000000000001475434152100142645ustar00rootroot00000000000000gir-0.20.5/src/chunk/chunk.rs000066400000000000000000000047231475434152100157500ustar00rootroot00000000000000use super::{conversion_from_glib, parameter_ffi_call_out}; use crate::analysis::{ function_parameters::TransformationType, return_value, safety_assertion_mode::SafetyAssertionMode, }; #[derive(Clone, Debug)] pub enum Chunk { Comment(Vec), Chunks(Vec), BlockHalf(Vec), // Block without open bracket, temporary UnsafeSmart(Vec), // TODO: remove (will change generated results) Unsafe(Vec), #[allow(clippy::upper_case_acronyms)] FfiCallTODO(String), FfiCall { name: String, params: Vec, }, FfiCallParameter { transformation_type: TransformationType, }, FfiCallOutParameter { par: parameter_ffi_call_out::Parameter, }, // TODO: separate without return_value::Info FfiCallConversion { ret: return_value::Info, array_length_name: Option, call: Box, }, Let { name: String, is_mut: bool, value: Box, type_: Option>, }, Uninitialized, UninitializedNamed { name: String, }, NullPtr, NullMutPtr, Custom(String), Tuple(Vec, TupleMode), FromGlibConversion { mode: conversion_from_glib::Mode, array_length_name: Option, value: Box, }, OptionalReturn { condition: String, value: Box, }, AssertErrorSanity, ErrorResultReturn { ret: Option>, value: Box, }, AssertInit(SafetyAssertionMode), Connect { signal: String, trampoline: String, in_trait: bool, is_detailed: bool, }, Name(String), ExternCFunc { name: String, parameters: Vec, body: Box, return_value: Option, bounds: String, }, Cast { name: String, type_: String, }, Call { func_name: String, arguments: Vec, }, } impl Chunk { pub fn is_uninitialized(&self) -> bool { matches!(*self, Self::Uninitialized) } } #[derive(Clone, Debug)] pub struct Param { pub name: String, pub typ: String, } pub fn chunks(ch: Chunk) -> Vec { vec![ch] } #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum TupleMode { Auto, // "", "1", "(1,2)" WithUnit, // "()", "1", "(1,2)" #[deprecated] #[allow(dead_code)] Simple, // "()", "(1)", "(1,2)" } gir-0.20.5/src/chunk/conversion_from_glib.rs000066400000000000000000000014361475434152100210430ustar00rootroot00000000000000use super::parameter_ffi_call_out; use crate::{ analysis::{self, try_from_glib::TryFromGlib}, library, }; #[derive(Clone, Debug)] pub struct Mode { pub typ: library::TypeId, pub transfer: library::Transfer, pub try_from_glib: TryFromGlib, } impl From<¶meter_ffi_call_out::Parameter> for Mode { fn from(orig: ¶meter_ffi_call_out::Parameter) -> Mode { Mode { typ: orig.typ, transfer: orig.transfer, try_from_glib: orig.try_from_glib.clone(), } } } impl From<&analysis::Parameter> for Mode { fn from(orig: &analysis::Parameter) -> Mode { Mode { typ: orig.lib_par.typ, transfer: orig.lib_par.transfer, try_from_glib: orig.try_from_glib.clone(), } } } gir-0.20.5/src/chunk/mod.rs000066400000000000000000000007751475434152100154220ustar00rootroot00000000000000use crate::env::Env; #[allow(clippy::module_inception)] mod chunk; pub mod conversion_from_glib; pub mod parameter_ffi_call_out; pub use self::chunk::{chunks, Chunk, Param, TupleMode}; pub fn ffi_function_todo(env: &Env, name: &str) -> Chunk { let sys_crate_name = env.main_sys_crate_name(); let call = Chunk::FfiCallTODO(format!("{sys_crate_name}:{name}")); let unsafe_ = Chunk::UnsafeSmart(chunks(call)); let block = Chunk::BlockHalf(chunks(unsafe_)); Chunk::Comment(chunks(block)) } gir-0.20.5/src/chunk/parameter_ffi_call_out.rs000066400000000000000000000023071475434152100213220ustar00rootroot00000000000000use crate::{ analysis::{self, try_from_glib::TryFromGlib}, library, }; #[derive(Clone, Debug)] pub struct Parameter { pub name: String, pub typ: library::TypeId, pub transfer: library::Transfer, pub caller_allocates: bool, pub is_error: bool, pub is_uninitialized: bool, pub try_from_glib: TryFromGlib, } impl Parameter { pub fn new(orig: &analysis::function_parameters::CParameter, is_uninitialized: bool) -> Self { Self { name: orig.name.clone(), typ: orig.typ, transfer: orig.transfer, caller_allocates: orig.caller_allocates, is_error: orig.is_error, is_uninitialized, try_from_glib: orig.try_from_glib.clone(), } } } impl From<&analysis::Parameter> for Parameter { fn from(orig: &analysis::Parameter) -> Self { Self { name: orig.lib_par.name.clone(), typ: orig.lib_par.typ, transfer: orig.lib_par.transfer, caller_allocates: orig.lib_par.caller_allocates, is_error: orig.lib_par.is_error, is_uninitialized: false, try_from_glib: orig.try_from_glib.clone(), } } } gir-0.20.5/src/codegen/000077500000000000000000000000001475434152100145605ustar00rootroot00000000000000gir-0.20.5/src/codegen/alias.rs000066400000000000000000000033031475434152100162160ustar00rootroot00000000000000use std::{ io::{prelude::*, Result}, path::Path, }; use crate::{ analysis::{namespaces, rust_type::RustType}, codegen::general::{doc_alias, start_comments}, config::gobjects::GObject, env::Env, file_saver, library::*, traits::*, }; pub fn generate(env: &Env, root_path: &Path, mod_rs: &mut Vec) { let configs: Vec<&GObject> = env .config .objects .values() .filter(|c| { c.status.need_generate() && c.type_id.is_some_and(|tid| tid.ns_id == namespaces::MAIN) }) .collect(); let mut has_any = false; for config in &configs { if let Type::Alias(_) = env.library.type_(config.type_id.unwrap()) { has_any = true; break; } } if !has_any { return; } let path = root_path.join("alias.rs"); file_saver::save_to_file(path, env.config.make_backup, |w| { start_comments(w, &env.config)?; writeln!(w)?; writeln!(w, "#[allow(unused_imports)]")?; writeln!(w, "use crate::auto::*;")?; writeln!(w)?; mod_rs.push("\nmod alias;".into()); for config in &configs { if let Type::Alias(alias) = env.library.type_(config.type_id.unwrap()) { mod_rs.push(format!("pub use self::alias::{};", alias.name)); generate_alias(env, w, alias, config)?; } } Ok(()) }); } fn generate_alias(env: &Env, w: &mut dyn Write, alias: &Alias, _: &GObject) -> Result<()> { let typ = RustType::try_new(env, alias.typ).into_string(); doc_alias(w, &alias.c_identifier, "", 0)?; writeln!(w, "pub type {} = {};", alias.name, typ)?; Ok(()) } gir-0.20.5/src/codegen/bound.rs000066400000000000000000000063251475434152100162430ustar00rootroot00000000000000use crate::{ analysis::{ bounds::{Bound, BoundType}, ref_mode::RefMode, }, library::Nullable, }; impl Bound { /// Returns the type parameter reference. /// Currently always returns the alias. pub(super) fn type_parameter_reference(&self) -> Option { self.alias } /// Returns the type parameter reference, with [`BoundType::IsA`] wrapped /// in `ref_mode` and `nullable` as appropriate. pub(super) fn full_type_parameter_reference( &self, ref_mode: RefMode, nullable: Nullable, r#async: bool, ) -> String { let ref_str = ref_mode.for_rust_type(); // Generate `impl Trait` if this bound does not have an alias let trait_bound = match self.type_parameter_reference() { Some(t) => t.to_string(), None => { let trait_bound = self.trait_bound(r#async); let trait_bound = format!("impl {trait_bound}"); // Combining a ref mode and lifetime requires parentheses for disambiguation match self.bound_type { BoundType::IsA(lifetime) => { // TODO: This is fragile let has_lifetime = r#async || lifetime.is_some(); if !ref_str.is_empty() && has_lifetime { format!("({trait_bound})") } else { trait_bound } } _ => trait_bound, } } }; match self.bound_type { BoundType::IsA(_) if *nullable => { format!("Option<{ref_str}{trait_bound}>") } BoundType::IsA(_) => format!("{ref_str}{trait_bound}"), BoundType::AsRef(_) if *nullable => { format!("Option<{trait_bound}>") } BoundType::NoWrapper | BoundType::AsRef(_) => trait_bound, } } /// Returns the type parameter definition for this bound, usually /// of the form `T: SomeTrait` or `T: IsA`. pub(super) fn type_parameter_definition(&self, r#async: bool) -> Option { self.alias .map(|alias| format!("{}: {}", alias, self.trait_bound(r#async))) } /// Returns the trait bound, usually of the form `SomeTrait` /// or `IsA`. pub(super) fn trait_bound(&self, r#async: bool) -> String { match self.bound_type { BoundType::NoWrapper => self.type_str.clone(), BoundType::IsA(lifetime) => { if r#async { assert!(lifetime.is_none(), "Async overwrites lifetime"); } let is_a = format!("IsA<{}>", self.type_str); let lifetime = r#async .then(|| " + Clone + 'static".to_string()) .or_else(|| lifetime.map(|l| format!(" + '{l}"))) .unwrap_or_default(); format!("{is_a}{lifetime}") } BoundType::AsRef(Some(_ /* lifetime */)) => panic!("AsRef cannot have a lifetime"), BoundType::AsRef(None) => format!("AsRef<{}>", self.type_str), } } } gir-0.20.5/src/codegen/child_properties.rs000066400000000000000000000070661475434152100204760ustar00rootroot00000000000000use std::io::{Result, Write}; use super::{ general::{doc_alias, doc_hidden}, property_body, }; use crate::{ analysis::{child_properties::ChildProperty, rust_type::RustType}, chunk::Chunk, env::Env, library, nameutil::use_gtk_type, traits::IntoString, writer::{primitives::tabs, ToCode}, }; pub fn generate( w: &mut dyn Write, env: &Env, prop: &ChildProperty, in_trait: bool, only_declaration: bool, indent: usize, ) -> Result<()> { generate_func(w, env, prop, in_trait, only_declaration, indent, true)?; generate_func(w, env, prop, in_trait, only_declaration, indent, false)?; Ok(()) } fn generate_func( w: &mut dyn Write, env: &Env, prop: &ChildProperty, in_trait: bool, only_declaration: bool, indent: usize, is_get: bool, ) -> Result<()> { let pub_prefix = if in_trait { "" } else { "pub " }; let decl_suffix = if only_declaration { ";" } else { " {" }; let type_string = RustType::try_new(env, prop.typ); let comment_prefix = if type_string.is_err() { "//" } else { "" }; writeln!(w)?; doc_hidden(w, prop.doc_hidden, comment_prefix, indent)?; let decl = declaration(env, prop, is_get); let add_doc_alias = if is_get { prop.name != prop.getter_name && prop.name != prop.prop_name } else { prop.name != prop.prop_name }; if add_doc_alias { doc_alias( w, &format!("{}.{}", &prop.child_name, &prop.name), comment_prefix, indent, )?; } writeln!( w, "{}{}{}{}{}", tabs(indent), comment_prefix, pub_prefix, decl, decl_suffix )?; if !only_declaration { let body = body(env, prop, in_trait, is_get).to_code(env); for s in body { writeln!(w, "{}{}{}", tabs(indent), comment_prefix, s)?; } } Ok(()) } fn declaration(env: &Env, prop: &ChildProperty, is_get: bool) -> String { let func_name = if is_get { format!("{}_{}", prop.child_name, prop.getter_name) } else { format!("set_{}_{}", prop.child_name, prop.prop_name) }; let mut bounds = if let Some(typ) = prop.child_type { let child_type = RustType::try_new(env, typ).into_string(); format!("T: IsA<{child_type}>") } else { format!("T: IsA<{}>", use_gtk_type(env, "Widget")) }; if !is_get && !prop.bounds.is_empty() { bounds = format!("{}, {}", prop.bounds, bounds); } let return_str = if is_get { let dir = library::ParameterDirection::Return; let ret_type = RustType::builder(env, prop.typ) .direction(dir) .nullable(prop.nullable) .ref_mode(prop.get_out_ref_mode) .try_build_param() .into_string(); format!(" -> {ret_type}") } else { String::new() }; format!( "fn {}<{}>(&self, item: &T{}){}", func_name, bounds, if is_get { String::new() } else { format!(", {}", prop.set_params) }, return_str ) } fn body(env: &Env, prop: &ChildProperty, in_trait: bool, is_get: bool) -> Chunk { let mut builder = property_body::Builder::new_for_child_property(env); builder .name(&prop.name) .in_trait(in_trait) .var_name(&prop.prop_name) .is_get(is_get); if let Ok(type_) = RustType::try_new(env, prop.typ) { builder.type_(type_.as_str()); } else { builder.type_("/*Unknown type*/"); } builder.generate() } gir-0.20.5/src/codegen/constants.rs000066400000000000000000000041551475434152100171470ustar00rootroot00000000000000use std::path::Path; use crate::{ analysis::imports::Imports, codegen::general::{ self, cfg_condition, cfg_deprecated, doc_alias, version_condition, version_condition_string, }, env::Env, file_saver, library, }; pub fn generate(env: &Env, root_path: &Path, mod_rs: &mut Vec) { let path = root_path.join("constants.rs"); let mut imports = Imports::new(&env.library); if env.analysis.constants.is_empty() { return; } let sys_crate_name = env.main_sys_crate_name(); imports.add("glib::GStr"); imports.add("crate::ffi"); file_saver::save_to_file(path, env.config.make_backup, |w| { general::start_comments(w, &env.config)?; general::uses(w, env, &imports, None)?; writeln!(w)?; mod_rs.push("\nmod constants;".into()); for constant in &env.analysis.constants { let type_ = env.type_(constant.typ); if let library::Type::Basic(library::Basic::Utf8) = type_ { cfg_deprecated(w, env, None, constant.deprecated_version, false, 0)?; cfg_condition(w, constant.cfg_condition.as_ref(), false, 0)?; version_condition(w, env, None, constant.version, false, 0)?; doc_alias(w, &constant.glib_name, "", 0)?; writeln!( w, "pub static {name}: &GStr = unsafe{{GStr::from_utf8_with_nul_unchecked({sys_crate_name}::{c_id})}};", sys_crate_name = sys_crate_name, name = constant.name, c_id = constant.glib_name )?; if let Some(cfg) = version_condition_string(env, None, constant.version, false, 0) { mod_rs.push(cfg); } mod_rs.push(format!( "{}pub use self::constants::{};", constant .deprecated_version .map(|_| "#[allow(deprecated)]\n") .unwrap_or(""), constant.name )); } } Ok(()) }); } gir-0.20.5/src/codegen/doc/000077500000000000000000000000001475434152100153255ustar00rootroot00000000000000gir-0.20.5/src/codegen/doc/format.rs000066400000000000000000000510511475434152100171650ustar00rootroot00000000000000#![allow(clippy::manual_map)] use std::{fmt::Write, sync::OnceLock}; use log::{info, warn}; use regex::{Captures, Regex}; use super::{gi_docgen, LocationInObject}; use crate::{ analysis::functions::Info, library::{FunctionKind, TypeId}, nameutil, Env, }; const LANGUAGE_SEP_BEGIN: &str = ""; const LANGUAGE_BLOCK_BEGIN: &str = "|["; const LANGUAGE_BLOCK_END: &str = "\n]|"; // A list of function names that are ignored when warning about a "not found // function" const IGNORE_C_WARNING_FUNCS: [&str; 6] = [ "g_object_unref", "g_object_ref", "g_free", "g_list_free", "g_strfreev", "printf", ]; pub fn reformat_doc( input: &str, env: &Env, in_type: Option<(&TypeId, Option)>, ) -> String { code_blocks_transformation(input, env, in_type) } fn try_split<'a>(src: &'a str, needle: &str) -> (&'a str, Option<&'a str>) { match src.find(needle) { Some(pos) => (&src[..pos], Some(&src[pos + needle.len()..])), None => (src, None), } } fn code_blocks_transformation( mut input: &str, env: &Env, in_type: Option<(&TypeId, Option)>, ) -> String { let mut out = String::with_capacity(input.len()); loop { input = match try_split(input, LANGUAGE_BLOCK_BEGIN) { (before, Some(after)) => { out.push_str(&format(before, env, in_type)); if let (before, Some(after)) = try_split(get_language(after, &mut out), LANGUAGE_BLOCK_END) { out.push_str(before); out.push_str("\n```"); after } else { after } } (before, None) => { out.push_str(&format(before, env, in_type)); return out; } }; } } fn get_language<'a>(entry: &'a str, out: &mut String) -> &'a str { if let (_, Some(after)) = try_split(entry, LANGUAGE_SEP_BEGIN) { if let (before, Some(after)) = try_split(after, LANGUAGE_SEP_END) { if !["text", "rust"].contains(&before) { write!(out, "\n\n**⚠️ The following code is in {before} ⚠️**").unwrap(); } write!(out, "\n\n```{before}").unwrap(); return after; } } out.push_str("\n```text"); entry } // try to get the language if any is defined or fallback to text fn get_markdown_language(input: &str) -> (&str, &str) { let (lang, after) = if let Some((lang, after)) = input.split_once('\n') { let lang = if lang.is_empty() { None } else { Some(lang) }; (lang, after) } else { (None, input) }; (lang.unwrap_or("text"), after) } // Re-format codeblocks & replaces the C types and GI-docgen with proper links fn format( mut input: &str, env: &Env, in_type: Option<(&TypeId, Option)>, ) -> String { let mut ret = String::with_capacity(input.len()); loop { input = match try_split(input, "```") { (before, Some(after)) => { // if we are inside a codeblock ret.push_str(&replace_symbols(before, env, in_type)); let (lang, after) = get_markdown_language(after); if !["text", "rust", "xml", "css", "json", "html"].contains(&lang) && after.lines().count() > 1 { write!(ret, "**⚠️ The following code is in {lang} ⚠️**\n\n").unwrap(); } writeln!(ret, "```{lang}").unwrap(); if let (before, Some(after)) = try_split(after, "```") { ret.push_str(before); ret.push_str("```"); after } else { after } } (before, None) => { ret.push_str(&replace_symbols(before, env, in_type)); return ret; } } } } fn replace_symbols( input: &str, env: &Env, in_type: Option<(&TypeId, Option)>, ) -> String { if env.config.use_gi_docgen { let out = gi_docgen::replace_c_types(input, env, in_type); let out = gi_docgen_symbol().replace_all(&out, |caps: &Captures<'_>| match &caps[2] { "TRUE" => "[`true`]".to_string(), "FALSE" => "[`false`]".to_string(), "NULL" => "[`None`]".to_string(), symbol_name => match &caps[1] { // Opt-in only for the %SYMBOLS, @/# causes breakages "%" => find_constant_or_variant_wrapper(symbol_name, env, in_type), s => panic!("Unknown symbol prefix `{s}`"), }, }); let out = gdk_gtk().replace_all(&out, |caps: &Captures<'_>| { find_type(&caps[2], env).unwrap_or_else(|| format!("`{}`", &caps[2])) }); out.to_string() } else { replace_c_types(input, env, in_type) } } fn symbol() -> &'static Regex { static REGEX: OnceLock = OnceLock::new(); REGEX.get_or_init(|| Regex::new(r"([@#%])(\w+\b)([:.]+[\w-]+\b)?").unwrap()) } fn gi_docgen_symbol() -> &'static Regex { static REGEX: OnceLock = OnceLock::new(); REGEX.get_or_init(|| Regex::new(r"([%])(\w+\b)([:.]+[\w-]+\b)?").unwrap()) } fn function() -> &'static Regex { static REGEX: OnceLock = OnceLock::new(); REGEX.get_or_init(|| Regex::new(r"([@#%])?(\w+\b[:.]+)?(\b[a-z0-9_]+)\(\)").unwrap()) } fn gdk_gtk() -> &'static Regex { // **note** // The optional . at the end is to make the regex more relaxed for some weird // broken cases on gtk3's docs it doesn't hurt other docs so please don't drop // it static REGEX: OnceLock = OnceLock::new(); REGEX.get_or_init(|| { Regex::new(r"`([^\(:])?((G[dts]k|Pango|cairo_|graphene_|Adw|Hdy|GtkSource)\w+\b)(\.)?`") .unwrap() }) } fn tags() -> &'static Regex { static REGEX: OnceLock = OnceLock::new(); REGEX.get_or_init(|| Regex::new(r"<[\w/-]+>").unwrap()) } fn spaces() -> &'static Regex { static REGEX: OnceLock = OnceLock::new(); REGEX.get_or_init(|| Regex::new(r"[ ]{2,}").unwrap()) } fn replace_c_types( entry: &str, env: &Env, in_type: Option<(&TypeId, Option)>, ) -> String { let out = function().replace_all(entry, |caps: &Captures<'_>| { let name = &caps[3]; find_method_or_function_by_ctype(None, name, env, in_type).unwrap_or_else(|| { if !IGNORE_C_WARNING_FUNCS.contains(&name) { info!("No function found for `{}()`", name); } format!("`{}{}()`", caps.get(2).map_or("", |m| m.as_str()), name) }) }); let out = symbol().replace_all(&out, |caps: &Captures<'_>| match &caps[2] { "TRUE" => "[`true`]".to_string(), "FALSE" => "[`false`]".to_string(), "NULL" => "[`None`]".to_string(), symbol_name => match &caps[1] { "%" => find_constant_or_variant_wrapper(symbol_name, env, in_type), "#" => { if let Some(member_path) = caps.get(3).map(|m| m.as_str()) { let method_name = member_path.trim_start_matches('.'); find_member(symbol_name, method_name, env, in_type).unwrap_or_else(|| { info!("`#{}` not found as method", symbol_name); format!("`{symbol_name}{member_path}`") }) } else if let Some(type_) = find_type(symbol_name, env) { type_ } else if let Some(constant_or_variant) = find_constant_or_variant(symbol_name, env, in_type) { warn!( "`{}` matches a constant/variant and should use `%` prefix instead of `#`", symbol_name ); constant_or_variant } else { info!("Type `#{}` not found", symbol_name); format!("`{symbol_name}`") } } "@" => { // XXX: Theoretically this code should check if the resulting // symbol truly belongs to `in_type`! if let Some(type_) = find_type(symbol_name, env) { warn!( "`{}` matches a type and should use `#` prefix instead of `%`", symbol_name ); type_ } else if let Some(constant_or_variant) = find_constant_or_variant(symbol_name, env, in_type) { constant_or_variant } else if let Some(function) = find_method_or_function_by_ctype(None, symbol_name, env, in_type) { function } else { // `@` is often used to refer to fields and function parameters. format!("`{symbol_name}`") } } s => panic!("Unknown symbol prefix `{s}`"), }, }); let out = gdk_gtk().replace_all(&out, |caps: &Captures<'_>| { find_type(&caps[2], env).unwrap_or_else(|| format!("`{}`", &caps[2])) }); let out = tags().replace_all(&out, "`$0`"); spaces().replace_all(&out, " ").into_owned() } /// Wrapper around [`find_constant_or_variant`] that fallbacks to returning /// the `symbol_name` fn find_constant_or_variant_wrapper( symbol_name: &str, env: &Env, in_type: Option<(&TypeId, Option)>, ) -> String { find_constant_or_variant(symbol_name, env, in_type).unwrap_or_else(|| { info!("Constant or variant `%{}` not found", symbol_name); format!("`{symbol_name}`") }) } fn find_member( type_: &str, method_name: &str, env: &Env, in_type: Option<(&TypeId, Option)>, ) -> Option { let symbols = env.symbols.borrow(); let is_signal = method_name.starts_with("::"); let is_property = !is_signal && method_name.starts_with(':'); if !is_signal && !is_property { find_method_or_function_by_ctype(Some(type_), method_name, env, in_type) } else { env.analysis .objects .values() .find(|o| o.c_type == type_) .map(|info| { let sym = symbols.by_tid(info.type_id).unwrap(); // we are sure the object exists let name = method_name.trim_start_matches(':'); if is_property { gen_property_doc_link(&sym.full_rust_name(), name) } else { gen_signal_doc_link(&sym.full_rust_name(), name) } }) } } fn find_constant_or_variant( symbol: &str, env: &Env, in_type: Option<(&TypeId, Option)>, ) -> Option { if let Some((flag_info, member_info)) = env.analysis.flags.iter().find_map(|f| { f.type_(&env.library) .members .iter() .find(|m| m.c_identifier == symbol && !m.status.ignored()) .map(|m| (f, m)) }) { Some(gen_member_doc_link( flag_info.type_id, &nameutil::bitfield_member_name(&member_info.name), env, in_type, )) } else if let Some((enum_info, member_info)) = env.analysis.enumerations.iter().find_map(|e| { e.type_(&env.library) .members .iter() .find(|m| m.c_identifier == symbol && !m.status.ignored()) .map(|m| (e, m)) }) { Some(gen_member_doc_link( enum_info.type_id, &nameutil::enum_member_name(&member_info.name), env, in_type, )) } else if let Some(const_info) = env .analysis .constants .iter() .find(|c| c.glib_name == symbol) { Some(gen_const_doc_link(const_info)) } else { None } } // A list of types that are automatically ignored by the `find_type` function const IGNORED_C_TYPES: [&str; 6] = [ "gconstpointer", "guint16", "guint", "gunicode", "gchararray", "GList", ]; /// either an object/interface, record, enum or a flag fn find_type(type_: &str, env: &Env) -> Option { if IGNORED_C_TYPES.contains(&type_) { return None; } let type_id = if let Some(obj) = env.analysis.objects.values().find(|o| o.c_type == type_) { Some(obj.type_id) } else if let Some(record) = env .analysis .records .values() .find(|r| r.type_(&env.library).c_type == type_) { Some(record.type_id) } else if let Some(enum_) = env .analysis .enumerations .iter() .find(|e| e.type_(&env.library).c_type == type_) { Some(enum_.type_id) } else if let Some(flag) = env .analysis .flags .iter() .find(|f| f.type_(&env.library).c_type == type_) { Some(flag.type_id) } else { None }; type_id.map(|ty| gen_symbol_doc_link(ty, env)) } fn find_method_or_function_by_ctype( c_type: Option<&str>, name: &str, env: &Env, in_type: Option<(&TypeId, Option)>, ) -> Option { find_method_or_function( env, in_type, |f| f.glib_name == name, |o| c_type.map_or(true, |t| o.c_type == t), |r| c_type.map_or(true, |t| r.type_(&env.library).c_type == t), |r| c_type.map_or(true, |t| r.type_(&env.library).c_type == t), |r| c_type.map_or(true, |t| r.type_(&env.library).c_type == t), c_type.is_some_and(|t| t.ends_with("Class")), false, ) } /// Find a function in all the possible items, if not found return the original /// name surrounded with backticks. A function can either be a /// struct/interface/record method, a global function or maybe a virtual /// function /// /// This function is generic so it can be de-duplicated between a /// - [`find_method_or_function_by_ctype()`] where the object/records are looked /// by their C name /// - [`gi_docgen::find_method_or_function_by_name()`] where the object/records /// are looked by their name pub(crate) fn find_method_or_function( env: &Env, in_type: Option<(&TypeId, Option)>, search_fn: impl Fn(&crate::analysis::functions::Info) -> bool + Copy, search_obj: impl Fn(&crate::analysis::object::Info) -> bool + Copy, search_record: impl Fn(&crate::analysis::record::Info) -> bool + Copy, search_enum: impl Fn(&crate::analysis::enums::Info) -> bool + Copy, search_flag: impl Fn(&crate::analysis::flags::Info) -> bool + Copy, is_class_method: bool, is_virtual_method: bool, ) -> Option { if is_virtual_method { if let Some((obj_info, fn_info)) = env .analysis .find_object_by_virtual_method(env, search_obj, search_fn) { Some(gen_object_fn_doc_link( obj_info, fn_info, env, in_type, &obj_info.name, )) } else { None } } else if is_class_method { if let Some((record_info, fn_info)) = env.analysis .find_record_by_function(env, search_record, search_fn) { let object = env.config.objects.get(&record_info.full_name); let visible_parent = object .and_then(|o| o.trait_name.clone()) .unwrap_or_else(|| format!("{}Ext", record_info.name)); let parent = format!("subclass::prelude::{}", visible_parent); let is_self = in_type == Some((&record_info.type_id, None)); Some(fn_info.doc_link(Some(&parent), Some(&visible_parent), is_self)) } else { None } // if we can find the function in an object } else if let Some((obj_info, fn_info)) = env .analysis .find_object_by_function(env, search_obj, search_fn) { Some(gen_object_fn_doc_link( obj_info, fn_info, env, in_type, &obj_info.name, )) // or in a record } else if let Some((record_info, fn_info)) = env.analysis .find_record_by_function(env, search_record, search_fn) { Some(gen_type_fn_doc_link( record_info.type_id, fn_info, env, in_type, )) } else if let Some((enum_info, fn_info)) = env.analysis .find_enum_by_function(env, search_enum, search_fn) { Some(gen_type_fn_doc_link( enum_info.type_id, fn_info, env, in_type, )) } else if let Some((flag_info, fn_info)) = env.analysis .find_flag_by_function(env, search_flag, search_fn) { Some(gen_type_fn_doc_link( flag_info.type_id, fn_info, env, in_type, )) // or as a global function } else if let Some(fn_info) = env.analysis.find_global_function(env, search_fn) { Some(fn_info.doc_link(None, None, false)) } else { None } } pub(crate) fn gen_type_fn_doc_link( type_id: TypeId, fn_info: &Info, env: &Env, in_type: Option<(&TypeId, Option)>, ) -> String { let symbols = env.symbols.borrow(); let sym_name = symbols.by_tid(type_id).unwrap().full_rust_name(); let is_self = in_type == Some((&type_id, None)); fn_info.doc_link(Some(&sym_name), None, is_self) } pub(crate) fn gen_object_fn_doc_link( obj_info: &crate::analysis::object::Info, fn_info: &Info, env: &Env, in_type: Option<(&TypeId, Option)>, visible_name: &str, ) -> String { let symbols = env.symbols.borrow(); let sym = symbols.by_tid(obj_info.type_id).unwrap(); let is_self = in_type == Some((&obj_info.type_id, Some(obj_info.function_location(fn_info)))); if fn_info.kind == FunctionKind::VirtualMethod || fn_info.kind == FunctionKind::ClassMethod { let (type_name, visible_type_name) = obj_info.generate_doc_link_info(fn_info); fn_info.doc_link( Some(&sym.full_rust_name().replace(visible_name, &type_name)), Some(&visible_type_name), false, ) } else if fn_info.kind == FunctionKind::Method { let (type_name, visible_type_name) = obj_info.generate_doc_link_info(fn_info); fn_info.doc_link( Some(&sym.full_rust_name().replace(visible_name, &type_name)), Some(&visible_type_name), is_self, ) } else { fn_info.doc_link(Some(&sym.full_rust_name()), None, is_self) } } // Helper function to generate a doc link for an enum member/bitfield variant pub(crate) fn gen_member_doc_link( type_id: TypeId, member_name: &str, env: &Env, in_type: Option<(&TypeId, Option)>, ) -> String { let symbols = env.symbols.borrow(); let sym = symbols.by_tid(type_id).unwrap().full_rust_name(); let is_self = in_type == Some((&type_id, None)); if is_self { format!("[`{member_name}`][Self::{member_name}]") } else { format!("[`{sym}::{member_name}`][crate::{sym}::{member_name}]") } } pub(crate) fn gen_const_doc_link(const_info: &crate::analysis::constants::Info) -> String { // for whatever reason constants are not part of the symbols list format!("[`{n}`][crate::{n}]", n = const_info.name) } pub(crate) fn gen_signal_doc_link(symbol: &str, signal: &str) -> String { format!("[`{signal}`][struct@crate::{symbol}#{signal}]") } pub(crate) fn gen_property_doc_link(symbol: &str, property: &str) -> String { format!("[`{property}`][struct@crate::{symbol}#{property}]") } pub(crate) fn gen_vfunc_doc_link(symbol: &str, vfunc: &str) -> String { format!("`vfunc::{symbol}::{vfunc}`") } pub(crate) fn gen_callback_doc_link(callback: &str) -> String { format!("`callback::{callback}") } pub(crate) fn gen_alias_doc_link(alias: &str) -> String { format!("`alias::{alias}`") } pub(crate) fn gen_symbol_doc_link(type_id: TypeId, env: &Env) -> String { let symbols = env.symbols.borrow(); let sym = symbols.by_tid(type_id).unwrap(); // Workaround the case of glib::Variant being a derive macro and a struct if sym.name() == "Variant" && (sym.crate_name().is_none() || sym.crate_name() == Some("glib")) { format!("[`{n}`][struct@crate::{n}]", n = sym.full_rust_name()) } else { format!("[`{n}`][crate::{n}]", n = sym.full_rust_name()) } } gir-0.20.5/src/codegen/doc/gi_docgen.rs000066400000000000000000000650661475434152100176260ustar00rootroot00000000000000use std::{ fmt::{self, Display, Formatter}, str::FromStr, sync::OnceLock, }; use regex::{Captures, Regex}; use super::format::find_method_or_function; use crate::{ analysis::object::LocationInObject, codegen::doc::format::{ gen_alias_doc_link, gen_callback_doc_link, gen_const_doc_link, gen_object_fn_doc_link, gen_property_doc_link, gen_signal_doc_link, gen_symbol_doc_link, gen_vfunc_doc_link, }, library::{TypeId, MAIN_NAMESPACE}, nameutil::mangle_keywords, Env, }; #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum GiDocgenError { InvalidLinkType(String), BrokenLinkType(String), InvalidLink, } impl Display for GiDocgenError { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { Self::InvalidLinkType(e) => f.write_str(&format!("Invalid link type \"{e}\"")), Self::BrokenLinkType(e) => f.write_str(&format!("Broken link syntax for type \"{e}\"")), Self::InvalidLink => f.write_str("Invalid link syntax"), } } } impl std::error::Error for GiDocgenError {} /// Convert a "Namespace.Type" to (Option, Type) fn namespace_type_from_details( link_details: &str, link_type: &str, ) -> Result<(Option, String), GiDocgenError> { let res: Vec<&str> = link_details.split('.').collect(); let len = res.len(); if len == 1 { Ok((None, res[0].to_string())) } else if len == 2 { if res[1].is_empty() { Err(GiDocgenError::BrokenLinkType(link_type.to_string())) } else { Ok((Some(res[0].to_string()), res[1].to_string())) } } else { Err(GiDocgenError::BrokenLinkType(link_type.to_string())) } } /// Convert a "Namespace.Type.method_name" to (Option, Option, /// name) Type is only optional for global functions and the order can be /// modified the `is_global_func` parameters fn namespace_type_method_from_details( link_details: &str, link_type: &str, is_global_func: bool, ) -> Result<(Option, Option, String), GiDocgenError> { let res: Vec<&str> = link_details.split('.').collect(); let len = res.len(); if len == 1 { Ok((None, None, res[0].to_string())) } else if len == 2 { if res[1].is_empty() { Err(GiDocgenError::BrokenLinkType(link_type.to_string())) } else if is_global_func { Ok((Some(res[0].to_string()), None, res[1].to_string())) } else { Ok((None, Some(res[0].to_string()), res[1].to_string())) } } else if len == 3 { if res[2].is_empty() { Err(GiDocgenError::BrokenLinkType(link_type.to_string())) } else { Ok(( Some(res[0].to_string()), Some(res[1].to_string()), res[2].to_string(), )) } } else { Err(GiDocgenError::BrokenLinkType(link_type.to_string())) } } fn gi_docgen_symbols() -> &'static Regex { static REGEX: OnceLock = OnceLock::new(); REGEX.get_or_init(|| { Regex::new(r"\[(callback|id|alias|class|const|ctor|enum|error|flags|func|iface|method|property|signal|struct|vfunc)[@](\w+\b)([:.]+[\w-]+\b)?([:.]+[\w-]+\b)?\]?").unwrap() }) } pub(crate) fn replace_c_types( entry: &str, env: &Env, in_type: Option<(&TypeId, Option)>, ) -> String { gi_docgen_symbols() .replace_all(entry, |caps: &Captures<'_>| { if let Ok(gi_type) = GiDocgen::from_str(&caps[0]) { gi_type.rust_link(env, in_type) } else { // otherwise fallback to the original string caps[0].to_string() } }) .to_string() } /// A representation of the various ways to link items using GI-docgen /// /// See for details. #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum GiDocgen { // C-identifier Id(String), // Alias to another type Alias(String), // Object Class Class { namespace: Option, type_: String, }, Const { namespace: Option, type_: String, }, Constructor { namespace: Option, type_: String, name: String, }, Callback { namespace: Option, name: String, }, Enum { namespace: Option, type_: String, }, Error { namespace: Option, type_: String, }, Flag { namespace: Option, type_: String, }, Func { namespace: Option, type_: Option, name: String, }, Interface { namespace: Option, type_: String, }, Method { namespace: Option, type_: String, name: String, is_class_method: bool, // Whether `type_` ends with Class }, Property { namespace: Option, type_: String, name: String, }, Signal { namespace: Option, type_: String, name: String, }, Struct { namespace: Option, type_: String, }, VFunc { namespace: Option, type_: String, name: String, }, } fn ns_type_to_doc(namespace: &Option, type_: &str) -> String { if let Some(ns) = namespace { format!("{ns}::{type_}") } else { type_.to_string() } } fn find_virtual_method_by_name( type_: Option<&str>, namespace: Option<&str>, name: &str, env: &Env, in_type: Option<(&TypeId, Option)>, ) -> Option { find_method_or_function( env, in_type, |f| { f.name == mangle_keywords(name) && namespace.as_ref().map_or(f.ns_id == MAIN_NAMESPACE, |n| { &env.library.namespaces[f.ns_id as usize].name == n }) }, |o| { type_.map_or(true, |t| { o.name == t && is_same_namespace(env, namespace, o.type_id) }) }, |_| false, |_| false, |_| false, false, true, ) } fn find_method_or_function_by_name( type_: Option<&str>, namespace: Option<&str>, name: &str, env: &Env, in_type: Option<(&TypeId, Option)>, is_class_method: bool, ) -> Option { find_method_or_function( env, in_type, |f| { f.name == mangle_keywords(name) && namespace.as_ref().map_or(f.ns_id == MAIN_NAMESPACE, |n| { &env.library.namespaces[f.ns_id as usize].name == n }) }, |o| { type_.map_or(true, |t| { o.name == t && is_same_namespace(env, namespace, o.type_id) }) }, |r| { type_.map_or(true, |t| { r.name == t && is_same_namespace(env, namespace, r.type_id) }) }, |e| { type_.map_or(true, |t| { e.name == t && is_same_namespace(env, namespace, e.type_id) }) }, |f| { type_.map_or(true, |t| { f.name == t && is_same_namespace(env, namespace, f.type_id) }) }, is_class_method, false, ) } fn is_same_namespace(env: &Env, namespace: Option<&str>, type_id: TypeId) -> bool { namespace .as_ref() .map_or(MAIN_NAMESPACE == type_id.ns_id, |n| { &env.library.namespaces[type_id.ns_id as usize].name == n }) } impl GiDocgen { pub fn rust_link( &self, env: &Env, in_type: Option<(&TypeId, Option)>, ) -> String { let symbols = env.symbols.borrow(); match self { GiDocgen::Enum { type_, namespace } | GiDocgen::Error { type_, namespace } => env .analysis .enumerations .iter() .find(|e| &e.name == type_) .map_or_else( || format!("`{}`", ns_type_to_doc(namespace, type_)), |info| gen_symbol_doc_link(info.type_id, env), ), GiDocgen::Class { type_, namespace } | GiDocgen::Interface { type_, namespace } => env .analysis .objects .values() .find(|o| { &o.name == type_ && is_same_namespace(env, namespace.as_deref(), o.type_id) }) .map_or_else( || format!("`{}`", ns_type_to_doc(namespace, type_)), |info| gen_symbol_doc_link(info.type_id, env), ), GiDocgen::Flag { type_, namespace } => env .analysis .flags .iter() .find(|e| { &e.name == type_ && is_same_namespace(env, namespace.as_deref(), e.type_id) }) .map_or_else( || format!("`{}`", ns_type_to_doc(namespace, type_)), |info| gen_symbol_doc_link(info.type_id, env), ), GiDocgen::Const { type_, namespace } => env .analysis .constants .iter() .find(|c| &c.name == type_ && is_same_namespace(env, namespace.as_deref(), c.typ)) .map_or_else( || format!("`{}`", ns_type_to_doc(namespace, type_)), gen_const_doc_link, ), GiDocgen::Property { type_, name, namespace, } => env .analysis .objects .values() .find(|o| { &o.name == type_ && is_same_namespace(env, namespace.as_deref(), o.type_id) }) .map_or_else( || gen_property_doc_link(&ns_type_to_doc(namespace, type_), name), |info| { let sym = symbols.by_tid(info.type_id).unwrap(); gen_property_doc_link(&sym.full_rust_name(), name) }, ), GiDocgen::Signal { type_, name, namespace, } => env .analysis .objects .values() .find(|o| { &o.name == type_ && is_same_namespace(env, namespace.as_deref(), o.type_id) }) .map_or_else( || gen_signal_doc_link(&ns_type_to_doc(namespace, type_), name), |info| { let sym = symbols.by_tid(info.type_id).unwrap(); gen_signal_doc_link(&sym.full_rust_name(), name) }, ), GiDocgen::Id(c_name) => symbols.by_c_name(c_name).map_or_else( || format!("`{c_name}`"), |sym| format!("[`{n}`][crate::{n}]", n = sym.full_rust_name()), ), GiDocgen::Struct { namespace, type_ } => env .analysis .records .values() .find(|r| { &r.name == type_ && is_same_namespace(env, namespace.as_deref(), r.type_id) }) .map_or_else( || format!("`{}`", ns_type_to_doc(namespace, type_)), |info| gen_symbol_doc_link(info.type_id, env), ), GiDocgen::Constructor { namespace, type_, name, } => env .analysis .find_object_by_function( env, |o| &o.name == type_ && is_same_namespace(env, namespace.as_deref(), o.type_id), |f| f.name == mangle_keywords(name), ) .map_or_else( || format!("`{}::{}()`", ns_type_to_doc(namespace, type_), name), |(obj_info, fn_info)| { gen_object_fn_doc_link(obj_info, fn_info, env, in_type, type_) }, ), GiDocgen::Func { namespace, type_, name, } => find_method_or_function_by_name( type_.as_deref(), namespace.as_deref(), name, env, in_type, false, ) .unwrap_or_else(|| { if let Some(ty) = type_ { format!("`{}::{}()`", ns_type_to_doc(namespace, ty), name) } else { format!("`{name}()`") } }), GiDocgen::Alias(alias) => gen_alias_doc_link(alias), GiDocgen::Method { namespace, type_, name, is_class_method, } => find_method_or_function_by_name( Some(type_), namespace.as_deref(), name, env, in_type, *is_class_method, ) .unwrap_or_else(|| format!("`{}::{}()`", ns_type_to_doc(namespace, type_), name)), GiDocgen::Callback { namespace, name } => { gen_callback_doc_link(&ns_type_to_doc(namespace, name)) } GiDocgen::VFunc { namespace, type_, name, } => find_virtual_method_by_name(Some(type_), namespace.as_deref(), name, env, in_type) .unwrap_or_else(|| gen_vfunc_doc_link(&ns_type_to_doc(namespace, type_), name)), } } } impl FromStr for GiDocgen { type Err = GiDocgenError; // We assume the string is contained inside a [] fn from_str(item_link: &str) -> Result { let item_link = item_link.trim_start_matches('[').trim_end_matches(']'); if let Some((link_type, link_details)) = item_link.split_once('@') { match link_type { "alias" => Ok(Self::Alias(link_details.to_string())), "class" => { let (namespace, type_) = namespace_type_from_details(link_details, "class")?; Ok(Self::Class { namespace, type_ }) } "const" => { let (namespace, type_) = namespace_type_from_details(link_details, "const")?; Ok(Self::Const { namespace, type_ }) } "ctor" => { let (namespace, type_, name) = namespace_type_method_from_details(link_details, "ctor", false)?; Ok(Self::Constructor { namespace, type_: type_ .ok_or_else(|| GiDocgenError::BrokenLinkType("ctor".to_string()))?, name, }) } "enum" => { let (namespace, type_) = namespace_type_from_details(link_details, "enum")?; Ok(Self::Enum { namespace, type_ }) } "error" => { let (namespace, type_) = namespace_type_from_details(link_details, "error")?; Ok(Self::Error { namespace, type_ }) } "flags" => { let (namespace, type_) = namespace_type_from_details(link_details, "flags")?; Ok(Self::Flag { namespace, type_ }) } "func" => { let (namespace, type_, name) = namespace_type_method_from_details(link_details, "func", true)?; Ok(Self::Func { namespace, type_, name, }) } "iface" => { let (namespace, type_) = namespace_type_from_details(link_details, "iface")?; Ok(Self::Interface { namespace, type_ }) } "callback" => { let (namespace, name) = namespace_type_from_details(link_details, "callback")?; Ok(Self::Callback { namespace, name }) } "method" => { let (namespace, type_, name) = namespace_type_method_from_details(link_details, "method", false)?; let type_ = type_.ok_or_else(|| GiDocgenError::BrokenLinkType("method".to_string()))?; Ok(Self::Method { namespace, is_class_method: type_.ends_with("Class"), type_, name, }) } "property" => { let (namespace, type_) = namespace_type_from_details(link_details, "property")?; let type_details: Vec<_> = type_.split(':').collect(); if type_details.len() < 2 || type_details[1].is_empty() { Err(GiDocgenError::BrokenLinkType("property".to_string())) } else { Ok(Self::Property { namespace, type_: type_details[0].to_string(), name: type_details[1].to_string(), }) } } "signal" => { let (namespace, type_) = namespace_type_from_details(link_details, "signal")?; let type_details: Vec<_> = type_.split("::").collect(); if type_details.len() < 2 || type_details[1].is_empty() { Err(GiDocgenError::BrokenLinkType("signal".to_string())) } else { Ok(Self::Signal { namespace, type_: type_details[0].to_string(), name: type_details[1].to_string(), }) } } "struct" => { let (namespace, type_) = namespace_type_from_details(link_details, "struct")?; Ok(Self::Struct { namespace, type_ }) } "vfunc" => { let (namespace, type_, name) = namespace_type_method_from_details(link_details, "vfunc", false)?; Ok(Self::VFunc { namespace, type_: type_ .ok_or_else(|| GiDocgenError::BrokenLinkType("vfunc".to_string()))?, name, }) } "id" => Ok(Self::Id(link_details.to_string())), e => Err(GiDocgenError::InvalidLinkType(e.to_string())), } } else { Err(GiDocgenError::InvalidLink) } } } #[cfg(test)] mod tests { use super::*; #[test] fn test_link_alias() { assert_eq!( GiDocgen::from_str("[alias@Allocation]"), Ok(GiDocgen::Alias("Allocation".to_string())) ); } #[test] fn test_link_class() { assert_eq!( GiDocgen::from_str("[class@Widget]"), Ok(GiDocgen::Class { namespace: None, type_: "Widget".to_string(), }) ); assert_eq!( GiDocgen::from_str("[class@Gdk.Surface]"), Ok(GiDocgen::Class { namespace: Some("Gdk".to_string()), type_: "Surface".to_string(), }) ); assert_eq!( GiDocgen::from_str("[class@Gsk.RenderNode]"), Ok(GiDocgen::Class { namespace: Some("Gsk".to_string()), type_: "RenderNode".to_string(), }) ); assert_eq!( GiDocgen::from_str("[class@Gsk.RenderNode.test]"), Err(GiDocgenError::BrokenLinkType("class".to_string())) ); assert_eq!( GiDocgen::from_str("[class@Gsk.]"), Err(GiDocgenError::BrokenLinkType("class".to_string())) ); } #[test] fn test_link_id() { assert_eq!( GiDocgen::from_str("[id@gtk_widget_show]"), Ok(GiDocgen::Id("gtk_widget_show".to_string())) ); } #[test] fn test_link_const() { assert_eq!( GiDocgen::from_str("[const@Gdk.KEY_q]"), Ok(GiDocgen::Const { namespace: Some("Gdk".to_string()), type_: "KEY_q".to_string() }) ); } #[test] fn test_link_callback() { assert_eq!( GiDocgen::from_str("[callback@Gtk.MapListModelMapFunc]"), Ok(GiDocgen::Callback { namespace: Some("Gtk".to_string()), name: "MapListModelMapFunc".to_string() }) ); } #[test] fn test_link_enum() { assert_eq!( GiDocgen::from_str("[enum@Orientation]"), Ok(GiDocgen::Enum { namespace: None, type_: "Orientation".to_string() }) ); } #[test] fn test_link_error() { assert_eq!( GiDocgen::from_str("[error@Gtk.BuilderParseError]"), Ok(GiDocgen::Error { namespace: Some("Gtk".to_string()), type_: "BuilderParseError".to_string() }) ); } #[test] fn test_link_flags() { assert_eq!( GiDocgen::from_str("[flags@Gdk.ModifierType]"), Ok(GiDocgen::Flag { namespace: Some("Gdk".to_string()), type_: "ModifierType".to_string() }) ); } #[test] fn test_link_iface() { assert_eq!( GiDocgen::from_str("[iface@Gtk.Buildable]"), Ok(GiDocgen::Interface { namespace: Some("Gtk".to_string()), type_: "Buildable".to_string() }) ); } #[test] fn test_link_struct() { assert_eq!( GiDocgen::from_str("[struct@Gtk.TextIter]"), Ok(GiDocgen::Struct { namespace: Some("Gtk".to_string()), type_: "TextIter".to_string() }) ); } #[test] fn test_link_property() { assert_eq!( GiDocgen::from_str("[property@Gtk.Orientable:orientation]"), Ok(GiDocgen::Property { namespace: Some("Gtk".to_string()), type_: "Orientable".to_string(), name: "orientation".to_string(), }) ); assert_eq!( GiDocgen::from_str("[property@Gtk.Orientable]"), Err(GiDocgenError::BrokenLinkType("property".to_string())) ); assert_eq!( GiDocgen::from_str("[property@Gtk.Orientable:]"), Err(GiDocgenError::BrokenLinkType("property".to_string())) ); } #[test] fn test_link_signal() { assert_eq!( GiDocgen::from_str("[signal@Gtk.RecentManager::changed]"), Ok(GiDocgen::Signal { namespace: Some("Gtk".to_string()), type_: "RecentManager".to_string(), name: "changed".to_string(), }) ); assert_eq!( GiDocgen::from_str("[signal@Gtk.RecentManager]"), Err(GiDocgenError::BrokenLinkType("signal".to_string())) ); assert_eq!( GiDocgen::from_str("[signal@Gtk.RecentManager::]"), Err(GiDocgenError::BrokenLinkType("signal".to_string())) ); assert_eq!( GiDocgen::from_str("[signal@Gtk.RecentManager:]"), Err(GiDocgenError::BrokenLinkType("signal".to_string())) ); } #[test] fn test_link_vfunc() { assert_eq!( GiDocgen::from_str("[vfunc@Gtk.Widget.measure]"), Ok(GiDocgen::VFunc { namespace: Some("Gtk".to_string()), type_: "Widget".to_string(), name: "measure".to_string(), }) ); assert_eq!( GiDocgen::from_str("[vfunc@Widget.snapshot]"), Ok(GiDocgen::VFunc { namespace: None, type_: "Widget".to_string(), name: "snapshot".to_string(), }) ); } #[test] fn test_link_ctor() { assert_eq!( GiDocgen::from_str("[ctor@Gtk.Box.new]"), Ok(GiDocgen::Constructor { namespace: Some("Gtk".to_string()), type_: "Box".to_string(), name: "new".to_string(), }) ); assert_eq!( GiDocgen::from_str("[ctor@Button.new_with_label]"), Ok(GiDocgen::Constructor { namespace: None, type_: "Button".to_string(), name: "new_with_label".to_string(), }) ); } #[test] fn test_link_func() { assert_eq!( GiDocgen::from_str("[func@Gtk.init]"), Ok(GiDocgen::Func { namespace: Some("Gtk".to_string()), type_: None, name: "init".to_string(), }) ); assert_eq!( GiDocgen::from_str("[func@show_uri]"), Ok(GiDocgen::Func { namespace: None, type_: None, name: "show_uri".to_string(), }) ); assert_eq!( GiDocgen::from_str("[func@Gtk.Window.list_toplevels]"), Ok(GiDocgen::Func { namespace: Some("Gtk".to_string()), type_: Some("Window".to_string()), name: "list_toplevels".to_string(), }) ); } #[test] fn test_link_method() { assert_eq!( GiDocgen::from_str("[method@Gtk.Widget.show]"), Ok(GiDocgen::Method { namespace: Some("Gtk".to_string()), type_: "Widget".to_string(), name: "show".to_string(), is_class_method: false, }) ); assert_eq!( GiDocgen::from_str("[method@WidgetClass.add_binding]"), Ok(GiDocgen::Method { namespace: None, type_: "WidgetClass".to_string(), name: "add_binding".to_string(), is_class_method: true, }) ); } } gir-0.20.5/src/codegen/doc/mod.rs000066400000000000000000001166451475434152100164670ustar00rootroot00000000000000use std::{ borrow::Cow, collections::{BTreeSet, HashSet}, io::{Result, Write}, sync::OnceLock, }; use log::{error, info}; use regex::{Captures, Regex}; use stripper_lib::{write_file_name, write_item_doc, Type as SType, TypeStruct}; use self::format::reformat_doc; use crate::{ analysis::{self, namespaces::MAIN, object::LocationInObject}, config::gobjects::GObject, env::Env, file_saver::save_to_file, library::{self, Type as LType, *}, nameutil, traits::*, version::Version, }; mod format; mod gi_docgen; // A list of C parameters that are not used directly by the Rust bindings const IGNORED_C_FN_PARAMS: [&str; 6] = [ "user_data", "user_destroy", "destroy_func", "dnotify", "destroy", "user_data_free_func", ]; trait ToStripperType { fn to_stripper_type(&self) -> TypeStruct; } macro_rules! impl_to_stripper_type { ($ty:ident, $enum_var:ident, $useless:expr) => { impl ToStripperType for $ty { fn to_stripper_type(&self) -> TypeStruct { TypeStruct::new( SType::$enum_var, &format!( "connect_{}", nameutil::mangle_keywords(nameutil::signal_to_snake(&self.name)) ), ) } } }; ($ty:ident, $enum_var:ident) => { impl ToStripperType for $ty { fn to_stripper_type(&self) -> TypeStruct { TypeStruct::new(SType::$enum_var, &nameutil::mangle_keywords(&self.name)) } } }; } trait FunctionLikeType { fn doc(&self) -> &Option; fn doc_deprecated(&self) -> &Option; fn ret(&self) -> &Parameter; fn parameters(&self) -> &[Parameter]; fn deprecated_version(&self) -> &Option; } macro_rules! impl_function_like_type { ($ty:ident) => { impl FunctionLikeType for $ty { fn doc(&self) -> &Option { &self.doc } fn doc_deprecated(&self) -> &Option { &self.doc_deprecated } fn ret(&self) -> &Parameter { &self.ret } fn parameters(&self) -> &[Parameter] { &self.parameters } fn deprecated_version(&self) -> &Option { &self.deprecated_version } } }; } impl_to_stripper_type!(Enumeration, Enum); impl_to_stripper_type!(Bitfield, Struct); impl_to_stripper_type!(Record, Struct); impl_to_stripper_type!(Class, Struct); impl_to_stripper_type!(Function, Fn); impl_to_stripper_type!(Signal, Fn, false); impl_function_like_type!(Function); impl_function_like_type!(Signal); pub fn generate(env: &Env) { info!("Generating documentation {:?}", env.config.doc_target_path); save_to_file(&env.config.doc_target_path, env.config.make_backup, |w| { generate_doc(w, env) }); } #[allow(clippy::type_complexity)] fn generate_doc(w: &mut dyn Write, env: &Env) -> Result<()> { write_file_name(w, None)?; let mut generators: Vec<(&str, Box Result<()>>)> = Vec::new(); for info in env.analysis.objects.values() { if info.type_id.ns_id == MAIN && !env.is_totally_deprecated(None, info.deprecated_version) { generators.push(( &info.name, Box::new(move |w, e| create_object_doc(w, e, info)), )); } } for info in env.analysis.records.values() { if info.type_id.ns_id == MAIN && !env.is_totally_deprecated(None, info.deprecated_version) { generators.push(( &info.name, Box::new(move |w, e| create_record_doc(w, e, info)), )); } } for (tid, type_) in env.library.namespace_types(MAIN) { if let LType::Enumeration(enum_) = type_ { if !env .config .objects .get(&tid.full_name(&env.library)) .map_or(true, |obj| obj.status.ignored()) && !env.is_totally_deprecated(None, enum_.deprecated_version) { generators.push(( enum_.name.as_str(), Box::new(move |w, e| create_enum_doc(w, e, enum_, tid)), )); } } else if let LType::Bitfield(bitfield) = type_ { if !env .config .objects .get(&tid.full_name(&env.library)) .map_or(true, |obj| obj.status.ignored()) && !env.is_totally_deprecated(None, bitfield.deprecated_version) { generators.push(( bitfield.name.as_str(), Box::new(move |w, e| create_bitfield_doc(w, e, bitfield, tid)), )); } } } let ns = env.library.namespace(library::MAIN_NAMESPACE); if let Some(ref global_functions) = env.analysis.global_functions { let functions = ns .functions .iter() .filter(|f| f.kind == library::FunctionKind::Global); for function in functions { if let Some(ref c_identifier) = function.c_identifier { let f_info = global_functions .functions .iter() .find(move |f| &f.glib_name == c_identifier); let fn_new_name = f_info.and_then(|analysed_f| analysed_f.new_name.clone()); let doc_trait_name = f_info.and_then(|f| f.doc_trait_name.as_ref()); let doc_struct_name = f_info.and_then(|f| f.doc_struct_name.as_ref()); assert!( !(doc_trait_name.is_some() && doc_struct_name.is_some()), "Can't use both doc_trait_name and doc_struct_name on the same function" ); let parent = if doc_trait_name.is_some() { doc_trait_name.map(|p| Box::new(TypeStruct::new(SType::Trait, p))) } else if doc_struct_name.is_some() { doc_struct_name.map(|p| Box::new(TypeStruct::new(SType::Impl, p))) } else { None }; let doc_ignored_parameters = f_info .map(|analyzed_f| analyzed_f.doc_ignore_parameters.clone()) .unwrap_or_default(); let should_be_documented = f_info.is_some_and(|f| f.should_docs_be_generated(env)); if !should_be_documented { continue; } create_fn_doc( w, env, function, parent, fn_new_name, &doc_ignored_parameters, None, f_info.map_or(true, |f| f.generate_doc), )?; } } } for constant in &ns.constants { // strings are mapped to a static let ty = if constant.c_type == "gchar*" { SType::Static } else { SType::Const }; let ty_id = TypeStruct::new(ty, &constant.name); let generate_doc = env .config .objects .get(&constant.typ.full_name(&env.library)) .map_or(true, |c| c.generate_doc); if generate_doc { write_item_doc(w, &ty_id, |w| { if let Some(ref doc) = constant.doc { writeln!(w, "{}", reformat_doc(doc, env, Some((&constant.typ, None))))?; } Ok(()) })?; } } generators.sort_by_key(|&(name, _)| name); for (_, f) in generators { f(w, env)?; } Ok(()) } fn create_object_doc(w: &mut dyn Write, env: &Env, info: &analysis::object::Info) -> Result<()> { let ty = TypeStruct::new(SType::Struct, &info.name); let ty_ext = TypeStruct::new(SType::Trait, &info.trait_name); let has_trait = info.generate_trait; let doc; let doc_deprecated; let functions: &[Function]; let virtual_methods: &[Function]; let signals: &[Signal]; let properties: &[Property]; let is_abstract; let has_builder; let obj = env .config .objects .get(&info.full_name) .expect("Object not found"); match env.library.type_(info.type_id) { Type::Class(cl) => { doc = cl.doc.as_ref(); doc_deprecated = cl.doc_deprecated.as_ref(); functions = &cl.functions; virtual_methods = &cl.virtual_methods; signals = &cl.signals; properties = &cl.properties; is_abstract = env.library.type_(info.type_id).is_abstract(); has_builder = obj.generate_builder; } Type::Interface(iface) => { doc = iface.doc.as_ref(); doc_deprecated = iface.doc_deprecated.as_ref(); functions = &iface.functions; virtual_methods = &iface.virtual_methods; signals = &iface.signals; properties = &iface.properties; is_abstract = false; has_builder = false; } _ => unreachable!(), } let manual_traits = get_type_manual_traits_for_implements(env, info); write_item_doc(w, &ty, |w| { if let Some(doc) = doc_deprecated { writeln!( w, "{}", reformat_doc( doc, env, Some((&info.type_id, Some(LocationInObject::Impl))) ) )?; } if let (Some(doc), true) = (doc, obj.generate_doc) { writeln!( w, "{}", reformat_doc( doc, env, Some((&info.type_id, Some(LocationInObject::Impl))) ) )?; } else { writeln!(w)?; } if is_abstract { writeln!( w, "\nThis is an Abstract Base Class, you cannot instantiate it." )?; } if !properties.is_empty() { writeln!(w, "\n## Properties")?; document_type_properties(env, w, info, properties, None)?; for parent_info in &info.supertypes { match env.library.type_(parent_info.type_id) { Type::Class(cl) => { if !cl.properties.is_empty() { document_type_properties(env, w, info, &cl.properties, Some(&cl.name))?; } } Type::Interface(iface) => { if !iface.properties.is_empty() { document_type_properties( env, w, info, &iface.properties, Some(&iface.name), )?; } } _ => (), } } } if !signals.is_empty() { writeln!(w, "\n## Signals")?; document_type_signals(env, w, info, signals, None)?; for parent_info in &info.supertypes { match env.library.type_(parent_info.type_id) { Type::Class(cl) => { if !cl.signals.is_empty() { document_type_signals(env, w, info, &cl.signals, Some(&cl.name))?; } } Type::Interface(iface) => { if !iface.signals.is_empty() { document_type_signals(env, w, info, &iface.signals, Some(&iface.name))?; } } _ => (), } } } let impl_self = if has_trait { Some(info.type_id) } else { None }; let mut implements = impl_self .iter() .chain(env.class_hierarchy.supertypes(info.type_id)) .filter(|&tid| { !env.type_status(&tid.full_name(&env.library)).ignored() && !env.type_(*tid).is_final_type() && !env.type_(*tid).is_fundamental() }) .map(|&tid| get_type_trait_for_implements(env, tid)) .collect::>(); implements.extend(manual_traits); if !implements.is_empty() { writeln!(w, "\n# Implements\n")?; writeln!(w, "{}", &implements.join(", "))?; } Ok(()) })?; if has_builder { let builder_ty = TypeStruct::new(SType::Impl, &format!("{}Builder", info.name)); let mut builder_properties: Vec<_> = properties.iter().collect(); for parent_info in &info.supertypes { match env.library.type_(parent_info.type_id) { Type::Class(cl) => { builder_properties.extend(cl.properties.iter().filter(|p| p.writable)); } Type::Interface(iface) => { builder_properties.extend(iface.properties.iter().filter(|p| p.writable)); } _ => (), } } for property in &builder_properties { if !property.writable { continue; } let ty = TypeStruct { ty: SType::Fn, name: nameutil::signal_to_snake(&property.name), parent: Some(Box::new(builder_ty.clone())), args: vec![], }; write_item_doc(w, &ty, |w| { if let Some(ref doc) = property.doc { writeln!( w, "{}", reformat_doc( &fix_param_names(doc, &None), env, Some((&info.type_id, Some(LocationInObject::Builder))) ) )?; } if let Some(ref doc) = property.doc_deprecated { writeln!( w, "{}", reformat_doc( &fix_param_names(doc, &None), env, Some((&info.type_id, Some(LocationInObject::Builder))) ) )?; } Ok(()) })?; } } if has_trait { write_item_doc(w, &ty_ext, |w| { writeln!(w, "Trait containing all [`struct@{}`] methods.", ty.name)?; let mut implementors = std::iter::once(info.type_id) .chain(env.class_hierarchy.subtypes(info.type_id)) .filter(|&tid| !env.type_status(&tid.full_name(&env.library)).ignored()) .map(|tid| { format!( "[`{0}`][struct@crate::{0}]", env.library.type_(tid).get_name() ) }) .collect::>(); implementors.sort(); writeln!(w, "\n# Implementors\n")?; writeln!(w, "{}", implementors.join(", "))?; Ok(()) })?; } let ty = TypeStruct { ty: SType::Impl, ..ty }; for function in functions { let configured_functions = obj.functions.matched(&function.name); let is_manual = configured_functions.iter().any(|f| f.status.manual()); let (ty, object_location) = if (has_trait || is_manual) && function.parameters.iter().any(|p| p.instance_parameter) && !info.final_type { if let Some(struct_name) = configured_functions .iter() .find_map(|f| f.doc_struct_name.as_ref()) { ( TypeStruct::new(SType::Impl, struct_name), Some(LocationInObject::Impl), ) } // We use "original_name" here to be sure to get the correct object since the "name" // field could have been renamed. else if let Some(trait_name) = configured_functions .iter() .find_map(|f| f.doc_trait_name.as_ref()) { ( TypeStruct::new(SType::Trait, trait_name), // Because we cannot sensibly deduce where the docs end up, // assume they're outside the docs so that no `Self::` links // are generated. It is currently quite uncommon to specify // the `{}Manual` trait, which would be ObjectLocation::ExtManual. None, ) } else if is_manual { ( TypeStruct::new(SType::Trait, &format!("{}ExtManual", info.name)), Some(LocationInObject::ExtManual), ) } else { (ty_ext.clone(), Some(LocationInObject::Ext)) } } else { (ty.clone(), Some(LocationInObject::Impl)) }; if let Some(c_identifier) = &function.c_identifier { let f_info = info.functions.iter().find(|f| &f.glib_name == c_identifier); let should_be_documented = f_info.is_some_and(|f| f.should_docs_be_generated(env)); if !should_be_documented { continue; } // Retrieve the new_name computed during analysis, if any let fn_new_name = f_info.and_then(|analysed_f| analysed_f.new_name.clone()); let doc_ignored_parameters = f_info .map(|analyzed_f| analyzed_f.doc_ignore_parameters.clone()) .unwrap_or_default(); create_fn_doc( w, env, function, Some(Box::new(ty)), fn_new_name, &doc_ignored_parameters, Some((&info.type_id, object_location)), f_info.map_or(true, |f| f.generate_doc), )?; } } for signal in signals { let configured_signals = obj.signals.matched(&signal.name); let (ty, object_location) = if has_trait { if let Some(trait_name) = configured_signals .iter() .find_map(|f| f.doc_trait_name.as_ref()) { (TypeStruct::new(SType::Trait, trait_name), None) } else { (ty_ext.clone(), Some(LocationInObject::Ext)) } } else { (ty.clone(), Some(LocationInObject::Impl)) }; create_fn_doc( w, env, signal, Some(Box::new(ty)), None, &HashSet::new(), Some((&info.type_id, object_location)), configured_signals.iter().all(|s| s.generate_doc), )?; } for function in virtual_methods { let configured_virtual_methods = obj.virtual_methods.matched(&function.name); let (ty, object_location) = if let Some(trait_name) = configured_virtual_methods .iter() .find_map(|f| f.doc_trait_name.as_ref()) { ( TypeStruct::new(SType::Trait, trait_name), // Because we cannot sensibly deduce where the docs end up, // assume they're outside the docs so that no `Self::` links // are generated. It is currently quite uncommon to specify // the `{}Manual` trait, which would be ObjectLocation::ExtManual. None, ) } else { ( TypeStruct::new(SType::Trait, &format!("{}Impl", info.name)), Some(LocationInObject::VirtualExt), ) }; if let Some(c_identifier) = &function.c_identifier { let f_info: Option<&analysis::functions::Info> = info .virtual_methods .iter() .find(|f| &f.glib_name == c_identifier); let should_be_documented = f_info.is_some_and(|f| f.should_docs_be_generated(env)); if !should_be_documented { continue; } // Retrieve the new_name computed during analysis, if any let fn_new_name = f_info.and_then(|analysed_f| analysed_f.new_name.clone()); let doc_ignored_parameters = f_info .map(|analyzed_f| analyzed_f.doc_ignore_parameters.clone()) .unwrap_or_default(); create_fn_doc( w, env, function, Some(Box::new(ty)), fn_new_name, &doc_ignored_parameters, Some((&info.type_id, object_location)), f_info.map_or(true, |f| f.generate_doc), )?; } } for property in properties { let getter_name = if property.getter.is_some() { None // Don't generate a getter for the property, there is a getter } else { info.properties .iter() .filter(|p| p.is_get) .find(|p| p.name == property.name) .map(|p| p.func_name.clone()) }; let setter_name = if property.setter.is_some() { None // don't generate a setter for the property, there is a setter } else { info.properties .iter() .filter(|p| !p.is_get) .find(|p| p.name == property.name) .map(|p| p.func_name.clone()) }; let (ty, object_location) = if has_trait { let configured_properties = obj.properties.matched(&property.name); if let Some(trait_name) = configured_properties .iter() .find_map(|f| f.doc_trait_name.as_ref()) { (TypeStruct::new(SType::Trait, trait_name), None) } else { (ty_ext.clone(), Some(LocationInObject::Ext)) } } else { (ty.clone(), Some(LocationInObject::Impl)) }; create_property_doc( w, env, property, Some(Box::new(ty)), (&info.type_id, object_location), getter_name, setter_name, info, )?; } Ok(()) } fn create_record_doc(w: &mut dyn Write, env: &Env, info: &analysis::record::Info) -> Result<()> { let record: &Record = env.library.type_(info.type_id).to_ref_as(); let ty = record.to_stripper_type(); let object = env.config.objects.get(&info.full_name); let trait_name = object .and_then(|o| o.trait_name.clone()) .unwrap_or_else(|| format!("{}Ext", info.name)); let generate_doc = object.map_or(true, |r| r.generate_doc); if generate_doc { write_item_doc(w, &ty, |w| { if let Some(ref doc) = record.doc { writeln!(w, "{}", reformat_doc(doc, env, Some((&info.type_id, None))))?; } if let Some(ver) = info.deprecated_version { writeln!(w, "\n# Deprecated since {ver}\n")?; } else if record.doc_deprecated.is_some() { writeln!(w, "\n# Deprecated\n")?; } if let Some(ref doc) = record.doc_deprecated { writeln!(w, "{}", reformat_doc(doc, env, Some((&info.type_id, None))))?; } Ok(()) })?; } for function in &record.functions { let function_ty = if function.kind == FunctionKind::ClassMethod { TypeStruct::new(SType::Trait, &trait_name) } else { TypeStruct { ty: SType::Impl, parent: ty.parent.clone(), name: ty.name.clone(), args: ty.args.clone(), } }; if let Some(c_identifier) = &function.c_identifier { let f_info = info.functions.iter().find(|f| &f.glib_name == c_identifier); let should_be_documented = f_info.is_some_and(|f| f.should_docs_be_generated(env)); if !should_be_documented { continue; } let fn_new_name = f_info.and_then(|analysed_f| analysed_f.new_name.clone()); create_fn_doc( w, env, function, Some(Box::new(function_ty)), fn_new_name, &HashSet::new(), Some((&info.type_id, None)), f_info.map_or(true, |f| f.generate_doc), )?; } } Ok(()) } fn create_enum_doc(w: &mut dyn Write, env: &Env, enum_: &Enumeration, tid: TypeId) -> Result<()> { let ty = enum_.to_stripper_type(); let config = env.config.objects.get(&tid.full_name(&env.library)); if config.map_or(true, |c| c.generate_doc) { write_item_doc(w, &ty, |w| { if let Some(ref doc) = enum_.doc { writeln!(w, "{}", reformat_doc(doc, env, Some((&tid, None))))?; } if let Some(ver) = enum_.deprecated_version { writeln!(w, "\n# Deprecated since {ver}\n")?; } else if enum_.doc_deprecated.is_some() { writeln!(w, "\n# Deprecated\n")?; } if let Some(ref doc) = enum_.doc_deprecated { writeln!(w, "{}", reformat_doc(doc, env, Some((&tid, None))))?; } Ok(()) })?; } for member in &enum_.members { let generate_doc = config .and_then(|m| { m.members .matched(&member.name) .first() .map(|m| m.generate_doc && !m.status.ignored()) }) .unwrap_or(true); if generate_doc && member.doc.is_some() { let sub_ty = TypeStruct { name: nameutil::enum_member_name(&member.name), parent: Some(Box::new(ty.clone())), ty: SType::Variant, args: Vec::new(), }; write_item_doc(w, &sub_ty, |w| { if let Some(ref doc) = member.doc { writeln!(w, "{}", reformat_doc(doc, env, Some((&tid, None))))?; } if let Some(ref doc) = member.doc_deprecated { writeln!(w, "{}", reformat_doc(doc, env, Some((&tid, None))))?; } Ok(()) })?; } } Ok(()) } fn create_bitfield_doc( w: &mut dyn Write, env: &Env, bitfield: &Bitfield, tid: TypeId, ) -> Result<()> { let ty = bitfield.to_stripper_type(); let config = env.config.objects.get(&tid.full_name(&env.library)); write_item_doc(w, &ty, |w| { if config.map_or(true, |c| c.generate_doc) { if let Some(ref doc) = bitfield.doc { writeln!(w, "{}", reformat_doc(doc, env, Some((&tid, None))))?; } } if let Some(ver) = bitfield.deprecated_version { writeln!(w, "\n# Deprecated since {ver}\n")?; } else if bitfield.doc_deprecated.is_some() { writeln!(w, "\n# Deprecated\n")?; } if let Some(ref doc) = bitfield.doc_deprecated { writeln!(w, "{}", reformat_doc(doc, env, Some((&tid, None))))?; } Ok(()) })?; for member in &bitfield.members { let generate_doc = config .and_then(|m| { m.members .matched(&member.name) .first() .map(|m| m.generate_doc) }) .unwrap_or(true); if generate_doc && member.doc.is_some() { let sub_ty = TypeStruct { name: nameutil::bitfield_member_name(&member.name), parent: Some(Box::new(ty.clone())), ty: SType::Const, args: Vec::new(), }; write_item_doc(w, &sub_ty, |w| { if let Some(ref doc) = member.doc { writeln!(w, "{}", reformat_doc(doc, env, Some((&tid, None))))?; } if let Some(ref doc) = member.doc_deprecated { writeln!(w, "{}", reformat_doc(doc, env, Some((&tid, None))))?; } Ok(()) })?; } } Ok(()) } fn param_name() -> &'static Regex { static REGEX: OnceLock = OnceLock::new(); REGEX.get_or_init(|| Regex::new(r"@(\w+)\b").unwrap()) } fn fix_param_names<'a>(doc: &'a str, self_name: &Option) -> Cow<'a, str> { param_name().replace_all(doc, |caps: &Captures<'_>| { if let Some(self_name) = self_name { if &caps[1] == self_name { return "@self".into(); } } format!("@{}", nameutil::mangle_keywords(&caps[1])) }) } fn create_fn_doc( w: &mut dyn Write, env: &Env, fn_: &T, parent: Option>, name_override: Option, doc_ignored_parameters: &HashSet, in_type: Option<(&TypeId, Option)>, generate_doc: bool, ) -> Result<()> where T: FunctionLikeType + ToStripperType, { if !generate_doc { return Ok(()); } if env.is_totally_deprecated(None, *fn_.deprecated_version()) { return Ok(()); } if fn_.doc().is_none() && fn_.doc_deprecated().is_none() && fn_.ret().doc.is_none() && fn_.parameters().iter().all(|p| p.doc.is_none()) { return Ok(()); } let mut st = fn_.to_stripper_type(); if let Some(name_override) = name_override { st.name = nameutil::mangle_keywords(name_override).to_string(); } let ty = TypeStruct { parent, ..st }; let self_name: Option = fn_ .parameters() .iter() .find(|p| p.instance_parameter) .map(|p| p.name.clone()); write_item_doc(w, &ty, |w| { if let Some(doc) = fn_.doc() { writeln!( w, "{}", reformat_doc(&fix_param_names(doc, &self_name), env, in_type) )?; } if let Some(ver) = fn_.deprecated_version() { writeln!(w, "\n# Deprecated since {ver}\n")?; } else if fn_.doc_deprecated().is_some() { writeln!(w, "\n# Deprecated\n")?; } if let Some(doc) = fn_.doc_deprecated() { writeln!( w, "{}", reformat_doc(&fix_param_names(doc, &self_name), env, in_type) )?; } // A list of parameter positions to filter out let mut indices_to_ignore: BTreeSet<_> = fn_ .parameters() .iter() .filter_map(|param| param.array_length) .collect(); if let Some(indice) = fn_.ret().array_length { indices_to_ignore.insert(indice); } // The original list of parameters without the ones that specify an array length let no_array_length_params: Vec<_> = fn_ .parameters() .iter() .enumerate() .filter_map(|(indice, param)| { (!indices_to_ignore.contains(&(indice as u32))).then_some(param) }) .filter(|param| !param.instance_parameter) .collect(); let in_parameters = no_array_length_params.iter().filter(|param| { let ignore = IGNORED_C_FN_PARAMS.contains(¶m.name.as_str()) || doc_ignored_parameters.contains(¶m.name) || param.direction == ParameterDirection::Out // special case error pointer as it's transformed to a Result || (param.name == "error" && param.c_type == "GError**") // special case `data` with explicit `gpointer` type as it could be something else (unlike `user_data`) || (param.name == "data" && param.c_type == "gpointer"); !ignore }); for parameter in in_parameters { if parameter.name.is_empty() { continue; } if let Some(ref doc) = parameter.doc { writeln!( w, "## `{}`", nameutil::mangle_keywords(parameter.name.as_str()) )?; writeln!( w, "{}", reformat_doc(&fix_param_names(doc, &self_name), env, in_type) )?; } } let out_parameters: Vec<_> = no_array_length_params .iter() .filter(|param| { param.direction == ParameterDirection::Out && !doc_ignored_parameters.contains(¶m.name) && !(param.name == "error" && param.c_type == "GError**") }) .collect(); if fn_.ret().doc.is_some() || !out_parameters.is_empty() { writeln!(w, "\n# Returns\n")?; } // document function's return if let Some(ref doc) = fn_.ret().doc { writeln!( w, "{}", reformat_doc(&fix_param_names(doc, &self_name), env, in_type) )?; } // document OUT parameters as part of the function's Return for parameter in out_parameters { if let Some(ref doc) = parameter.doc { writeln!( w, "\n## `{}`", nameutil::mangle_keywords(parameter.name.as_str()) )?; writeln!( w, "{}", reformat_doc(&fix_param_names(doc, &self_name), env, in_type), )?; } } Ok(()) }) } fn create_property_doc( w: &mut dyn Write, env: &Env, property: &Property, parent: Option>, in_type: (&TypeId, Option), getter_name: Option, setter_name: Option, obj_info: &analysis::object::Info, ) -> Result<()> { if env.is_totally_deprecated(Some(in_type.0.ns_id), property.deprecated_version) { return Ok(()); } let generate_doc = env .config .objects .get(&obj_info.type_id.full_name(&env.library)) .map_or(true, |r| r.generate_doc); if !generate_doc { return Ok(()); } if property.doc.is_none() && property.doc_deprecated.is_none() && (property.readable || property.writable) { return Ok(()); } let mut v = Vec::with_capacity(2); if let Some(getter_name) = getter_name { v.push(TypeStruct { parent: parent.clone(), ..TypeStruct::new(SType::Fn, &getter_name) }); } if let Some(setter_name) = setter_name { v.push(TypeStruct { parent, ..TypeStruct::new(SType::Fn, &setter_name) }); } for item in &v { write_item_doc(w, item, |w| { if let Some(ref doc) = property.doc { writeln!( w, "{}", reformat_doc(&fix_param_names(doc, &None), env, Some(in_type)) )?; } if let Some(ver) = property.deprecated_version { writeln!(w, "\n# Deprecated since {ver}\n")?; } else if property.doc_deprecated.is_some() { writeln!(w, "\n# Deprecated\n")?; } if let Some(ref doc) = property.doc_deprecated { writeln!( w, "{}", reformat_doc(&fix_param_names(doc, &None), env, Some(in_type)) )?; } Ok(()) })?; } Ok(()) } fn get_type_trait_for_implements(env: &Env, tid: TypeId) -> String { let trait_name = if let Some(&GObject { trait_name: Some(ref trait_name), .. }) = env.config.objects.get(&tid.full_name(&env.library)) { trait_name.clone() } else { format!("{}Ext", env.library.type_(tid).get_name()) }; if tid.ns_id == MAIN_NAMESPACE { format!("[`{trait_name}`][trait@crate::prelude::{trait_name}]") } else if let Some(symbol) = env.symbols.borrow().by_tid(tid) { let mut symbol = symbol.clone(); symbol.make_trait(&trait_name); format!("[`trait@{}`]", &symbol.full_rust_name()) } else { error!("Type {} doesn't have crate", tid.full_name(&env.library)); format!("`{trait_name}`") } } pub fn get_type_manual_traits_for_implements( env: &Env, info: &analysis::object::Info, ) -> Vec { let mut manual_trait_iters = Vec::new(); for type_id in [info.type_id] .iter() .chain(info.supertypes.iter().map(|stid| &stid.type_id)) { let full_name = type_id.full_name(&env.library); if let Some(obj) = &env.config.objects.get(&full_name) { if !obj.manual_traits.is_empty() { manual_trait_iters.push(obj.manual_traits.iter()); } } } manual_trait_iters .into_iter() .flatten() .map(|name| format!("[`{name}`][trait@crate::prelude::{name}]")) .collect() } pub fn document_type_properties( env: &Env, w: &mut dyn Write, info: &analysis::object::Info, properties: &[Property], subtype: Option<&str>, ) -> Result<()> { if let Some(subtype_name) = subtype { writeln!(w, "

{subtype_name}

")?; } for property in properties { let mut details = Vec::new(); if property.readable { details.push("Readable"); } if property.writable { details.push("Writeable"); } if property.construct { details.push("Construct"); } if property.construct_only { details.push("Construct Only"); } if let Some(doc) = &property.doc { writeln!( w, "\n\n#### `{}`\n {}\n\n{}", property.name, reformat_doc( &fix_param_names(doc, &None), env, Some((&info.type_id, None)) ), details.join(" | "), )?; } else { writeln!(w, "\n\n#### `{}`\n {}", property.name, details.join(" | "),)?; } } if subtype.is_some() { writeln!(w, "
")?; } Ok(()) } pub fn document_type_signals( env: &Env, w: &mut dyn Write, info: &analysis::object::Info, signals: &[Signal], subtype: Option<&str>, ) -> Result<()> { if let Some(subtype_name) = subtype { writeln!(w, "

{subtype_name}

")?; } for signal in signals { let mut details = Vec::new(); if signal.is_action { details.push("Action"); } if signal.is_detailed { details.push("Detailed"); } if let Some(doc) = &signal.doc { writeln!( w, "\n\n#### `{}`\n {}\n\n{}", signal.name, reformat_doc( &fix_param_names(doc, &None), env, Some((&info.type_id, None)) ), details.join(" | "), )?; } else { writeln!(w, "\n\n#### `{}`\n {}", signal.name, details.join(" | "),)?; } } if subtype.is_some() { writeln!(w, "
")?; } Ok(()) } gir-0.20.5/src/codegen/enums.rs000066400000000000000000000413421475434152100162610ustar00rootroot00000000000000use std::{ collections::HashSet, io::{prelude::*, Result}, path::Path, }; use super::{function, trait_impls}; use crate::{ analysis::enums::Info, codegen::{ general::{ self, allow_deprecated, cfg_condition, cfg_condition_no_doc, cfg_condition_string, cfg_deprecated, derives, doc_alias, version_condition, version_condition_no_doc, version_condition_string, }, generate_default_impl, }, config::gobjects::GObject, env::Env, file_saver, library::*, nameutil::{enum_member_name, use_glib_if_needed, use_glib_type}, traits::*, version::Version, }; pub fn generate(env: &Env, root_path: &Path, mod_rs: &mut Vec) { if !env .analysis .enumerations .iter() .any(|e| env.config.objects[&e.full_name].status.need_generate()) { return; } let path = root_path.join("enums.rs"); file_saver::save_to_file(path, env.config.make_backup, |w| { general::start_comments(w, &env.config)?; general::uses(w, env, &env.analysis.enum_imports, None)?; writeln!(w)?; mod_rs.push("\nmod enums;".into()); for enum_analysis in &env.analysis.enumerations { let config = &env.config.objects[&enum_analysis.full_name]; if !config.status.need_generate() { continue; } let enum_ = enum_analysis.type_(&env.library); if let Some(cfg) = version_condition_string(env, None, enum_.version, false, 0) { mod_rs.push(cfg); } if let Some(cfg) = cfg_condition_string(config.cfg_condition.as_ref(), false, 0) { mod_rs.push(cfg); } mod_rs.push(format!( "{}{} use self::enums::{};", enum_ .deprecated_version .map(|_| "#[allow(deprecated)]\n") .unwrap_or(""), enum_analysis.visibility.export_visibility(), enum_.name )); generate_enum(env, w, enum_, config, enum_analysis)?; } Ok(()) }); } fn generate_enum( env: &Env, w: &mut dyn Write, enum_: &Enumeration, config: &GObject, analysis: &Info, ) -> Result<()> { struct Member<'a> { name: String, c_name: String, version: Option, deprecated_version: Option, cfg_condition: Option<&'a String>, } let mut members: Vec> = Vec::new(); let mut vals: HashSet = HashSet::new(); let sys_crate_name = env.sys_crate_import(analysis.type_id); for member in &enum_.members { let member_config = config.members.matched(&member.name); if member.status.ignored() || vals.contains(&member.value) { continue; } vals.insert(member.value.clone()); let deprecated_version = member_config .iter() .find_map(|m| m.deprecated_version) .or(member.deprecated_version); let version = member_config .iter() .find_map(|m| m.version) .or(member.version); let cfg_condition = member_config.iter().find_map(|m| m.cfg_condition.as_ref()); members.push(Member { name: enum_member_name(&member.name), c_name: member.c_identifier.clone(), version, deprecated_version, cfg_condition, }); } cfg_deprecated( w, env, Some(analysis.type_id), enum_.deprecated_version, false, 0, )?; version_condition(w, env, None, enum_.version, false, 0)?; cfg_condition(w, config.cfg_condition.as_ref(), false, 0)?; if config.must_use { writeln!(w, "#[must_use]")?; } if let Some(ref d) = config.derives { derives(w, d, 1)?; } else { writeln!(w, "#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]")?; } writeln!(w, "#[derive(Clone, Copy)]")?; if config.exhaustive { writeln!(w, "#[repr(i32)]")?; } else { writeln!(w, "#[non_exhaustive]")?; } doc_alias(w, &enum_.c_type, "", 0)?; writeln!(w, "{} enum {} {{", analysis.visibility, enum_.name)?; for member in &members { cfg_deprecated( w, env, Some(analysis.type_id), member.deprecated_version, false, 1, )?; version_condition(w, env, None, member.version, false, 1)?; cfg_condition(w, member.cfg_condition.as_ref(), false, 1)?; // Don't generate a doc_alias if the C name is the same as the Rust one if member.c_name != member.name { doc_alias(w, &member.c_name, "", 1)?; } if config.exhaustive { writeln!( w, "\t{} = {}::{},", member.name, sys_crate_name, member.c_name )?; } else { writeln!(w, "\t{},", member.name)?; } } if !config.exhaustive { writeln!( w, "\ #[doc(hidden)] __Unknown(i32),", )?; } writeln!(w, "}}")?; let any_deprecated_version = enum_ .deprecated_version .or_else(|| members.iter().find_map(|m| m.deprecated_version)); let functions = analysis .functions .iter() .filter(|f| f.status.need_generate()) .collect::>(); if !functions.is_empty() { writeln!(w)?; version_condition(w, env, None, enum_.version, false, 0)?; cfg_condition_no_doc(w, config.cfg_condition.as_ref(), false, 0)?; allow_deprecated(w, enum_.deprecated_version, false, 0)?; write!(w, "impl {} {{", analysis.name)?; for func_analysis in functions { function::generate( w, env, Some(analysis.type_id), func_analysis, Some(&analysis.specials), enum_.version, false, false, 1, )?; } writeln!(w, "}}")?; } trait_impls::generate( w, env, &analysis.name, &analysis.functions, &analysis.specials, None, None, config.cfg_condition.as_deref(), )?; writeln!(w)?; // Only inline from_glib / into_glib implementations if there are not many enums members let maybe_inline = if members.len() <= 12 || config.exhaustive { "#[inline]\n" } else { "" }; // Generate IntoGlib trait implementation. version_condition(w, env, None, enum_.version, false, 0)?; cfg_condition_no_doc(w, config.cfg_condition.as_ref(), false, 0)?; allow_deprecated(w, any_deprecated_version, false, 0)?; writeln!( w, "#[doc(hidden)] impl IntoGlib for {name} {{ type GlibType = {sys_crate_name}::{ffi_name}; {maybe_inline}fn into_glib(self) -> {sys_crate_name}::{ffi_name} {{", sys_crate_name = sys_crate_name, name = enum_.name, ffi_name = enum_.c_type, maybe_inline = maybe_inline )?; if config.exhaustive { writeln!( w, "self as {sys_crate_name}::{ffi_name}", sys_crate_name = sys_crate_name, ffi_name = enum_.c_type, )?; } else { writeln!(w, "match self {{",)?; for member in &members { version_condition_no_doc(w, env, None, member.version, false, 3)?; cfg_condition_no_doc(w, member.cfg_condition.as_ref(), false, 3)?; writeln!( w, "\t\t\tSelf::{} => {}::{},", member.name, sys_crate_name, member.c_name )?; } writeln!(w, "\t\t\tSelf::__Unknown(value) => value,")?; writeln!( w, "\ }}" )?; } writeln!( w, "\ }} }} " )?; let assert = if env.config.generate_safety_asserts { "skip_assert_initialized!();\n\t\t" } else { "" }; // Generate FromGlib trait implementation. version_condition(w, env, None, enum_.version, false, 0)?; cfg_condition_no_doc(w, config.cfg_condition.as_ref(), false, 0)?; allow_deprecated(w, any_deprecated_version, false, 0)?; writeln!( w, "#[doc(hidden)] impl FromGlib<{sys_crate_name}::{ffi_name}> for {name} {{ {maybe_inline}unsafe fn from_glib(value: {sys_crate_name}::{ffi_name}) -> Self {{ {assert}", sys_crate_name = sys_crate_name, name = enum_.name, ffi_name = enum_.c_type, assert = assert, maybe_inline = maybe_inline )?; if config.exhaustive { let all_members = members .iter() .map(|m| format!("{}::{}", sys_crate_name, m.c_name)) .collect::>() .join(", "); writeln!(w, "debug_assert!([{all_members}].contains(&value));")?; writeln!(w, "std::mem::transmute(value)",)?; } else { writeln!(w, "match value {{")?; for member in &members { version_condition_no_doc(w, env, None, member.version, false, 3)?; cfg_condition_no_doc(w, member.cfg_condition.as_ref(), false, 3)?; writeln!( w, "\t\t\t{}::{} => Self::{},", sys_crate_name, member.c_name, member.name )?; } writeln!(w, "\t\t\tvalue => Self::__Unknown(value),")?; writeln!( w, "\ }}" )?; } writeln!( w, "\ }} }} " )?; // Generate ErrorDomain trait implementation. if let Some(ref domain) = enum_.error_domain { let has_failed_member = members.iter().any(|m| m.name == "Failed"); version_condition(w, env, None, enum_.version, false, 0)?; cfg_condition_no_doc(w, config.cfg_condition.as_ref(), false, 0)?; allow_deprecated(w, any_deprecated_version, false, 0)?; writeln!( w, "impl {glib_error_domain} for {name} {{ #[inline] fn domain() -> {glib_quark} {{ {assert}", name = enum_.name, glib_error_domain = use_glib_type(env, "error::ErrorDomain"), glib_quark = use_glib_type(env, "Quark"), assert = assert )?; match domain { ErrorDomain::Quark(quark) => { writeln!( w, " static QUARK: ::std::sync::OnceLock<{0}ffi::GQuark> = ::std::sync::OnceLock::new(); let quark = *QUARK.get_or_init(|| unsafe {{ {0}ffi::g_quark_from_static_string(b\"{1}\\0\".as_ptr() as *const _) }}); unsafe {{ from_glib(quark) }}", use_glib_if_needed(env, ""), quark, )?; } ErrorDomain::Function(f) => { writeln!(w, " unsafe {{ from_glib({sys_crate_name}::{f}()) }}")?; } } writeln!( w, " }} #[inline] fn code(self) -> i32 {{ self.into_glib() }} #[inline] #[allow(clippy::match_single_binding)] fn from(code: i32) -> Option {{ {assert}match unsafe {{ from_glib(code) }} {{" )?; if has_failed_member && !config.exhaustive { writeln!(w, "\t\t\tSelf::__Unknown(_) => Some(Self::Failed),")?; } writeln!(w, "\t\t\tvalue => Some(value),")?; writeln!( w, "\ }} }} }} " )?; } // Generate StaticType trait implementation. if let Some(ref get_type) = enum_.glib_get_type { let configured_functions = config.functions.matched("get_type"); let version = std::iter::once(enum_.version) .chain(configured_functions.iter().map(|f| f.version)) .max() .flatten(); version_condition(w, env, None, version, false, 0)?; cfg_condition_no_doc(w, config.cfg_condition.as_ref(), false, 0)?; allow_deprecated(w, enum_.deprecated_version, false, 0)?; writeln!( w, "impl StaticType for {name} {{ #[inline]", name = enum_.name, )?; doc_alias(w, get_type, "", 1)?; writeln!( w, " fn static_type() -> {glib_type} {{ unsafe {{ from_glib({sys_crate_name}::{get_type}()) }} }} }}", sys_crate_name = sys_crate_name, get_type = get_type, glib_type = use_glib_type(env, "Type") )?; writeln!(w)?; version_condition(w, env, None, version, false, 0)?; cfg_condition_no_doc(w, config.cfg_condition.as_ref(), false, 0)?; allow_deprecated(w, enum_.deprecated_version, false, 0)?; writeln!( w, "impl {has_param_spec} for {name} {{ type ParamSpec = {param_spec_enum}; type SetValue = Self; type BuilderFn = fn(&str, Self) -> {param_spec_builder}; fn param_spec_builder() -> Self::BuilderFn {{ Self::ParamSpec::builder_with_default }} }}", name = enum_.name, has_param_spec = use_glib_type(env, "HasParamSpec"), param_spec_enum = use_glib_type(env, "ParamSpecEnum"), param_spec_builder = use_glib_type(env, "ParamSpecEnumBuilder"), )?; writeln!(w)?; version_condition(w, env, None, version, false, 0)?; cfg_condition_no_doc(w, config.cfg_condition.as_ref(), false, 0)?; allow_deprecated(w, enum_.deprecated_version, false, 0)?; writeln!( w, "impl {valuetype} for {name} {{ type Type = Self; }}", name = enum_.name, valuetype = use_glib_type(env, "value::ValueType"), )?; writeln!(w)?; version_condition(w, env, None, version, false, 0)?; cfg_condition_no_doc(w, config.cfg_condition.as_ref(), false, 0)?; allow_deprecated(w, enum_.deprecated_version, false, 0)?; writeln!( w, "unsafe impl<'a> {from_value_type}<'a> for {name} {{ type Checker = {genericwrongvaluetypechecker}; #[inline] unsafe fn from_value(value: &'a {gvalue}) -> Self {{ {assert}from_glib({glib}(value.to_glib_none().0)) }} }}", name = enum_.name, glib = use_glib_type(env, "gobject_ffi::g_value_get_enum"), gvalue = use_glib_type(env, "Value"), genericwrongvaluetypechecker = use_glib_type(env, "value::GenericValueTypeChecker"), assert = assert, from_value_type = use_glib_type(env, "value::FromValue"), )?; writeln!(w)?; version_condition(w, env, None, version, false, 0)?; cfg_condition_no_doc(w, config.cfg_condition.as_ref(), false, 0)?; allow_deprecated(w, enum_.deprecated_version, false, 0)?; writeln!( w, "impl ToValue for {name} {{ #[inline] fn to_value(&self) -> {gvalue} {{ let mut value = {gvalue}::for_value_type::(); unsafe {{ {glib}(value.to_glib_none_mut().0, self.into_glib()); }} value }} #[inline] fn value_type(&self) -> {gtype} {{ Self::static_type() }} }}", name = enum_.name, glib = use_glib_type(env, "gobject_ffi::g_value_set_enum"), gvalue = use_glib_type(env, "Value"), gtype = use_glib_type(env, "Type"), )?; writeln!(w)?; version_condition(w, env, None, version, false, 0)?; cfg_condition_no_doc(w, config.cfg_condition.as_ref(), false, 0)?; allow_deprecated(w, enum_.deprecated_version, false, 0)?; writeln!( w, "impl From<{name}> for {gvalue} {{ #[inline] fn from(v: {name}) -> Self {{ {assert}ToValue::to_value(&v) }} }}", name = enum_.name, gvalue = use_glib_type(env, "Value"), assert = assert, )?; writeln!(w)?; } generate_default_impl( w, env, config, &enum_.name, enum_.version, enum_.members.iter(), |member| { let e_member = members.iter().find(|m| m.c_name == member.c_identifier)?; let member_config = config.members.matched(&member.name); let version = member_config .iter() .find_map(|m| m.version) .or(e_member.version); let cfg_condition = member_config.iter().find_map(|m| m.cfg_condition.as_ref()); Some((version, cfg_condition, e_member.name.as_str())) }, )?; Ok(()) } gir-0.20.5/src/codegen/flags.rs000066400000000000000000000271331475434152100162300ustar00rootroot00000000000000use std::{ io::{prelude::*, Result}, path::Path, }; use super::{function, general::allow_deprecated, trait_impls}; use crate::{ analysis::flags::Info, codegen::{ general::{ self, cfg_condition, cfg_condition_doc, cfg_condition_no_doc, cfg_condition_string, cfg_deprecated, derives, doc_alias, version_condition, version_condition_doc, version_condition_no_doc, version_condition_string, }, generate_default_impl, }, config::gobjects::GObject, env::Env, file_saver, library::*, nameutil::{bitfield_member_name, use_glib_type}, traits::*, }; pub fn generate(env: &Env, root_path: &Path, mod_rs: &mut Vec) { if !env .analysis .flags .iter() .any(|f| env.config.objects[&f.full_name].status.need_generate()) { return; } let path = root_path.join("flags.rs"); file_saver::save_to_file(path, env.config.make_backup, |w| { general::start_comments(w, &env.config)?; general::uses(w, env, &env.analysis.flags_imports, None)?; writeln!(w)?; mod_rs.push("\nmod flags;".into()); for flags_analysis in &env.analysis.flags { let config = &env.config.objects[&flags_analysis.full_name]; if !config.status.need_generate() { continue; } let flags = flags_analysis.type_(&env.library); if let Some(cfg) = version_condition_string(env, None, flags.version, false, 0) { mod_rs.push(cfg); } if let Some(cfg) = cfg_condition_string(config.cfg_condition.as_ref(), false, 0) { mod_rs.push(cfg); } mod_rs.push(format!( "{}{} use self::flags::{};", flags .deprecated_version .map(|_| "#[allow(deprecated)]\n") .unwrap_or(""), flags_analysis.visibility.export_visibility(), flags.name )); generate_flags(env, w, flags, config, flags_analysis)?; } Ok(()) }); } fn generate_flags( env: &Env, w: &mut dyn Write, flags: &Bitfield, config: &GObject, analysis: &Info, ) -> Result<()> { let sys_crate_name = env.sys_crate_import(analysis.type_id); cfg_condition_no_doc(w, config.cfg_condition.as_ref(), false, 0)?; version_condition_no_doc(w, env, None, flags.version, false, 0)?; writeln!(w, "bitflags! {{")?; cfg_condition_doc(w, config.cfg_condition.as_ref(), false, 1)?; version_condition_doc(w, env, flags.version, false, 1)?; cfg_deprecated( w, env, Some(analysis.type_id), flags.deprecated_version, false, 1, )?; if config.must_use { writeln!(w, " #[must_use]")?; } if let Some(ref d) = config.derives { derives(w, d, 1)?; } writeln!(w, " #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]")?; doc_alias(w, &flags.c_type, "", 1)?; writeln!( w, " {} struct {}: u32 {{", analysis.visibility, flags.name )?; for member in &flags.members { let member_config = config.members.matched(&member.name); if member.status.ignored() { continue; } let name = bitfield_member_name(&member.name); let deprecated_version = member_config .iter() .find_map(|m| m.deprecated_version) .or(member.deprecated_version); let version = member_config .iter() .find_map(|m| m.version) .or(member.version); let cfg_cond = member_config.iter().find_map(|m| m.cfg_condition.as_ref()); cfg_deprecated(w, env, Some(analysis.type_id), deprecated_version, false, 2)?; version_condition(w, env, None, version, false, 2)?; cfg_condition(w, cfg_cond, false, 2)?; if member.c_identifier != member.name { doc_alias(w, &member.c_identifier, "", 2)?; } writeln!( w, "\t\tconst {} = {}::{} as _;", name, sys_crate_name, member.c_identifier, )?; } writeln!( w, " }} }}" )?; let functions = analysis .functions .iter() .filter(|f| f.status.need_generate()) .collect::>(); if !functions.is_empty() { writeln!(w)?; version_condition(w, env, None, flags.version, false, 0)?; cfg_condition_no_doc(w, config.cfg_condition.as_ref(), false, 0)?; allow_deprecated(w, flags.deprecated_version, false, 0)?; write!(w, "impl {} {{", analysis.name)?; for func_analysis in functions { function::generate( w, env, Some(analysis.type_id), func_analysis, Some(&analysis.specials), flags.version, false, false, 1, )?; } writeln!(w, "}}")?; } trait_impls::generate( w, env, &analysis.name, &analysis.functions, &analysis.specials, None, None, config.cfg_condition.as_deref(), )?; writeln!(w)?; generate_default_impl( w, env, config, &flags.name, flags.version, flags.members.iter(), |member| { let member_config = config.members.matched(&member.name); if member.status.ignored() { return None; } let version = member_config .iter() .find_map(|m| m.version) .or(member.version); let cfg_cond = member_config.iter().find_map(|m| m.cfg_condition.as_ref()); Some((version, cfg_cond, bitfield_member_name(&member.name))) }, )?; version_condition(w, env, None, flags.version, false, 0)?; cfg_condition_no_doc(w, config.cfg_condition.as_ref(), false, 0)?; allow_deprecated(w, flags.deprecated_version, false, 0)?; writeln!( w, "#[doc(hidden)] impl IntoGlib for {name} {{ type GlibType = {sys_crate_name}::{ffi_name}; #[inline] fn into_glib(self) -> {sys_crate_name}::{ffi_name} {{ self.bits() }} }} ", sys_crate_name = sys_crate_name, name = flags.name, ffi_name = flags.c_type )?; let assert = if env.config.generate_safety_asserts { "skip_assert_initialized!();\n\t\t" } else { "" }; version_condition(w, env, None, flags.version, false, 0)?; cfg_condition_no_doc(w, config.cfg_condition.as_ref(), false, 0)?; allow_deprecated(w, flags.deprecated_version, false, 0)?; writeln!( w, "#[doc(hidden)] impl FromGlib<{sys_crate_name}::{ffi_name}> for {name} {{ #[inline] unsafe fn from_glib(value: {sys_crate_name}::{ffi_name}) -> Self {{ {assert}Self::from_bits_truncate(value) }} }} ", sys_crate_name = sys_crate_name, name = flags.name, ffi_name = flags.c_type, assert = assert )?; if let Some(ref get_type) = flags.glib_get_type { let configured_functions = config.functions.matched("get_type"); let version = std::iter::once(flags.version) .chain(configured_functions.iter().map(|f| f.version)) .max() .flatten(); version_condition(w, env, None, version, false, 0)?; cfg_condition_no_doc(w, config.cfg_condition.as_ref(), false, 0)?; allow_deprecated(w, flags.deprecated_version, false, 0)?; writeln!( w, "impl StaticType for {name} {{ #[inline]", name = flags.name, )?; doc_alias(w, get_type, "", 1)?; writeln!( w, " fn static_type() -> {glib_type} {{ unsafe {{ from_glib({sys_crate_name}::{get_type}()) }} }} }}", sys_crate_name = sys_crate_name, get_type = get_type, glib_type = use_glib_type(env, "Type") )?; writeln!(w)?; version_condition(w, env, None, version, false, 0)?; cfg_condition_no_doc(w, config.cfg_condition.as_ref(), false, 0)?; allow_deprecated(w, flags.deprecated_version, false, 0)?; writeln!( w, "impl {has_param_spec} for {name} {{ type ParamSpec = {param_spec_flags}; type SetValue = Self; type BuilderFn = fn(&str) -> {param_spec_builder}; fn param_spec_builder() -> Self::BuilderFn {{ Self::ParamSpec::builder }} }}", name = flags.name, has_param_spec = use_glib_type(env, "HasParamSpec"), param_spec_flags = use_glib_type(env, "ParamSpecFlags"), param_spec_builder = use_glib_type(env, "ParamSpecFlagsBuilder"), )?; writeln!(w)?; version_condition(w, env, None, version, false, 0)?; cfg_condition_no_doc(w, config.cfg_condition.as_ref(), false, 0)?; allow_deprecated(w, flags.deprecated_version, false, 0)?; writeln!( w, "impl {valuetype} for {name} {{ type Type = Self; }}", name = flags.name, valuetype = use_glib_type(env, "value::ValueType"), )?; writeln!(w)?; version_condition(w, env, None, version, false, 0)?; cfg_condition_no_doc(w, config.cfg_condition.as_ref(), false, 0)?; allow_deprecated(w, flags.deprecated_version, false, 0)?; writeln!( w, "unsafe impl<'a> {from_value_type}<'a> for {name} {{ type Checker = {genericwrongvaluetypechecker}; #[inline] unsafe fn from_value(value: &'a {gvalue}) -> Self {{ {assert}from_glib({glib}(value.to_glib_none().0)) }} }}", name = flags.name, glib = use_glib_type(env, "gobject_ffi::g_value_get_flags"), gvalue = use_glib_type(env, "Value"), genericwrongvaluetypechecker = use_glib_type(env, "value::GenericValueTypeChecker"), assert = assert, from_value_type = use_glib_type(env, "value::FromValue"), )?; writeln!(w)?; version_condition(w, env, None, version, false, 0)?; cfg_condition_no_doc(w, config.cfg_condition.as_ref(), false, 0)?; allow_deprecated(w, flags.deprecated_version, false, 0)?; writeln!( w, "impl ToValue for {name} {{ #[inline] fn to_value(&self) -> {gvalue} {{ let mut value = {gvalue}::for_value_type::(); unsafe {{ {glib}(value.to_glib_none_mut().0, self.into_glib()); }} value }} #[inline] fn value_type(&self) -> {gtype} {{ Self::static_type() }} }}", name = flags.name, glib = use_glib_type(env, "gobject_ffi::g_value_set_flags"), gvalue = use_glib_type(env, "Value"), gtype = use_glib_type(env, "Type"), )?; writeln!(w)?; version_condition(w, env, None, version, false, 0)?; cfg_condition_no_doc(w, config.cfg_condition.as_ref(), false, 0)?; allow_deprecated(w, flags.deprecated_version, false, 0)?; writeln!( w, "impl From<{name}> for {gvalue} {{ #[inline] fn from(v: {name}) -> Self {{ {assert}ToValue::to_value(&v) }} }}", name = flags.name, gvalue = use_glib_type(env, "Value"), assert = assert, )?; writeln!(w)?; } Ok(()) } gir-0.20.5/src/codegen/function.rs000066400000000000000000000367151475434152100167670ustar00rootroot00000000000000use std::{ fmt, io::{Result, Write}, result::Result as StdResult, }; use log::warn; use super::{ function_body_chunk, general::{ allow_deprecated, cfg_condition, cfg_deprecated, doc_alias, doc_hidden, not_version_condition, version_condition, }, parameter::ToParameter, return_value::{out_parameter_types, out_parameters_as_return, ToReturnValue}, special_functions, }; use crate::{ analysis::{self, bounds::Bounds, try_from_glib::TryFromGlib}, chunk::{ffi_function_todo, Chunk}, env::Env, library::{self, TypeId}, nameutil::use_glib_type, version::Version, writer::{primitives::tabs, ToCode}, }; // We follow the rules of the `return_self_not_must_use` clippy lint: // // If `Self` is returned (so `-> Self`) in a method (whatever the form of the // `self`), then the `#[must_use]` attribute must be added. pub fn get_must_use_if_needed( parent_type_id: Option, analysis: &analysis::functions::Info, comment_prefix: &str, ) -> Option { // If there is no parent, it means it's not a (trait) method so we're not // interested. if let Some(parent_type_id) = parent_type_id { // Check it's a trait declaration or a method declaration (outside of a trait // implementation). if analysis.kind == library::FunctionKind::Method { // We now get the list of the returned types. let outs = out_parameter_types(analysis); // If there is only one type returned, we check if it's the same type as `self` // (stored in `parent_type_id`). if [parent_type_id] == *outs.as_slice() { return Some(format!("{comment_prefix}#[must_use]\n")); } } } None } pub fn generate( w: &mut dyn Write, env: &Env, parent_type_id: Option, analysis: &analysis::functions::Info, special_functions: Option<&analysis::special_functions::Infos>, scope_version: Option, in_trait: bool, only_declaration: bool, indent: usize, ) -> Result<()> { if !analysis.status.need_generate() { return Ok(()); } if analysis.is_async_finish(env) { return Ok(()); } if let Some(special_functions) = special_functions { if special_functions::generate(w, env, analysis, special_functions, scope_version)? { return Ok(()); } } if analysis.hidden { return Ok(()); } let commented = analysis.commented; let comment_prefix = if commented { "//" } else { "" }; let pub_prefix = if in_trait { String::new() } else { format!("{} ", analysis.visibility) }; let unsafe_ = if analysis.unsafe_ { "unsafe " } else { "" }; let declaration = declaration(env, analysis); let suffix = if only_declaration { ";" } else { " {" }; writeln!(w)?; cfg_deprecated(w, env, None, analysis.deprecated_version, commented, indent)?; cfg_condition(w, analysis.cfg_condition.as_ref(), commented, indent)?; let version = Version::if_stricter_than(analysis.version, scope_version); version_condition(w, env, None, version, commented, indent)?; not_version_condition(w, analysis.not_version, commented, indent)?; doc_hidden(w, analysis.doc_hidden, comment_prefix, indent)?; allow_deprecated(w, analysis.deprecated_version, commented, indent)?; doc_alias(w, &analysis.glib_name, comment_prefix, indent)?; if analysis.codegen_name() != analysis.func_name { doc_alias(w, &analysis.func_name, comment_prefix, indent)?; } // TODO Warn the user if both get_property and set_property are set as it is // an error on the gir data. if let Some(get_property) = &analysis.get_property { if get_property != analysis.codegen_name() { doc_alias(w, get_property, comment_prefix, indent)?; } } else if let Some(set_property) = &analysis.set_property { if set_property != analysis.codegen_name() { doc_alias(w, set_property, comment_prefix, indent)?; } } // Don't add a guard for public or copy/equal functions let dead_code_cfg = if !analysis.visibility.is_public() && !analysis.is_special() { "#[allow(dead_code)]" } else { "" }; let allow_should_implement_trait = if analysis.codegen_name() == "default" { format!( "{}{}#[allow(clippy::should_implement_trait)]", tabs(indent), comment_prefix ) } else { String::new() }; writeln!( w, "{}{}{}{}{}{}{}{}{}", allow_should_implement_trait, dead_code_cfg, get_must_use_if_needed(parent_type_id, analysis, comment_prefix).unwrap_or_default(), tabs(indent), comment_prefix, pub_prefix, unsafe_, declaration, suffix, )?; if !only_declaration { let body = body_chunk(env, analysis, parent_type_id).to_code(env); for s in body { writeln!(w, "{}{}", tabs(indent), s)?; } } if analysis.async_future.is_some() { let declaration = declaration_futures(env, analysis); let suffix = if only_declaration { ";" } else { " {" }; writeln!(w)?; cfg_deprecated(w, env, None, analysis.deprecated_version, commented, indent)?; writeln!(w, "{}{}", tabs(indent), comment_prefix)?; cfg_condition(w, analysis.cfg_condition.as_ref(), commented, indent)?; version_condition(w, env, None, version, commented, indent)?; not_version_condition(w, analysis.not_version, commented, indent)?; doc_hidden(w, analysis.doc_hidden, comment_prefix, indent)?; writeln!( w, "{}{}{}{}{}{}", tabs(indent), comment_prefix, pub_prefix, unsafe_, declaration, suffix )?; if !only_declaration { let body = body_chunk_futures(env, analysis).unwrap(); for s in body.lines() { if !s.is_empty() { writeln!(w, "{}{}{}", tabs(indent + 1), comment_prefix, s)?; } else { writeln!(w)?; } } writeln!(w, "{}{}}}", tabs(indent), comment_prefix)?; } } Ok(()) } pub fn declaration(env: &Env, analysis: &analysis::functions::Info) -> String { let outs_as_return = !analysis.outs.is_empty(); let return_str = if outs_as_return { out_parameters_as_return(env, analysis) } else if analysis.ret.bool_return_is_error.is_some() { format!(" -> Result<(), {}>", use_glib_type(env, "error::BoolError")) } else if let Some(return_type) = analysis.ret.to_return_value( env, analysis .ret .parameter .as_ref() .map_or(&TryFromGlib::Default, |par| &par.try_from_glib), false, ) { format!(" -> {return_type}") } else { String::new() }; let mut param_str = String::with_capacity(100); let (bounds, _) = bounds(&analysis.bounds, &[], false, false); for par in &analysis.parameters.rust_parameters { if !param_str.is_empty() { param_str.push_str(", "); } let c_par = &analysis.parameters.c_parameters[par.ind_c]; let s = c_par.to_parameter(env, &analysis.bounds, false); param_str.push_str(&s); } format!( "fn {}{}({}){}", analysis.codegen_name(), bounds, param_str, return_str, ) } pub fn declaration_futures(env: &Env, analysis: &analysis::functions::Info) -> String { let async_future = analysis.async_future.as_ref().unwrap(); let return_str = if let Some(ref error_parameters) = async_future.error_parameters { format!( " -> Pin> + 'static>>", async_future.success_parameters, error_parameters ) } else { format!( " -> Pin + 'static>>", async_future.success_parameters ) }; let mut param_str = String::with_capacity(100); let mut skipped = 0; let mut skipped_bounds = vec![]; for (pos, par) in analysis.parameters.rust_parameters.iter().enumerate() { let c_par = &analysis.parameters.c_parameters[par.ind_c]; if c_par.name == "callback" || c_par.name == "cancellable" { skipped += 1; if let Some(alias) = analysis .bounds .get_parameter_bound(&c_par.name) .and_then(|bound| bound.type_parameter_reference()) { skipped_bounds.push(alias); } continue; } if pos - skipped > 0 { param_str.push_str(", "); } let s = c_par.to_parameter(env, &analysis.bounds, true); param_str.push_str(&s); } let (bounds, _) = bounds(&analysis.bounds, skipped_bounds.as_ref(), true, false); format!( "fn {}{}({}){}", async_future.name, bounds, param_str, return_str, ) } pub fn bounds( bounds: &Bounds, skip: &[char], r#async: bool, filter_callback_modified: bool, ) -> (String, Vec) { use crate::analysis::bounds::BoundType::*; if bounds.is_empty() { return (String::new(), Vec::new()); } let skip_lifetimes = bounds .iter() // TODO: False or true? .filter(|bound| bound.alias.is_some_and(|alias| skip.contains(&alias))) .filter_map(|bound| match bound.bound_type { IsA(lifetime) | AsRef(lifetime) => lifetime, _ => None, }) .collect::>(); let lifetimes = bounds .iter_lifetimes() .filter(|s| !skip_lifetimes.contains(s)) .map(|s| format!("'{s}")) .collect::>(); let bounds = bounds.iter().filter(|bound| { bound.alias.map_or(true, |alias| !skip.contains(&alias)) && (!filter_callback_modified || !bound.callback_modified) }); let type_names = lifetimes .iter() .cloned() .chain( bounds .clone() .filter_map(|b| b.type_parameter_definition(r#async)), ) .collect::>(); let type_names = if type_names.is_empty() { String::new() } else { format!("<{}>", type_names.join(", ")) }; let bounds = lifetimes .into_iter() // TODO: enforce that this is only used on NoWrapper! // TODO: Analyze if alias is used in function, otherwise set to None! .chain(bounds.filter_map(|b| b.alias).map(|a| a.to_string())) .collect::>(); (type_names, bounds) } pub fn body_chunk( env: &Env, analysis: &analysis::functions::Info, parent_type_id: Option, ) -> Chunk { if analysis.commented { return ffi_function_todo(env, &analysis.glib_name); } let outs_as_return = !analysis.outs.is_empty(); let mut builder = function_body_chunk::Builder::new(); let sys_crate_name = if let Some(ty_id) = parent_type_id { env.sys_crate_import(ty_id) } else { env.main_sys_crate_name().to_owned() }; builder .glib_name(&format!("{}::{}", sys_crate_name, analysis.glib_name)) .assertion(analysis.assertion) .ret(&analysis.ret) .transformations(&analysis.parameters.transformations) .in_unsafe(analysis.unsafe_) .outs_mode(analysis.outs.mode); if analysis.r#async { if let Some(ref trampoline) = analysis.trampoline { builder.async_trampoline(trampoline); } else { warn!( "Async function {} has no associated _finish function", analysis.codegen_name(), ); } } else { for trampoline in &analysis.callbacks { builder.callback(trampoline); } for trampoline in &analysis.destroys { builder.destroy(trampoline); } } for par in &analysis.parameters.c_parameters { if outs_as_return && analysis.outs.iter().any(|out| out.lib_par.name == par.name) { builder.out_parameter(env, par); } else { builder.parameter(); } } let (bounds, bounds_names) = bounds(&analysis.bounds, &[], false, true); builder.generate(env, &bounds, &bounds_names.join(", ")) } pub fn body_chunk_futures( env: &Env, analysis: &analysis::functions::Info, ) -> StdResult { use std::fmt::Write; use crate::analysis::ref_mode::RefMode; let async_future = analysis.async_future.as_ref().unwrap(); let mut body = String::new(); let gio_future_name = if env.config.library_name != "Gio" { "gio::GioFuture" } else { "crate::GioFuture" }; writeln!(body)?; if !async_future.assertion.is_none() { writeln!(body, "{}", async_future.assertion)?; } let skip = usize::from(async_future.is_method); // Skip the instance parameter for par in analysis.parameters.rust_parameters.iter().skip(skip) { if par.name == "cancellable" || par.name == "callback" { continue; } let c_par = &analysis.parameters.c_parameters[par.ind_c]; let type_ = env.type_(par.typ); let is_str = matches!(type_, library::Type::Basic(library::Basic::Utf8)); let is_slice = matches!(type_, library::Type::CArray(_)); if is_slice { writeln!(body, "let {} = {}.to_vec();", par.name, par.name)?; } else if *c_par.nullable { writeln!( body, "let {} = {}.map(ToOwned::to_owned);", par.name, par.name )?; } else if is_str { writeln!(body, "let {} = String::from({});", par.name, par.name)?; } else if c_par.ref_mode != RefMode::None { writeln!(body, "let {} = {}.clone();", par.name, par.name)?; } } if async_future.is_method { writeln!( body, "Box_::pin({gio_future_name}::new(self, move |obj, cancellable, send| {{" )?; } else { writeln!( body, "Box_::pin({gio_future_name}::new(&(), move |_obj, cancellable, send| {{" )?; } if async_future.is_method { writeln!(body, "\tobj.{}(", analysis.codegen_name())?; } else if analysis.type_name.is_ok() { writeln!(body, "\tSelf::{}(", analysis.codegen_name())?; } else { writeln!(body, "\t{}(", analysis.codegen_name())?; } // Skip the instance parameter for par in analysis.parameters.rust_parameters.iter().skip(skip) { if par.name == "cancellable" { writeln!(body, "\t\tSome(cancellable),")?; } else if par.name == "callback" { continue; } else { let c_par = &analysis.parameters.c_parameters[par.ind_c]; if *c_par.nullable { writeln!( body, "\t\t{}.as_ref().map(::std::borrow::Borrow::borrow),", par.name )?; } else if c_par.ref_mode != RefMode::None { writeln!(body, "\t\t&{},", par.name)?; } else { writeln!(body, "\t\t{},", par.name)?; } } } writeln!(body, "\t\tmove |res| {{")?; writeln!(body, "\t\t\tsend.resolve(res);")?; writeln!(body, "\t\t}},")?; writeln!(body, "\t);")?; writeln!(body, "}}))")?; Ok(body) } gir-0.20.5/src/codegen/function_body_chunk.rs000066400000000000000000001443421475434152100211700ustar00rootroot00000000000000use std::collections::{hash_map::Entry, BTreeMap, HashMap}; use crate::{ analysis::{ self, conversion_type::ConversionType, function_parameters::{ CParameter as AnalysisCParameter, Transformation, TransformationType, }, functions::{find_index_to_ignore, AsyncTrampoline}, out_parameters::{Mode, ThrowFunctionReturnStrategy}, return_value, rust_type::RustType, safety_assertion_mode::SafetyAssertionMode, trampoline_parameters, trampolines::Trampoline, }, chunk::{parameter_ffi_call_out, Chunk, Param, TupleMode}, env::Env, library::{self, ParameterDirection, TypeId}, nameutil::{is_gstring, use_gio_type, use_glib_if_needed, use_glib_type}, traits::*, }; #[derive(Clone, Debug)] enum Parameter { // Used to separate in and out parameters in `add_in_array_lengths` // and `generate_func_parameters` In, Out { parameter: parameter_ffi_call_out::Parameter, mem_mode: OutMemMode, }, } use self::Parameter::*; #[derive(Clone, Debug, Eq, PartialEq)] enum OutMemMode { Uninitialized, UninitializedNamed(String), NullPtr, NullMutPtr, } impl OutMemMode { fn is_uninitialized(&self) -> bool { matches!(*self, Self::Uninitialized) } } #[derive(Clone, Default)] struct ReturnValue { pub ret: return_value::Info, } #[derive(Default)] pub struct Builder { async_trampoline: Option, callbacks: Vec, destroys: Vec, glib_name: String, parameters: Vec, transformations: Vec, ret: ReturnValue, outs_as_return: bool, in_unsafe: bool, outs_mode: Mode, assertion: SafetyAssertionMode, } // Key: user data index // Value: (global position used as id, type, callbacks) type FuncParameters<'a> = BTreeMap>; struct FuncParameter<'a> { pos: usize, full_type: Option<(String, String)>, callbacks: Vec<&'a Trampoline>, } impl Builder { pub fn new() -> Self { Default::default() } pub fn async_trampoline(&mut self, trampoline: &AsyncTrampoline) -> &mut Self { self.async_trampoline = Some(trampoline.clone()); self } pub fn callback(&mut self, trampoline: &Trampoline) -> &mut Self { self.callbacks.push(trampoline.clone()); self } pub fn destroy(&mut self, trampoline: &Trampoline) -> &mut Self { self.destroys.push(trampoline.clone()); self } pub fn glib_name(&mut self, name: &str) -> &mut Self { self.glib_name = name.into(); self } pub fn assertion(&mut self, assertion: SafetyAssertionMode) -> &mut Self { self.assertion = assertion; self } pub fn ret(&mut self, ret: &return_value::Info) -> &mut Self { self.ret = ReturnValue { ret: ret.clone() }; self } pub fn parameter(&mut self) -> &mut Self { self.parameters.push(Parameter::In); self } pub fn out_parameter(&mut self, env: &Env, parameter: &AnalysisCParameter) -> &mut Self { let mem_mode = c_type_mem_mode(env, parameter); self.parameters.push(Parameter::Out { parameter: parameter_ffi_call_out::Parameter::new( parameter, mem_mode.is_uninitialized(), ), mem_mode, }); self.outs_as_return = true; self } pub fn transformations(&mut self, transformations: &[Transformation]) -> &mut Self { self.transformations = transformations.to_owned(); self } pub fn outs_mode(&mut self, mode: Mode) -> &mut Self { self.outs_mode = mode; self } pub fn in_unsafe(&mut self, in_unsafe: bool) -> &mut Self { self.in_unsafe = in_unsafe; self } pub fn generate(&self, env: &Env, bounds: &str, bounds_names: &str) -> Chunk { let mut body = Vec::new(); let mut uninitialized_vars = if self.outs_as_return { self.write_out_variables(&mut body, env) } else { Vec::new() }; let mut group_by_user_data = FuncParameters::new(); // We group arguments by callbacks. if !self.callbacks.is_empty() || !self.destroys.is_empty() { for (pos, callback) in self.callbacks.iter().enumerate() { let user_data_index = callback.user_data_index; if group_by_user_data.contains_key(&user_data_index) { continue; } let calls = self .callbacks .iter() .filter(|c| c.user_data_index == user_data_index) .collect::>(); group_by_user_data.insert( user_data_index, FuncParameter { pos, full_type: if calls.len() > 1 { if calls.iter().all(|c| c.scope.is_call()) { Some(( format!( "&({})", calls .iter() .map(|c| format!("&{}", c.bound_name)) .collect::>() .join(", ") ), format!( "&mut ({})", calls .iter() .map(|c| format!("&mut {}", c.bound_name)) .collect::>() .join(", ") ), )) } else { let s = format!( "Box_<({})>", calls .iter() .map(|c| c.bound_name.to_string()) .collect::>() .join(", ") ); Some((s.clone(), s)) } } else { None }, callbacks: calls, }, ); } } let call = self.generate_call(&group_by_user_data); let call = self.generate_call_conversion(call, &mut uninitialized_vars); let ret = self.generate_out_return(&mut uninitialized_vars); let (call, ret) = self.apply_outs_mode(call, ret, &mut uninitialized_vars); body.push(call); if let Some(chunk) = ret { body.push(chunk); } let mut chunks = Vec::new(); self.add_in_array_lengths(&mut chunks); self.add_assertion(&mut chunks); if !self.callbacks.is_empty() || !self.destroys.is_empty() { // Key: user data index // Value: the current pos in the tuple for the given argument. let mut poses = HashMap::with_capacity(group_by_user_data.len()); for trampoline in &self.callbacks { *poses .entry(&trampoline.user_data_index) .or_insert_with(|| 0) += 1; } let mut poses = poses .into_iter() .filter(|(_, x)| *x > 1) .map(|(x, _)| (x, 0)) .collect::>(); for trampoline in &self.callbacks { let user_data_index = trampoline.user_data_index; let pos = poses.entry(&trampoline.user_data_index); self.add_trampoline( env, &mut chunks, trampoline, &group_by_user_data[&user_data_index].full_type, match pos { Entry::Occupied(ref x) => Some(*x.get()), _ => None, }, bounds, bounds_names, false, ); pos.and_modify(|x| { *x += 1; }); } for destroy in &self.destroys { self.add_trampoline( env, &mut chunks, destroy, &group_by_user_data[&destroy.user_data_index].full_type, None, // doesn't matter for destroy bounds, bounds_names, true, ); } for FuncParameter { pos, full_type, callbacks: calls, } in group_by_user_data.values() { if calls.len() > 1 { chunks.push(Chunk::Let { name: format!("super_callback{pos}"), is_mut: false, value: Box::new(Chunk::Custom(if poses.is_empty() { format!( "Box_::new(Box_::new(({})))", calls .iter() .map(|c| format!("{}_data", c.name)) .collect::>() .join(", ") ) } else if calls.iter().all(|c| c.scope.is_call()) { format!( "&mut ({})", calls .iter() .map(|c| format!("{}_data", c.name)) .collect::>() .join(", ") ) } else { format!( "Box_::new(({}))", calls .iter() .map(|c| format!("{}_data", c.name)) .collect::>() .join(", ") ) })), type_: Some(Box::new(Chunk::Custom( full_type.clone().map(|x| x.0).unwrap(), ))), }); } else if !calls.is_empty() { chunks.push(Chunk::Let { name: format!("super_callback{pos}"), is_mut: false, value: Box::new(Chunk::Custom(format!( "{}{}_data", if calls[0].scope.is_call() { "&mut " } else { "" }, calls[0].name ))), type_: Some(Box::new(Chunk::Custom(if calls[0].scope.is_call() { format!("&mut {}", calls[0].bound_name) } else { format!("Box_<{}>", calls[0].bound_name) }))), }); } } } else if let Some(ref trampoline) = self.async_trampoline { self.add_async_trampoline(env, &mut chunks, trampoline); } chunks.push(if self.in_unsafe { Chunk::Chunks(body) } else { Chunk::Unsafe(body) }); Chunk::BlockHalf(chunks) } fn remove_extra_assume_init( &self, array_length_name: &Option, uninitialized_vars: &mut Vec<(String, bool)>, ) { // To prevent to call twice `.assume_init()` on the length variable, we need to // remove them from the `uninitialized_vars` array. if let Some(array_length_name) = array_length_name { uninitialized_vars.retain(|(x, _)| x != array_length_name); } } fn generate_initialized_value( &self, name: &str, uninitialized_vars: &[(String, bool)], ) -> Chunk { if let Some(need_from_glib) = self.is_uninitialized_var(name, uninitialized_vars) { Chunk::Custom(format!( "{}{}.assume_init(){}", if need_from_glib { "from_glib(" } else { "" }, name, if need_from_glib { ")" } else { "" }, )) } else { Chunk::Custom(name.to_string()) } } fn is_uninitialized_var( &self, name: &str, uninitialized_vars: &[(String, bool)], ) -> Option { uninitialized_vars .iter() .find(|(n, _)| n.eq(name)) .map(|(_, need_from_glib)| *need_from_glib) } fn add_trampoline( &self, env: &Env, chunks: &mut Vec, trampoline: &Trampoline, full_type: &Option<(String, String)>, pos: Option, bounds: &str, bounds_names: &str, is_destroy: bool, ) { if !is_destroy { if full_type.is_none() { if trampoline.scope.is_call() { chunks.push(Chunk::Custom(format!( "let mut {0}_data: {1} = {0};", trampoline.name, trampoline.bound_name ))); } else { chunks.push(Chunk::Custom(format!( "let {0}_data: Box_<{1}> = Box_::new({0});", trampoline.name, trampoline.bound_name ))); } } else if trampoline.scope.is_call() { chunks.push(Chunk::Custom(format!( "let mut {0}_data: &mut {1} = &mut {0};", trampoline.name, trampoline.bound_name ))); } else { chunks.push(Chunk::Custom(format!( "let {0}_data: {1} = {0};", trampoline.name, trampoline.bound_name ))); } } let mut body = Vec::new(); let mut arguments = Vec::new(); for par in &trampoline.parameters.transformations { if par.name == "this" || trampoline.parameters.c_parameters[par.ind_c].is_real_gpointer(env) { continue; } let ty_name = match RustType::try_new(env, par.typ) { Ok(x) => x.into_string(), _ => String::new(), }; let nullable = trampoline.parameters.rust_parameters[par.ind_rust].nullable; let is_basic = add_chunk_for_type(env, par.typ, par, &mut body, &ty_name, nullable); if is_gstring(&ty_name) { if *nullable { arguments.push(Chunk::Name(format!( "(*{}).as_ref().map(|s| s.as_str())", par.name ))); } else { arguments.push(Chunk::Name(format!("{}.as_str()", par.name))); } continue; } if *nullable && !is_basic { arguments.push(Chunk::Name(format!("{}.as_ref().as_ref()", par.name))); continue; } arguments.push(Chunk::Name(format!( "{}{}", if is_basic { "" } else { "&" }, par.name ))); } let func = trampoline .parameters .c_parameters .last() .map_or_else(|| "Unknown".to_owned(), |p| p.name.clone()); if let Some(full_type) = full_type { if is_destroy || trampoline.scope.is_async() { body.push(Chunk::Let { name: format!("{}callback", if is_destroy { "_" } else { "" }), is_mut: false, value: Box::new(Chunk::Custom(format!("Box_::from_raw({func} as *mut _)"))), type_: Some(Box::new(Chunk::Custom(full_type.1.clone()))), }); } else { body.push(Chunk::Let { name: "callback".to_owned(), is_mut: false, value: Box::new(Chunk::Custom(format!( "{}*({} as *mut _)", if !trampoline.scope.is_call() { "&" } else if pos.is_some() { "&mut " } else { "" }, func ))), type_: Some(Box::new(Chunk::Custom( if !trampoline.scope.is_async() && !trampoline.scope.is_call() { format!( "&{}", full_type .1 .strip_prefix("Box_<") .unwrap() .strip_suffix(">") .unwrap() ) } else { full_type.1.clone() }, ))), }); if trampoline.scope.is_async() { body.push(Chunk::Custom(format!( "let callback = callback{}{};", if let Some(pos) = pos { format!(".{pos}") } else { String::new() }, if *trampoline.nullable { ".expect(\"cannot get closure...\")" } else { "" } ))); } else if !trampoline.scope.is_call() { if *trampoline.nullable { body.push(Chunk::Custom(format!( "if let Some(ref callback) = callback{} {{", if let Some(pos) = pos { format!(".{pos}") } else { String::new() } ))); } else { body.push(Chunk::Custom(format!( "let callback = &callback{};", if let Some(pos) = pos { format!(".{pos}") } else { String::new() } ))); } } else if !trampoline.scope.is_async() && *trampoline.nullable { body.push(Chunk::Custom(format!( "if let Some(ref {}callback) = {} {{", if trampoline.scope.is_call() { "mut " } else { "" }, if let Some(pos) = pos { format!("(*callback).{pos}") } else { "*callback".to_owned() } ))); } } } else { body.push(Chunk::Let { name: format!("{}callback", if is_destroy { "_" } else { "" }), is_mut: false, value: Box::new(Chunk::Custom( if is_destroy || trampoline.scope.is_async() { format!("Box_::from_raw({} as *mut {})", func, trampoline.bound_name) } else if trampoline.scope.is_call() { format!("{} as *mut {}", func, trampoline.bound_name) } else { format!("&*({} as *mut {})", func, trampoline.bound_name) }, )), type_: None, }); if !is_destroy && *trampoline.nullable { if trampoline.scope.is_async() { body.push(Chunk::Custom( "let callback = (*callback).expect(\"cannot get closure...\");".to_owned(), )); } else { body.push(Chunk::Custom(format!( "if let Some(ref {}callback) = {} {{", if trampoline.scope.is_call() { "mut " } else { "" }, if let Some(pos) = pos { format!("(*callback).{pos}") } else { "*callback".to_owned() } ))); } } } if !is_destroy { use crate::writer::to_code::ToCode; body.push(Chunk::Custom(format!( "{}({})", if !*trampoline.nullable { "(*callback)" } else if trampoline.scope.is_async() { "callback" } else { "\tcallback" }, arguments .iter() .flat_map(|arg| arg.to_code(env)) .collect::>() .join(", "), ))); if !trampoline.scope.is_async() && *trampoline.nullable { body.push(Chunk::Custom("} else {".to_owned())); body.push(Chunk::Custom( "\tpanic!(\"cannot get closure...\")".to_owned(), )); body.push(Chunk::Custom("}".to_owned())); } if trampoline.ret.c_type != "void" { use crate::codegen::trampoline_to_glib::TrampolineToGlib; body.push(Chunk::Custom(trampoline.ret.trampoline_to_glib(env))); } } let extern_func = Chunk::ExternCFunc { name: format!("{}_func", trampoline.name), parameters: trampoline .parameters .c_parameters .iter() .skip(1) // to skip the generated this .map(|p| { if p.is_real_gpointer(env) { Param { name: p.name.clone(), typ: use_glib_if_needed(env, "ffi::gpointer"), } } else { Param { name: p.name.clone(), typ: crate::analysis::ffi_type::ffi_type(env, p.typ, &p.c_type) .expect("failed to write c_type") .into_string(), } } }) .collect::>(), body: Box::new(Chunk::Chunks(body)), return_value: if trampoline.ret.c_type != "void" { let p = &trampoline.ret; Some( crate::analysis::ffi_type::ffi_type(env, p.typ, &p.c_type) .expect("failed to write c_type") .into_string(), ) } else { None }, bounds: bounds.to_owned(), }; chunks.push(extern_func); let bounds_str = if bounds_names.is_empty() { String::new() } else { format!("::<{bounds_names}>") }; if !is_destroy { if *trampoline.nullable { chunks.push(Chunk::Custom(format!( "let {0} = if {0}_data.is_some() {{ Some({0}_func{1} as _) }} else {{ None }};", trampoline.name, bounds_str ))); } else { chunks.push(Chunk::Custom(format!( "let {0} = Some({0}_func{1} as _);", trampoline.name, bounds_str ))); } } else { chunks.push(Chunk::Custom(format!( "let destroy_call{} = Some({}_func{} as _);", trampoline.destroy_index, trampoline.name, bounds_str ))); } } fn add_async_trampoline( &self, env: &Env, chunks: &mut Vec, trampoline: &AsyncTrampoline, ) { chunks.push(Chunk::Custom(String::from( r#" let main_context = glib::MainContext::ref_thread_default(); let is_main_context_owner = main_context.is_owner(); let has_acquired_main_context = (!is_main_context_owner) .then(|| main_context.acquire().ok()) .flatten(); assert!( is_main_context_owner || has_acquired_main_context.is_some(), "Async operations only allowed if the thread is owning the MainContext" ); "#, ))); chunks.push(Chunk::Let { name: "user_data".to_string(), is_mut: false, value: Box::new(Chunk::Custom(format!( "Box_::new({}::new(callback))", use_glib_type(env, "thread_guard::ThreadGuard") ))), type_: Some(Box::new(Chunk::Custom(format!( "Box_<{}<{}>>", use_glib_type(env, "thread_guard::ThreadGuard"), trampoline.bound_name )))), }); let mut finish_args = vec![]; let mut uninitialized_vars = Vec::new(); if trampoline.is_method { finish_args.push(Chunk::Cast { name: "_source_object".to_string(), type_: "*mut _".to_string(), }); } let mut found_async_result = false; finish_args.extend( trampoline .output_params .iter() .filter(|out| { out.lib_par.direction == ParameterDirection::Out || out.lib_par.typ.full_name(&env.library) == "Gio.AsyncResult" }) .map(|out| { if out.lib_par.typ.full_name(&env.library) == "Gio.AsyncResult" { found_async_result = true; return Chunk::Name("res".to_string()); } let kind = type_mem_mode(env, &out.lib_par); let mut par: parameter_ffi_call_out::Parameter = out.into(); if kind.is_uninitialized() { par.is_uninitialized = true; uninitialized_vars.push(( out.lib_par.name.clone(), self.check_if_need_glib_conversion(env, out.lib_par.typ), )); } Chunk::FfiCallOutParameter { par } }), ); assert!( found_async_result, "The check *wasn't* performed in analysis part: Guillaume was wrong!" ); let index_to_ignore = find_index_to_ignore( trampoline.output_params.iter().map(|par| &par.lib_par), trampoline.ffi_ret.as_ref().map(|ret| &ret.lib_par), ); let mut result: Vec<_> = trampoline .output_params .iter() .enumerate() .filter(|&(index, out)| { out.lib_par.direction == ParameterDirection::Out && out.lib_par.name != "error" && Some(index) != index_to_ignore }) .map(|(_, out)| { let mem_mode = c_type_mem_mode_lib( env, out.lib_par.typ, out.lib_par.caller_allocates, out.lib_par.transfer, ); let value = self.generate_initialized_value(&out.lib_par.name, &uninitialized_vars); if let OutMemMode::UninitializedNamed(_) = mem_mode { value } else { let array_length_name = self.array_length(out).cloned(); self.remove_extra_assume_init(&array_length_name, &mut uninitialized_vars); Chunk::FromGlibConversion { mode: out.into(), array_length_name, value: Box::new(value), } } }) .collect(); if let Some(ref ffi_ret) = trampoline.ffi_ret { let mem_mode = c_type_mem_mode_lib( env, ffi_ret.lib_par.typ, ffi_ret.lib_par.caller_allocates, ffi_ret.lib_par.transfer, ); let value = Chunk::Name("ret".to_string()); if let OutMemMode::UninitializedNamed(_) = mem_mode { result.insert(0, value); } else { let array_length_name = self.array_length(ffi_ret).cloned(); self.remove_extra_assume_init(&array_length_name, &mut uninitialized_vars); result.insert( 0, Chunk::FromGlibConversion { mode: ffi_ret.into(), array_length_name, value: Box::new(value), }, ); } } // If the trampoline doesn't have a GError parameter let has_error_parameter = self .async_trampoline .as_ref() .map(|a| a.has_error_parameter) .unwrap_or_default(); let result = Chunk::Tuple(result, TupleMode::WithUnit); let mut body = if has_error_parameter { vec![Chunk::Let { name: "error".to_string(), is_mut: true, value: Box::new(Chunk::NullMutPtr), type_: None, }] } else { vec![] }; let output_vars = trampoline .output_params .iter() .filter(|out| { out.lib_par.direction == ParameterDirection::Out && out.lib_par.name != "error" }) .map(|out| Chunk::Let { name: out.lib_par.name.clone(), is_mut: true, value: Box::new(type_mem_mode(env, &out.lib_par)), type_: None, }); body.extend(output_vars); let ret_name = if trampoline.ffi_ret.is_some() { if has_error_parameter { "ret" } else { "result" // Needed as in case of an error param we would have // let result = if error.is_null() { Ok()} else { // Err()}; } } else { "_" }; body.push(Chunk::Let { name: ret_name.to_string(), is_mut: false, value: Box::new(Chunk::FfiCall { name: trampoline.finish_func_name.clone(), params: finish_args, }), type_: None, }); if has_error_parameter { body.push(Chunk::Let { name: "result".to_string(), is_mut: false, value: Box::new(Chunk::ErrorResultReturn { ret: None, value: Box::new(result), }), type_: None, }); } body.push(Chunk::Let { name: "callback".to_string(), is_mut: false, value: Box::new(Chunk::Custom("Box_::from_raw(user_data as *mut _)".into())), type_: Some(Box::new(Chunk::Custom(format!( "Box_<{}<{}>>", use_glib_type(env, "thread_guard::ThreadGuard"), trampoline.bound_name )))), }); body.push(Chunk::Let { name: "callback".to_string(), is_mut: false, value: Box::new(Chunk::Custom("callback.into_inner()".into())), type_: Some(Box::new(Chunk::Custom(format!( "{}", trampoline.bound_name )))), }); body.push(Chunk::Call { func_name: "callback".to_string(), arguments: vec![Chunk::Name("result".to_string())], }); let parameters = vec![ Param { name: "_source_object".to_string(), typ: format!("*mut {}", use_glib_type(env, "gobject_ffi::GObject")), }, Param { name: "res".to_string(), typ: format!("*mut {}", use_gio_type(env, "ffi::GAsyncResult")), }, Param { name: "user_data".to_string(), typ: use_glib_if_needed(env, "ffi::gpointer"), }, ]; chunks.push(Chunk::ExternCFunc { name: format!( "{}<{}: {}>", trampoline.name, trampoline.bound_name, trampoline.callback_type ), parameters, body: Box::new(Chunk::Chunks(body)), return_value: None, bounds: String::new(), }); let chunk = Chunk::Let { name: "callback".to_string(), is_mut: false, value: Box::new(Chunk::Name(format!( "{}::<{}>", trampoline.name, trampoline.bound_name ))), type_: None, }; chunks.push(chunk); } fn array_length(&self, param: &analysis::Parameter) -> Option<&String> { self.async_trampoline.as_ref().and_then(|trampoline| { param .lib_par .array_length .map(|index| &trampoline.output_params[index as usize].lib_par.name) }) } fn add_assertion(&self, chunks: &mut Vec) { match self.assertion { SafetyAssertionMode::None => (), x => chunks.insert(0, Chunk::AssertInit(x)), } } fn add_in_array_lengths(&self, chunks: &mut Vec) { for trans in &self.transformations { if let TransformationType::Length { ref array_name, ref array_length_name, .. } = trans.transformation_type { if let In = self.parameters[trans.ind_c] { let value = Chunk::Custom(format!("{array_name}.len() as _")); chunks.push(Chunk::Let { name: array_length_name.clone(), is_mut: false, value: Box::new(value), type_: None, }); } } } } fn generate_call(&self, calls: &FuncParameters<'_>) -> Chunk { let params = self.generate_func_parameters(calls); Chunk::FfiCall { name: self.glib_name.clone(), params, } } fn generate_call_conversion( &self, call: Chunk, uninitialized_vars: &mut Vec<(String, bool)>, ) -> Chunk { let array_length_name = self.find_array_length_name(""); self.remove_extra_assume_init(&array_length_name, uninitialized_vars); Chunk::FfiCallConversion { ret: self.ret.ret.clone(), array_length_name, call: Box::new(call), } } fn generate_func_parameters(&self, calls: &FuncParameters<'_>) -> Vec { let mut params = Vec::new(); for trans in &self.transformations { if !trans.transformation_type.is_to_glib() { continue; } let par = &self.parameters[trans.ind_c]; let chunk = match par { In => Chunk::FfiCallParameter { transformation_type: trans.transformation_type.clone(), }, Out { parameter, .. } => Chunk::FfiCallOutParameter { par: parameter.clone(), }, }; params.push(chunk); } let mut to_insert = Vec::new(); for (user_data_index, FuncParameter { pos, callbacks, .. }) in calls.iter() { let all_call = callbacks.iter().all(|c| c.scope.is_call()); to_insert.push(( *user_data_index, Chunk::FfiCallParameter { transformation_type: TransformationType::ToGlibDirect { name: if all_call { format!("super_callback{pos} as *mut _ as *mut _") } else { format!("Box_::into_raw(super_callback{pos}) as *mut _") }, }, }, )); } for destroy in &self.destroys { to_insert.push(( destroy.destroy_index, Chunk::FfiCallParameter { transformation_type: TransformationType::ToGlibDirect { name: format!("destroy_call{}", destroy.destroy_index), }, }, )); } to_insert.sort_unstable_by(|(a, _), (b, _)| a.cmp(b)); for (pos, data) in to_insert { params.insert(pos, data); } params } fn get_outs(&self) -> Vec<&Parameter> { self.parameters .iter() .filter(|par| matches!(*par, Out { .. })) .collect() } fn get_outs_without_error(&self) -> Vec<&Parameter> { self.parameters .iter() .filter(|par| { if let Out { parameter, .. } = par { !parameter.is_error } else { false } }) .collect() } fn check_if_need_glib_conversion(&self, env: &Env, typ: TypeId) -> bool { // TODO: maybe improve this part to potentially handle more cases than just // glib::Pid? matches!( env.type_(typ), library::Type::Alias(a) if a.c_identifier == "GPid" ) } fn write_out_variables(&self, body: &mut Vec, env: &Env) -> Vec<(String, bool)> { let mut uninitialized_vars = Vec::new(); for par in self.get_outs() { if let Out { parameter, mem_mode, } = par { let val = self.get_uninitialized(mem_mode); if val.is_uninitialized() { uninitialized_vars.push(( parameter.name.clone(), self.check_if_need_glib_conversion(env, parameter.typ), )); } let chunk = Chunk::Let { name: parameter.name.clone(), is_mut: true, value: Box::new(val), type_: None, }; body.push(chunk); } } uninitialized_vars } fn get_uninitialized(&self, mem_mode: &OutMemMode) -> Chunk { use self::OutMemMode::*; match mem_mode { Uninitialized => Chunk::Uninitialized, UninitializedNamed(ref name) => Chunk::UninitializedNamed { name: name.clone() }, NullPtr => Chunk::NullPtr, NullMutPtr => Chunk::NullMutPtr, } } fn generate_out_return(&self, uninitialized_vars: &mut Vec<(String, bool)>) -> Option { if !self.outs_as_return { return None; } let outs = self.get_outs_without_error(); let mut chs: Vec = Vec::with_capacity(outs.len()); for par in outs { if let Out { parameter, mem_mode, } = par { if self.transformations.iter().any(|tr| { matches!( &tr.transformation_type, TransformationType::Length { array_length_name, .. } if array_length_name == ¶meter.name ) }) { continue; } chs.push(self.out_parameter_to_return(parameter, mem_mode, uninitialized_vars)); } } let chunk = Chunk::Tuple(chs, TupleMode::Auto); Some(chunk) } fn out_parameter_to_return( &self, parameter: ¶meter_ffi_call_out::Parameter, mem_mode: &OutMemMode, uninitialized_vars: &mut Vec<(String, bool)>, ) -> Chunk { let value = self.generate_initialized_value(¶meter.name, uninitialized_vars); if let OutMemMode::UninitializedNamed(_) = mem_mode { value } else { let array_length_name = self.find_array_length_name(¶meter.name); self.remove_extra_assume_init(&array_length_name, uninitialized_vars); Chunk::FromGlibConversion { mode: parameter.into(), array_length_name, value: Box::new(value), } } } fn apply_outs_mode( &self, call: Chunk, ret: Option, uninitialized_vars: &mut Vec<(String, bool)>, ) -> (Chunk, Option) { use crate::analysis::out_parameters::Mode::*; match self.outs_mode { None => (call, ret), Normal => (call, ret), Optional => { let call = Chunk::Let { name: "ret".into(), is_mut: false, value: Box::new(call), type_: Option::None, }; let ret = ret.expect("No return in optional outs mode"); let ret = Chunk::OptionalReturn { condition: "ret".into(), value: Box::new(ret), }; (call, Some(ret)) } Combined => { let call = Chunk::Let { name: "ret".into(), is_mut: false, value: Box::new(call), type_: Option::None, }; let mut ret = ret.expect("No return in combined outs mode"); if let Chunk::Tuple(ref mut vec, _) = ret { vec.insert(0, Chunk::Custom("ret".into())); } (call, Some(ret)) } Throws(return_strategy) => { // extracting original FFI function call let (boxed_call, array_length_name, ret_info) = if let Chunk::FfiCallConversion { call: inner, array_length_name, ret: ret_info, } = call { (inner, array_length_name, ret_info) } else { panic!("Call without Chunk::FfiCallConversion") }; self.remove_extra_assume_init(&array_length_name, uninitialized_vars); let (name, assert_safe_ret) = match return_strategy { ThrowFunctionReturnStrategy::ReturnResult => ("ret", Option::None), ThrowFunctionReturnStrategy::CheckError => { ("is_ok", Some(Box::new(Chunk::AssertErrorSanity))) } ThrowFunctionReturnStrategy::Void => ("_", Option::None), }; let call = Chunk::Let { name: name.into(), is_mut: false, value: boxed_call, type_: Option::None, }; let mut ret = ret.expect("No return in throws outs mode"); if let Chunk::Tuple(vec, mode) = &mut ret { *mode = TupleMode::WithUnit; if return_strategy == ThrowFunctionReturnStrategy::ReturnResult { let val = Chunk::Custom("ret".into()); let conv = Chunk::FfiCallConversion { call: Box::new(val), array_length_name, ret: ret_info, }; vec.insert(0, conv); } } else { panic!("Return is not Tuple") } ret = Chunk::ErrorResultReturn { ret: assert_safe_ret, value: Box::new(ret), }; (call, Some(ret)) } } } fn find_array_length_name(&self, array_name_: &str) -> Option { self.transformations.iter().find_map(|tr| { if let TransformationType::Length { ref array_name, ref array_length_name, .. } = tr.transformation_type { if array_name == array_name_ { Some(array_length_name.clone()) } else { None } } else { None } }) } } fn c_type_mem_mode_lib( env: &Env, typ: library::TypeId, caller_allocates: bool, transfer: library::Transfer, ) -> OutMemMode { use self::OutMemMode::*; match ConversionType::of(env, typ) { ConversionType::Pointer => { if caller_allocates { UninitializedNamed(RustType::try_new(env, typ).unwrap().into_string()) } else { use crate::library::Type::*; let type_ = env.library.type_(typ); match type_ { Basic( library::Basic::Utf8 | library::Basic::OsString | library::Basic::Filename, ) => { if transfer == library::Transfer::Full { NullMutPtr } else { NullPtr } } _ => NullMutPtr, } } } _ => Uninitialized, } } fn c_type_mem_mode(env: &Env, parameter: &AnalysisCParameter) -> OutMemMode { c_type_mem_mode_lib( env, parameter.typ, parameter.caller_allocates, parameter.transfer, ) } fn type_mem_mode(env: &Env, parameter: &library::Parameter) -> Chunk { match ConversionType::of(env, parameter.typ) { ConversionType::Pointer => { if parameter.caller_allocates { Chunk::UninitializedNamed { name: RustType::try_new(env, parameter.typ).unwrap().into_string(), } } else { use crate::library::Type::*; let type_ = env.library.type_(parameter.typ); match type_ { Basic( library::Basic::Utf8 | library::Basic::OsString | library::Basic::Filename, ) => { if parameter.transfer == library::Transfer::Full { Chunk::NullMutPtr } else { Chunk::NullPtr } } _ => Chunk::NullMutPtr, } } } _ => Chunk::Uninitialized, } } fn add_chunk_for_type( env: &Env, typ_: library::TypeId, par: &trampoline_parameters::Transformation, body: &mut Vec, ty_name: &str, nullable: library::Nullable, ) -> bool { let type_ = env.type_(typ_); match type_ { library::Type::Basic(x) if !x.requires_conversion() => true, library::Type::Basic(library::Basic::Boolean) => { body.push(Chunk::Custom(format!( "let {0} = from_glib({0});", par.name ))); true } library::Type::Basic(library::Basic::UniChar) => { body.push(Chunk::Custom(format!( "let {0} = std::convert::TryFrom::try_from({0})\ .expect(\"conversion from an invalid Unicode value attempted\");", par.name ))); true } library::Type::Alias(_) if ty_name == "glib::Pid" => { body.push(Chunk::Custom(format!( "let {0} = from_glib({0});", par.name ))); true } library::Type::Alias(x) => add_chunk_for_type(env, x.typ, par, body, ty_name, nullable), x => { let (begin, end) = crate::codegen::trampoline_from_glib::from_glib_xxx(par.transfer, true); let type_name; if is_gstring(ty_name) { if *nullable { if par.conversion_type == ConversionType::Borrow { type_name = String::from(": Borrowed>"); } else { type_name = String::from(": Option"); } } else if par.conversion_type == ConversionType::Borrow { type_name = String::from(": Borrowed"); } else { type_name = String::from(": GString"); } } else if par.transfer == library::Transfer::None && *nullable { if par.conversion_type == ConversionType::Borrow { type_name = format!(": Borrowed>"); } else { type_name = format!(": Option<{ty_name}>"); } } else { type_name = String::new(); } body.push(Chunk::Custom(format!( "let {1}{3} = {0}{1}{2};", begin, par.name, end, type_name ))); x.is_basic() } } } gir-0.20.5/src/codegen/functions.rs000066400000000000000000000016341475434152100171420ustar00rootroot00000000000000use std::path::Path; use log::info; use crate::{ codegen::{function, general}, env::Env, file_saver, }; pub fn generate(env: &Env, root_path: &Path, mod_rs: &mut Vec) { info!("Generate global functions"); let Some(ref functions) = env.analysis.global_functions else { return; }; // Don't generate anything if we have no functions if functions.functions.is_empty() { return; } let path = root_path.join("functions.rs"); file_saver::save_to_file(path, env.config.make_backup, |w| { general::start_comments(w, &env.config)?; general::uses(w, env, &functions.imports, None)?; writeln!(w)?; mod_rs.push("\npub(crate) mod functions;".into()); for func_analysis in &functions.functions { function::generate(w, env, None, func_analysis, None, None, false, false, 0)?; } Ok(()) }); } gir-0.20.5/src/codegen/general.rs000066400000000000000000000736501475434152100165560ustar00rootroot00000000000000use std::{ collections::BTreeMap, fmt::{Display, Write as FmtWrite}, io::{Result, Write}, ops::Index, }; use super::Visibility; use crate::{ analysis::{ self, general::StatusedTypeId, imports::{ImportConditions, Imports}, namespaces, special_functions::TraitInfo, }, config::{derives::Derive, Config}, env::Env, gir_version::VERSION, library::TypeId, nameutil::use_glib_type, version::Version, writer::primitives::tabs, }; pub fn start_comments(w: &mut dyn Write, conf: &Config) -> Result<()> { if conf.single_version_file.is_some() { start_comments_no_version(w, conf) } else { single_version_file(w, conf, "// ")?; writeln!(w, "// DO NOT EDIT") } } pub fn start_comments_no_version(w: &mut dyn Write, conf: &Config) -> Result<()> { writeln!( w, "// This file was generated by gir (https://github.com/gtk-rs/gir)" )?; write!( w, "{}", conf.girs_version .iter() .fold(String::new(), |mut output, info| { writeln!( output, "// from {}{}", info.gir_dir.display(), info.get_repository_url() .map_or_else(String::new, |url| format!(" ({url})")), ) .unwrap(); output }) )?; writeln!(w, "// DO NOT EDIT")?; Ok(()) } pub fn single_version_file(w: &mut dyn Write, conf: &Config, prefix: &str) -> Result<()> { write!( w, "{}Generated by gir (https://github.com/gtk-rs/gir @ {}) {}", prefix, VERSION, conf.girs_version .iter() .fold(String::new(), |mut output, info| { match (info.get_repository_url(), info.get_hash()) { (Some(url), Some(hash)) => writeln!( output, "{}from {} ({} @ {})", prefix, info.gir_dir.display(), url, hash, ), (None, Some(hash)) => { writeln!( output, "{}from {} (@ {})", prefix, info.gir_dir.display(), hash, ) } _ => writeln!(output, "{}from {}", prefix, info.gir_dir.display()), } .unwrap(); output }), ) } pub fn uses( w: &mut dyn Write, env: &Env, imports: &Imports, outer_version: Option, ) -> Result<()> { writeln!(w)?; let mut grouped_imports: BTreeMap<(&str, Option), Vec<&str>> = BTreeMap::new(); for (name, scope) in imports.iter() { let (crate_name, import) = name.split_once("::").unwrap(); let mut scope = scope.clone(); // The check here is needed to group unneeded version guards and allow grouping those imports scope.version = Version::if_stricter_than(scope.version, outer_version); let to_compare_with = env.config.min_required_version(env, None); scope.version = match (scope.version, to_compare_with) { (Some(v), Some(to_compare_v)) => { if v > to_compare_v { scope.version } else { None } } (Some(_), _) => scope.version, _ => None, }; let key = if scope.constraints.is_empty() && scope.version.is_none() { (crate_name, None) } else { (crate_name, Some(scope)) }; grouped_imports .entry(key) .and_modify(|entry| entry.push(import)) .or_insert_with(|| vec![import]); } for ((crate_name, scope), names) in grouped_imports.iter() { if !scope.is_none() { let scope = scope.as_ref().unwrap(); if !scope.constraints.is_empty() { if scope.constraints.len() == 1 { writeln!(w, "#[cfg({})]", scope.constraints[0])?; } else { writeln!(w, "#[cfg(any({}))]", scope.constraints.join(", "))?; } writeln!( w, "#[cfg_attr(docsrs, doc(cfg({})))]", scope.constraints.join(", ") )?; } version_condition(w, env, None, scope.version, false, 0)?; } writeln!(w, "use {crate_name}::{{{}}};", names.join(","))?; } Ok(()) } fn format_parent_name(env: &Env, p: &StatusedTypeId) -> String { if p.type_id.ns_id == namespaces::MAIN { p.name.clone() } else { format!( "{krate}::{name}", krate = env.namespaces[p.type_id.ns_id].crate_name, name = p.name, ) } } pub fn define_fundamental_type( w: &mut dyn Write, env: &Env, type_name: &str, glib_name: &str, glib_func_name: &str, ref_func: Option<&str>, unref_func: Option<&str>, parents: &[StatusedTypeId], visibility: Visibility, type_id: TypeId, ) -> Result<()> { let sys_crate_name = env.sys_crate_import(type_id); writeln!(w, "{} {{", use_glib_type(env, "wrapper!"))?; doc_alias(w, glib_name, "", 1)?; external_doc_link( w, env.config.external_docs_url.as_deref(), type_name, &visibility, 1, )?; writeln!( w, "\t{visibility} struct {type_name}(Shared<{sys_crate_name}::{glib_name}>);" )?; writeln!(w)?; writeln!(w, "\tmatch fn {{")?; let (ref_fn, unref_fn, ptr, ffi_crate_name) = if parents.is_empty() { // it's the super-type, it must have a ref/unref functions ( ref_func.unwrap().to_owned(), unref_func.unwrap().to_owned(), "ptr".to_owned(), sys_crate_name.to_owned(), ) } else { let (ref_fn, unref_fn, ptr, ffi_crate_name) = parents .iter() .find_map(|p| { use crate::library::*; let type_ = env.library.type_(p.type_id); let parent_sys_crate_name = env.sys_crate_import(p.type_id); match type_ { Type::Class(class) => Some(( class.ref_fn.as_ref().unwrap().clone(), class.unref_fn.as_ref().unwrap().clone(), format!( "ptr as *mut {}::{}", parent_sys_crate_name, class.c_type.clone() ), parent_sys_crate_name, )), _ => None, } }) .unwrap(); // otherwise get that information from the parent (ref_fn, unref_fn, ptr, ffi_crate_name) }; writeln!(w, "\t\tref => |ptr| {ffi_crate_name}::{ref_fn}({ptr}),")?; writeln!(w, "\t\tunref => |ptr| {ffi_crate_name}::{unref_fn}({ptr}),")?; writeln!(w, "\t}}")?; writeln!(w, "}}")?; // We can't use type_ from glib::wrapper! because that would auto-implement // Value traits which are often not the correct types writeln!(w, "\n")?; writeln!(w, "impl StaticType for {} {{", type_name)?; writeln!(w, "\tfn static_type() -> {} {{", use_glib_type(env, "Type"))?; writeln!( w, "\t\t unsafe {{ from_glib({sys_crate_name}::{glib_func_name}()) }}" )?; writeln!(w, "\t}}")?; writeln!(w, "}}")?; Ok(()) } pub fn define_object_type( w: &mut dyn Write, env: &Env, type_name: &str, glib_name: &str, glib_class_name: Option<&str>, glib_func_name: &str, is_interface: bool, parents: &[StatusedTypeId], visibility: Visibility, type_id: TypeId, ) -> Result<()> { let sys_crate_name = env.sys_crate_import(type_id); let class_name = { if let Some(s) = glib_class_name { format!(", {sys_crate_name}::{s}") } else { String::new() } }; let kind_name = if is_interface { "Interface" } else { "Object" }; let parents: Vec = parents .iter() .filter(|p| !p.status.ignored()) .cloned() .collect(); writeln!(w, "{} {{", use_glib_type(env, "wrapper!"))?; doc_alias(w, glib_name, "", 1)?; external_doc_link( w, env.config.external_docs_url.as_deref(), type_name, &visibility, 1, )?; if parents.is_empty() { writeln!( w, "\t{visibility} struct {type_name}({kind_name}<{sys_crate_name}::{glib_name}{class_name}>);" )?; } else if is_interface { let prerequisites: Vec = parents.iter().map(|p| format_parent_name(env, p)).collect(); writeln!( w, "\t{} struct {}(Interface<{}::{}{}>) @requires {};", visibility, type_name, sys_crate_name, glib_name, class_name, prerequisites.join(", ") )?; } else { let interfaces: Vec = parents .iter() .filter(|p| { use crate::library::*; matches!( *env.library.type_(p.type_id), Type::Interface { .. } if !p.status.ignored() ) }) .map(|p| format_parent_name(env, p)) .collect(); let parents: Vec = parents .iter() .filter(|p| { use crate::library::*; matches!( *env.library.type_(p.type_id), Type::Class { .. } if !p.status.ignored() ) }) .map(|p| format_parent_name(env, p)) .collect(); let mut parents_string = String::new(); if !parents.is_empty() { parents_string.push_str(format!(" @extends {}", parents.join(", ")).as_str()); } if !interfaces.is_empty() { if !parents.is_empty() { parents_string.push(','); } parents_string.push_str(format!(" @implements {}", interfaces.join(", ")).as_str()); } writeln!( w, "\t{visibility} struct {type_name}(Object<{sys_crate_name}::{glib_name}{class_name}>){parents_string};", )?; } writeln!(w)?; writeln!(w, "\tmatch fn {{")?; writeln!(w, "\t\ttype_ => || {sys_crate_name}::{glib_func_name}(),")?; writeln!(w, "\t}}")?; writeln!(w, "}}")?; Ok(()) } fn define_boxed_type_internal( w: &mut dyn Write, env: &Env, type_name: &str, glib_name: &str, copy_fn: &TraitInfo, free_fn: &str, boxed_inline: bool, init_function_expression: &Option, copy_into_function_expression: &Option, clear_function_expression: &Option, get_type_fn: Option<&str>, derive: &[Derive], visibility: Visibility, type_id: TypeId, ) -> Result<()> { let sys_crate_name = env.sys_crate_import(type_id); writeln!(w, "{} {{", use_glib_type(env, "wrapper!"))?; derives(w, derive, 1)?; writeln!( w, "\t{} struct {}(Boxed{}<{}::{}>);", visibility, type_name, if boxed_inline { "Inline" } else { "" }, sys_crate_name, glib_name )?; writeln!(w)?; writeln!(w, "\tmatch fn {{")?; let mut_ov = if copy_fn.first_parameter_mut { "mut_override(ptr)" } else { "ptr" }; writeln!( w, "\t\tcopy => |ptr| {}::{}({}),", sys_crate_name, copy_fn.glib_name, mut_ov )?; writeln!(w, "\t\tfree => |ptr| {sys_crate_name}::{free_fn}(ptr),")?; if let ( Some(init_function_expression), Some(copy_into_function_expression), Some(clear_function_expression), ) = ( init_function_expression, copy_into_function_expression, clear_function_expression, ) { writeln!(w, "\t\tinit => {init_function_expression},",)?; writeln!(w, "\t\tcopy_into => {copy_into_function_expression},",)?; writeln!(w, "\t\tclear => {clear_function_expression},",)?; } if let Some(get_type_fn) = get_type_fn { writeln!(w, "\t\ttype_ => || {sys_crate_name}::{get_type_fn}(),")?; } writeln!(w, "\t}}")?; writeln!(w, "}}")?; Ok(()) } pub fn define_boxed_type( w: &mut dyn Write, env: &Env, type_name: &str, glib_name: &str, copy_fn: &TraitInfo, free_fn: &str, boxed_inline: bool, init_function_expression: &Option, copy_into_function_expression: &Option, clear_function_expression: &Option, get_type_fn: Option<(String, Option)>, derive: &[Derive], visibility: Visibility, type_id: TypeId, ) -> Result<()> { writeln!(w)?; if let Some((ref get_type_fn, get_type_version)) = get_type_fn { if get_type_version.is_some() { version_condition(w, env, None, get_type_version, false, 0)?; define_boxed_type_internal( w, env, type_name, glib_name, copy_fn, free_fn, boxed_inline, init_function_expression, copy_into_function_expression, clear_function_expression, Some(get_type_fn), derive, visibility, type_id, )?; writeln!(w)?; not_version_condition_no_docsrs(w, env, None, get_type_version, false, 0)?; define_boxed_type_internal( w, env, type_name, glib_name, copy_fn, free_fn, boxed_inline, init_function_expression, copy_into_function_expression, clear_function_expression, None, derive, visibility, type_id, )?; } else { define_boxed_type_internal( w, env, type_name, glib_name, copy_fn, free_fn, boxed_inline, init_function_expression, copy_into_function_expression, clear_function_expression, Some(get_type_fn), derive, visibility, type_id, )?; } } else { define_boxed_type_internal( w, env, type_name, glib_name, copy_fn, free_fn, boxed_inline, init_function_expression, copy_into_function_expression, clear_function_expression, None, derive, visibility, type_id, )?; } Ok(()) } pub fn define_auto_boxed_type( w: &mut dyn Write, env: &Env, type_name: &str, glib_name: &str, boxed_inline: bool, init_function_expression: &Option, copy_into_function_expression: &Option, clear_function_expression: &Option, get_type_fn: &str, derive: &[Derive], visibility: Visibility, type_id: TypeId, ) -> Result<()> { let sys_crate_name = env.sys_crate_import(type_id); writeln!(w)?; writeln!(w, "{} {{", use_glib_type(env, "wrapper!"))?; derives(w, derive, 1)?; writeln!( w, "\t{} struct {}(Boxed{}<{}::{}>);", visibility, type_name, if boxed_inline { "Inline" } else { "" }, sys_crate_name, glib_name )?; writeln!(w)?; writeln!(w, "\tmatch fn {{")?; writeln!( w, "\t\tcopy => |ptr| {}({}::{}(), ptr as *mut _) as *mut {}::{},", use_glib_type(env, "gobject_ffi::g_boxed_copy"), sys_crate_name, get_type_fn, sys_crate_name, glib_name )?; writeln!( w, "\t\tfree => |ptr| {}({}::{}(), ptr as *mut _),", use_glib_type(env, "gobject_ffi::g_boxed_free"), sys_crate_name, get_type_fn )?; if let ( Some(init_function_expression), Some(copy_into_function_expression), Some(clear_function_expression), ) = ( init_function_expression, copy_into_function_expression, clear_function_expression, ) { writeln!(w, "\t\tinit => {init_function_expression},",)?; writeln!(w, "\t\tcopy_into => {copy_into_function_expression},",)?; writeln!(w, "\t\tclear => {clear_function_expression},",)?; } writeln!(w, "\t\ttype_ => || {sys_crate_name}::{get_type_fn}(),")?; writeln!(w, "\t}}")?; writeln!(w, "}}")?; Ok(()) } fn define_shared_type_internal( w: &mut dyn Write, env: &Env, type_name: &str, glib_name: &str, ref_fn: &str, unref_fn: &str, get_type_fn: Option<&str>, derive: &[Derive], visibility: Visibility, type_id: TypeId, ) -> Result<()> { let sys_crate_name = env.sys_crate_import(type_id); writeln!(w, "{} {{", use_glib_type(env, "wrapper!"))?; derives(w, derive, 1)?; writeln!( w, "\t{visibility} struct {type_name}(Shared<{sys_crate_name}::{glib_name}>);" )?; writeln!(w)?; writeln!(w, "\tmatch fn {{")?; writeln!(w, "\t\tref => |ptr| {sys_crate_name}::{ref_fn}(ptr),")?; writeln!(w, "\t\tunref => |ptr| {sys_crate_name}::{unref_fn}(ptr),")?; if let Some(get_type_fn) = get_type_fn { writeln!(w, "\t\ttype_ => || {sys_crate_name}::{get_type_fn}(),")?; } writeln!(w, "\t}}")?; writeln!(w, "}}")?; Ok(()) } pub fn define_shared_type( w: &mut dyn Write, env: &Env, type_name: &str, glib_name: &str, ref_fn: &str, unref_fn: &str, get_type_fn: Option<(String, Option)>, derive: &[Derive], visibility: Visibility, type_id: TypeId, ) -> Result<()> { writeln!(w)?; if let Some((ref get_type_fn, get_type_version)) = get_type_fn { if get_type_version.is_some() { version_condition(w, env, None, get_type_version, false, 0)?; define_shared_type_internal( w, env, type_name, glib_name, ref_fn, unref_fn, Some(get_type_fn), derive, visibility, type_id, )?; writeln!(w)?; not_version_condition_no_docsrs(w, env, None, get_type_version, false, 0)?; define_shared_type_internal( w, env, type_name, glib_name, ref_fn, unref_fn, None, derive, visibility, type_id, )?; } else { define_shared_type_internal( w, env, type_name, glib_name, ref_fn, unref_fn, Some(get_type_fn), derive, visibility, type_id, )?; } } else { define_shared_type_internal( w, env, type_name, glib_name, ref_fn, unref_fn, None, derive, visibility, type_id, )?; } Ok(()) } pub fn cfg_deprecated( w: &mut dyn Write, env: &Env, type_tid: Option, deprecated: Option, commented: bool, indent: usize, ) -> Result<()> { if let Some(s) = cfg_deprecated_string(env, type_tid, deprecated, commented, indent) { writeln!(w, "{s}")?; } Ok(()) } pub fn cfg_deprecated_string( env: &Env, type_tid: Option, deprecated: Option, commented: bool, indent: usize, ) -> Option { let comment = if commented { "//" } else { "" }; deprecated.map(|v| { if env.is_too_low_version(type_tid.map(|t| t.ns_id), Some(v)) { format!("{}{}#[deprecated = \"Since {}\"]", tabs(indent), comment, v) } else { format!( "{}{}#[cfg_attr({}, deprecated = \"Since {}\")]", tabs(indent), comment, v.to_cfg(None), v, ) } }) } pub fn version_condition( w: &mut dyn Write, env: &Env, ns_id: Option, version: Option, commented: bool, indent: usize, ) -> Result<()> { if let Some(s) = version_condition_string(env, ns_id, version, commented, indent) { writeln!(w, "{s}")?; } Ok(()) } pub fn version_condition_no_doc( w: &mut dyn Write, env: &Env, ns_id: Option, version: Option, commented: bool, indent: usize, ) -> Result<()> { let to_compare_with = env.config.min_required_version(env, ns_id); let should_generate = match (version, to_compare_with) { (Some(v), Some(to_compare_v)) => v > to_compare_v, (Some(_), _) => true, _ => false, }; if should_generate { // Prefix with the crate name if it's not the main one let namespace_name = ns_id.and_then(|ns| { if ns == namespaces::MAIN { None } else { Some(env.namespaces.index(ns).crate_name.clone()) } }); if let Some(s) = cfg_condition_string_no_doc( Some(&version.unwrap().to_cfg(namespace_name.as_deref())), commented, indent, ) { writeln!(w, "{s}")?; } } Ok(()) } pub fn version_condition_doc( w: &mut dyn Write, env: &Env, version: Option, commented: bool, indent: usize, ) -> Result<()> { match version { Some(v) if v > env.config.min_cfg_version => { if let Some(s) = cfg_condition_string_doc(Some(&v.to_cfg(None)), commented, indent) { writeln!(w, "{s}")?; } } _ => {} } Ok(()) } pub fn version_condition_string( env: &Env, ns_id: Option, version: Option, commented: bool, indent: usize, ) -> Option { let to_compare_with = env.config.min_required_version(env, ns_id); let should_generate = match (version, to_compare_with) { (Some(v), Some(to_compare_v)) => v > to_compare_v, (Some(_), _) => true, _ => false, }; if should_generate { // Prefix with the crate name if it's not the main one let namespace_name = ns_id.and_then(|ns| { if ns == namespaces::MAIN { None } else { Some(env.namespaces.index(ns).crate_name.clone()) } }); cfg_condition_string( Some(&version.unwrap().to_cfg(namespace_name.as_deref())), commented, indent, ) } else { None } } pub fn not_version_condition( w: &mut dyn Write, version: Option, commented: bool, indent: usize, ) -> Result<()> { if let Some(s) = version.and_then(|v| { cfg_condition_string(Some(&format!("not({})", v.to_cfg(None))), commented, indent) }) { writeln!(w, "{s}")?; } Ok(()) } pub fn not_version_condition_no_docsrs( w: &mut dyn Write, env: &Env, ns_id: Option, version: Option, commented: bool, indent: usize, ) -> Result<()> { if let Some(v) = version { let comment = if commented { "//" } else { "" }; let namespace_name = ns_id.and_then(|ns| { if ns == namespaces::MAIN { None } else { Some(env.namespaces.index(ns).crate_name.clone()) } }); let s = format!( "{}{}#[cfg(not(any({})))]", tabs(indent), comment, v.to_cfg(namespace_name.as_deref()) ); writeln!(w, "{s}")?; } Ok(()) } pub fn cfg_condition( w: &mut dyn Write, cfg_condition: Option<&(impl Display + ?Sized)>, commented: bool, indent: usize, ) -> Result<()> { if let Some(s) = cfg_condition_string(cfg_condition, commented, indent) { writeln!(w, "{s}")?; } Ok(()) } pub fn cfg_condition_no_doc( w: &mut dyn Write, cfg_condition: Option<&(impl Display + ?Sized)>, commented: bool, indent: usize, ) -> Result<()> { if let Some(s) = cfg_condition_string_no_doc(cfg_condition, commented, indent) { writeln!(w, "{s}")?; } Ok(()) } pub fn cfg_condition_string_no_doc( cfg_condition: Option<&(impl Display + ?Sized)>, commented: bool, indent: usize, ) -> Option { cfg_condition.map(|cfg| { let comment = if commented { "//" } else { "" }; format!("{0}{1}#[cfg({2})]", tabs(indent), comment, cfg) }) } pub fn cfg_condition_doc( w: &mut dyn Write, cfg_condition: Option<&(impl Display + ?Sized)>, commented: bool, indent: usize, ) -> Result<()> { if let Some(s) = cfg_condition_string_doc(cfg_condition, commented, indent) { writeln!(w, "{s}")?; } Ok(()) } pub fn cfg_condition_string_doc( cfg_condition: Option<&(impl Display + ?Sized)>, commented: bool, indent: usize, ) -> Option { cfg_condition.map(|cfg| { let comment = if commented { "//" } else { "" }; format!( "{0}{1}#[cfg_attr(docsrs, doc(cfg({2})))]", tabs(indent), comment, cfg, ) }) } pub fn cfg_condition_string( cfg_condition: Option<&(impl Display + ?Sized)>, commented: bool, indent: usize, ) -> Option { cfg_condition.map(|_| { format!( "{}\n{}", cfg_condition_string_no_doc(cfg_condition, commented, indent).unwrap(), cfg_condition_string_doc(cfg_condition, commented, indent).unwrap(), ) }) } pub fn derives(w: &mut dyn Write, derives: &[Derive], indent: usize) -> Result<()> { for derive in derives { let s = match &derive.cfg_condition { Some(condition) => format!( "#[cfg_attr({}, derive({}))]", condition, derive.names.join(", ") ), None => format!("#[derive({})]", derive.names.join(", ")), }; writeln!(w, "{}{}", tabs(indent), s)?; } Ok(()) } pub fn doc_alias(w: &mut dyn Write, name: &str, comment_prefix: &str, indent: usize) -> Result<()> { writeln!( w, "{}{}#[doc(alias = \"{}\")]", tabs(indent), comment_prefix, name, ) } pub fn external_doc_link( w: &mut dyn Write, external_url: Option<&str>, name: &str, visibility: &Visibility, indent: usize, ) -> Result<()> { // Don't generate the external doc link on non-public types. if !visibility.is_public() { Ok(()) } else if let Some(external_url) = external_url { writeln!( w, "{}/// This documentation is incomplete due to license restrictions and limitations on docs.rs. Please have a look at [our official docs]({}/index.html?search={}) for more information.", tabs(indent), external_url.trim_end_matches('/'), name ) } else { Ok(()) } } pub fn doc_hidden( w: &mut dyn Write, doc_hidden: bool, comment_prefix: &str, indent: usize, ) -> Result<()> { if doc_hidden { writeln!(w, "{}{}#[doc(hidden)]", tabs(indent), comment_prefix) } else { Ok(()) } } pub fn allow_deprecated( w: &mut dyn Write, allow_deprecated: Option, commented: bool, indent: usize, ) -> Result<()> { if allow_deprecated.is_some() { writeln!( w, "{}{}#[allow(deprecated)]", tabs(indent), if commented { "//" } else { "" } ) } else { Ok(()) } } pub fn write_vec(w: &mut dyn Write, v: &[T]) -> Result<()> { for s in v { writeln!(w, "{s}")?; } Ok(()) } pub fn declare_default_from_new( w: &mut dyn Write, env: &Env, name: &str, functions: &[analysis::functions::Info], has_builder: bool, ) -> Result<()> { if let Some(func) = functions.iter().find(|f| { !f.hidden && f.status.need_generate() && f.name == "new" // Cannot generate Default implementation for Option<> && f.ret.parameter.as_ref().is_some_and(|x| !*x.lib_par.nullable) }) { if func.parameters.rust_parameters.is_empty() { writeln!(w)?; version_condition(w, env, None, func.version, false, 0)?; writeln!( w, "impl Default for {name} {{ fn default() -> Self {{ Self::new() }} }}" )?; } else if has_builder { // create an alternative default implementation the uses `glib::object::Object::new()` writeln!(w)?; version_condition(w, env, None, func.version, false, 0)?; writeln!( w, "impl Default for {name} {{ fn default() -> Self {{ glib::object::Object::new::() }} }}" )?; } } Ok(()) } /// Escapes string in format suitable for placing inside double quotes. pub fn escape_string(s: &str) -> String { let mut es = String::with_capacity(s.len() * 2); for c in s.chars() { match c { '\"' | '\\' => es.push('\\'), _ => (), } es.push(c); } es } #[cfg(test)] mod tests { use super::*; #[test] fn test_escape_string() { assert_eq!(escape_string(""), ""); assert_eq!(escape_string("no escaping here"), "no escaping here"); assert_eq!(escape_string(r#"'"\"#), r#"'\"\\"#); } } gir-0.20.5/src/codegen/mod.rs000066400000000000000000000106201475434152100157040ustar00rootroot00000000000000use std::{ fmt::Display, io::{Result, Write}, path::Path, }; use general::{cfg_condition, version_condition}; use crate::{ config::{gobjects::GObject, WorkMode}, env::Env, file_saver::*, library::Member, version::Version, }; mod alias; mod bound; mod child_properties; mod constants; mod doc; mod enums; mod flags; pub mod function; mod function_body_chunk; mod functions; mod general; mod object; mod objects; mod parameter; mod properties; mod property_body; mod record; mod records; mod ref_mode; mod return_value; mod signal; mod signal_body; mod special_functions; mod sys; mod trait_impls; mod trampoline; mod trampoline_from_glib; mod visibility; pub use visibility::Visibility; mod trampoline_to_glib; pub mod translate_from_glib; pub mod translate_to_glib; pub fn generate(env: &Env) { match env.config.work_mode { WorkMode::Normal => normal_generate(env), WorkMode::Sys => sys::generate(env), WorkMode::Doc => doc::generate(env), WorkMode::DisplayNotBound => {} } } fn normal_generate(env: &Env) { let mut mod_rs: Vec = Vec::new(); let mut traits: Vec = Vec::new(); let mut builders: Vec = Vec::new(); let root_path = env.config.auto_path.as_path(); generate_single_version_file(env); objects::generate(env, root_path, &mut mod_rs, &mut traits, &mut builders); records::generate(env, root_path, &mut mod_rs); enums::generate(env, root_path, &mut mod_rs); flags::generate(env, root_path, &mut mod_rs); alias::generate(env, root_path, &mut mod_rs); functions::generate(env, root_path, &mut mod_rs); constants::generate(env, root_path, &mut mod_rs); generate_mod_rs(env, root_path, &mod_rs, &traits, &builders); } pub fn generate_mod_rs( env: &Env, root_path: &Path, mod_rs: &[String], traits: &[String], builders: &[String], ) { let path = root_path.join("mod.rs"); save_to_file(path, env.config.make_backup, |w| { general::start_comments(w, &env.config)?; general::write_vec(w, mod_rs)?; writeln!(w)?; if !traits.is_empty() { writeln!(w, "pub(crate) mod traits {{")?; general::write_vec(w, traits)?; writeln!(w, "}}")?; } if !builders.is_empty() { writeln!(w, "pub(crate) mod builders {{")?; general::write_vec(w, builders)?; writeln!(w, "}}")?; } Ok(()) }); } pub fn generate_single_version_file(env: &Env) { if let Some(ref path) = env.config.single_version_file { save_to_file(path, env.config.make_backup, |w| { general::single_version_file(w, &env.config, "") }); } } pub fn generate_default_impl< 'a, D: Display, F: Fn(&'a Member) -> Option<(Option, Option<&'a String>, D)>, >( w: &mut dyn Write, env: &Env, config: &GObject, type_name: &str, type_version: Option, mut members: impl Iterator, callback: F, ) -> Result<()> { if let Some(ref default_value) = config.default_value { let member = match members.find(|m| m.name == *default_value) { Some(m) => m, None => { log::error!( "type `{}` doesn't have a member named `{}`. Not generating default impl.", type_name, default_value, ); return Ok(()); } }; let (version, cfg_cond, member_name) = match callback(member) { Some(n) => n, None => { log::error!( "member `{}` on type `{}` isn't generated so no default impl.", default_value, type_name, ); return Ok(()); } }; // First we generate the type cfg. version_condition(w, env, None, type_version, false, 0)?; cfg_condition(w, config.cfg_condition.as_ref(), false, 0)?; // Then we generate the member cfg. version_condition(w, env, None, version, false, 0)?; cfg_condition(w, cfg_cond, false, 0)?; writeln!( w, "\n\ impl Default for {type_name} {{\n\ \tfn default() -> Self {{\n\ \t\tSelf::{member_name}\n\ \t}}\n\ }}\n", ) } else { Ok(()) } } gir-0.20.5/src/codegen/object.rs000066400000000000000000000464561475434152100164130ustar00rootroot00000000000000use std::{ collections::{BTreeMap, HashSet}, io::{Result, Write}, }; use super::{ child_properties, function, general, general::{ cfg_deprecated_string, not_version_condition_no_docsrs, version_condition, version_condition_no_doc, version_condition_string, }, properties, signal, trait_impls, }; use crate::{ analysis::{ self, bounds::BoundType, object::has_builder_properties, record_type::RecordType, ref_mode::RefMode, rust_type::RustType, safety_assertion_mode::SafetyAssertionMode, }, env::Env, library::{self, Nullable}, nameutil, traits::IntoString, }; pub fn generate(w: &mut dyn Write, env: &Env, analysis: &analysis::object::Info) -> Result<()> { general::start_comments(w, &env.config)?; if analysis .functions .iter() .any(|f| f.deprecated_version.is_some()) { writeln!(w, "#![allow(deprecated)]")?; } general::uses(w, env, &analysis.imports, analysis.version)?; let config = &env.config.objects[&analysis.full_name]; if config.default_value.is_some() { log::error!( "`default_value` can only be used on flags and enums. {} is neither. Ignoring \ `default_value`.", analysis.name, ); } // Collect all supertypes that were added at a later time. The `glib::wrapper!` // call needs to be done multiple times with different `#[cfg]` directives // if there is a difference. let mut namespaces = Vec::new(); for p in &analysis.supertypes { use crate::library::*; let mut versions = BTreeMap::new(); match *env.library.type_(p.type_id) { Type::Interface(Interface { .. }) | Type::Class(Class { .. }) if !p.status.ignored() => { let full_name = p.type_id.full_name(&env.library); // TODO: Might want to add a configuration on the object to override this per // supertype in case the supertype existed in older versions but newly became on // for this very type. if let Some(object) = env.analysis.objects.get(&full_name) { let parent_version = object.version; let namespace_min_version = env .config .min_required_version(env, Some(object.type_id.ns_id)); if parent_version > analysis.version && parent_version > namespace_min_version { versions .entry(parent_version) .and_modify(|t: &mut Vec<_>| t.push(p)) .or_insert_with(|| vec![p]); if !versions.is_empty() { namespaces.push((p.type_id.ns_id, versions)); } } } } _ => continue, } } if namespaces.is_empty() || analysis.is_fundamental { writeln!(w)?; if analysis.is_fundamental { general::define_fundamental_type( w, env, &analysis.name, &analysis.c_type, &analysis.get_type, analysis.ref_fn.as_deref(), analysis.unref_fn.as_deref(), &analysis.supertypes, analysis.visibility, analysis.type_id, )?; } else { general::define_object_type( w, env, &analysis.name, &analysis.c_type, analysis.c_class_type.as_deref(), &analysis.get_type, analysis.is_interface, &analysis.supertypes, analysis.visibility, analysis.type_id, )?; } } else { // Write the `glib::wrapper!` calls from the highest version to the lowest and // remember which supertypes have to be removed for the next call. let mut remove_types: HashSet = HashSet::new(); let mut previous_version = None; let mut previous_ns_id = None; for (ns_id, versions) in &namespaces { for (&version, stypes) in versions.iter().rev() { let supertypes = analysis .supertypes .iter() .filter(|t| !remove_types.contains(&t.type_id)) .cloned() .collect::>(); writeln!(w)?; if previous_version.is_some() { not_version_condition_no_docsrs( w, env, Some(*ns_id), previous_version, false, 0, )?; version_condition_no_doc(w, env, Some(*ns_id), version, false, 0)?; } else { version_condition(w, env, Some(*ns_id), version, false, 0)?; } general::define_object_type( w, env, &analysis.name, &analysis.c_type, analysis.c_class_type.as_deref(), &analysis.get_type, analysis.is_interface, &supertypes, analysis.visibility, analysis.type_id, )?; for t in stypes { remove_types.insert(t.type_id); } previous_ns_id = Some(*ns_id); previous_version = version; } } // Write the base `glib::wrapper!`. let supertypes = analysis .supertypes .iter() .filter(|t| !remove_types.contains(&t.type_id)) .cloned() .collect::>(); writeln!(w)?; not_version_condition_no_docsrs(w, env, previous_ns_id, previous_version, false, 0)?; general::define_object_type( w, env, &analysis.name, &analysis.c_type, analysis.c_class_type.as_deref(), &analysis.get_type, analysis.is_interface, &supertypes, analysis.visibility, analysis.type_id, )?; } if (analysis.need_generate_inherent() && analysis.should_generate_impl_block()) || !analysis.final_type { writeln!(w)?; write!(w, "impl {} {{", analysis.name)?; if !analysis.final_type { writeln!( w, " pub const NONE: Option<&'static {}> = None; ", analysis.name )?; } for func_analysis in &analysis.constructors() { function::generate( w, env, Some(analysis.type_id), func_analysis, Some(&analysis.specials), analysis.version, false, false, 1, )?; } if has_builder_properties(&analysis.builder_properties) { // generate builder method that returns the corresponding builder let builder_name = format!("{}Builder", analysis.name); writeln!( w, " // rustdoc-stripper-ignore-next /// Creates a new builder-pattern struct instance to construct [`{name}`] objects. /// /// This method returns an instance of [`{builder_name}`](crate::builders::{builder_name}) which can be used to create [`{name}`] objects. pub fn builder() -> {builder_name} {{ {builder_name}::new() }} ", name = analysis.name, builder_name = builder_name )?; } if !analysis.need_generate_trait() { for func_analysis in &analysis.methods() { function::generate( w, env, Some(analysis.type_id), func_analysis, Some(&analysis.specials), analysis.version, false, false, 1, )?; } for property in &analysis.properties { properties::generate(w, env, property, false, false, 1)?; } for child_property in &analysis.child_properties { child_properties::generate(w, env, child_property, false, false, 1)?; } } for func_analysis in &analysis.functions() { function::generate( w, env, Some(analysis.type_id), func_analysis, Some(&analysis.specials), analysis.version, false, false, 1, )?; } if !analysis.need_generate_trait() { for signal_analysis in analysis .signals .iter() .chain(analysis.notify_signals.iter()) { signal::generate(w, env, signal_analysis, false, false, 1)?; } } writeln!(w, "}}")?; general::declare_default_from_new( w, env, &analysis.name, &analysis.functions, has_builder_properties(&analysis.builder_properties), )?; } trait_impls::generate( w, env, &analysis.name, &analysis.functions, &analysis.specials, if analysis.need_generate_trait() { Some(&analysis.trait_name) } else { None }, analysis.version, None, // There is no need for #[cfg()] since it's applied on the whole file. )?; if has_builder_properties(&analysis.builder_properties) { writeln!(w)?; generate_builder(w, env, analysis)?; } if analysis.concurrency != library::Concurrency::None { writeln!(w)?; } match analysis.concurrency { library::Concurrency::Send | library::Concurrency::SendSync => { writeln!(w, "unsafe impl Send for {} {{}}", analysis.name)?; } _ => (), } if let library::Concurrency::SendSync = analysis.concurrency { writeln!(w, "unsafe impl Sync for {} {{}}", analysis.name)?; } if analysis.need_generate_trait() { writeln!(w)?; generate_trait(w, env, analysis)?; } Ok(()) } fn generate_builder(w: &mut dyn Write, env: &Env, analysis: &analysis::object::Info) -> Result<()> { let glib_crate_name = if env.namespaces.is_glib_crate { "crate" } else { "glib" }; writeln!( w, "// rustdoc-stripper-ignore-next /// A [builder-pattern] type to construct [`{}`] objects. /// /// [builder-pattern]: https://doc.rust-lang.org/1.0.0/style/ownership/builders.html", analysis.name, )?; writeln!(w, "#[must_use = \"The builder must be built to be used\"]")?; writeln!( w, "pub struct {name}Builder {{ builder: {glib_name}::object::ObjectBuilder<'static, {name}>, }} impl {name}Builder {{ fn new() -> Self {{ Self {{ builder: {glib_name}::object::Object::builder() }} }}", name = analysis.name, glib_name = glib_crate_name, )?; for (builder_props, super_tid) in &analysis.builder_properties { for property in builder_props { let direction = if property.is_get { library::ParameterDirection::In } else { library::ParameterDirection::Out }; let param_type = RustType::builder(env, property.typ) .direction(direction) .ref_mode(property.set_in_ref_mode) .try_build(); let comment_prefix = if param_type.is_err() { "//" } else { "" }; let mut param_type_str = param_type.into_string(); let (param_type_override, bounds, conversion) = match param_type_str.as_str() { "&str" => ( Some(format!("impl Into<{glib_crate_name}::GString>")), String::new(), ".into()", ), "&[&str]" => ( Some(format!("impl Into<{glib_crate_name}::StrV>")), String::from(""), ".into()", ), _ if !property.bounds.is_empty() => { let (bounds, _) = function::bounds(&property.bounds, &[], false, false); let param_bound = property.bounds.get_parameter_bound(&property.name); let alias = param_bound.map(|bound| { bound.full_type_parameter_reference(RefMode::ByRef, Nullable(false), false) }); let conversion = param_bound.and_then(|bound| match bound.bound_type { BoundType::AsRef(_) => Some(".as_ref().clone()"), _ => None, }); (alias, bounds, conversion.unwrap_or(".clone().upcast()")) } typ if typ.starts_with('&') => { let should_clone = if let crate::library::Type::Record(record) = env.type_(property.typ) { match RecordType::of(record) { RecordType::Boxed => "", RecordType::AutoBoxed => { if !record.has_copy() { "" } else { ".clone()" } } _ => ".clone()", } } else { ".clone()" }; (None, String::new(), should_clone) } _ => (None, String::new(), ""), }; if let Some(param_type_override) = param_type_override { param_type_str = param_type_override.to_string(); } let name = nameutil::mangle_keywords(nameutil::signal_to_snake(&property.name)); let version_condition_string = version_condition_string(env, Some(super_tid.ns_id), property.version, false, 1); let deprecated_string = cfg_deprecated_string(env, Some(*super_tid), property.deprecated_version, false, 1); let version_prefix = version_condition_string .map(|version| format!("{comment_prefix}{version}\n")) .unwrap_or_default(); let deprecation_prefix = deprecated_string .map(|version| format!("{comment_prefix}{version}\n")) .unwrap_or_default(); writeln!( w, " {version_prefix}{deprecation_prefix} {comment_prefix}pub fn {name}{bounds}(self, {name}: {param_type_str}) -> Self {{ {comment_prefix} Self {{ builder: self.builder.property(\"{property_name}\", {name}{conversion}), }} {comment_prefix}}}", property_name = property.name, )?; } } writeln!( w, " // rustdoc-stripper-ignore-next /// Build the [`{name}`]. #[must_use = \"Building the object from the builder is usually expensive and is not expected to have side effects\"] pub fn build(self) -> {name} {{", name = analysis.name, )?; // The split allows us to not have clippy::let_and_return lint disabled if let Some(code) = analysis.builder_postprocess.as_ref() { // We don't generate an assertion macro for the case where you have a build post-process // as it is only used to initialize gtk in gtk::ApplicationBuilder which is too early to assert anything writeln!(w, " let ret = self.builder.build();")?; writeln!(w, " {{\n {code}\n }}")?; writeln!(w, " ret\n }}")?; } else { if env.config.generate_safety_asserts { writeln!(w, "{}", SafetyAssertionMode::InMainThread)?; } writeln!(w, " self.builder.build() }}")?; } writeln!(w, "}}") } fn generate_trait(w: &mut dyn Write, env: &Env, analysis: &analysis::object::Info) -> Result<()> { write!( w, "mod sealed {{ pub trait Sealed {{}} impl> Sealed for T {{}} }} pub trait {}: IsA<{}> + sealed::Sealed + 'static {{", analysis.trait_name, analysis.name )?; for func_analysis in &analysis.methods() { function::generate( w, env, Some(analysis.type_id), func_analysis, Some(&analysis.specials), analysis.version, true, false, 1, )?; } for property in &analysis.properties { properties::generate(w, env, property, true, false, 1)?; } for child_property in &analysis.child_properties { child_properties::generate(w, env, child_property, true, false, 1)?; } for signal_analysis in analysis .signals .iter() .chain(analysis.notify_signals.iter()) { signal::generate(w, env, signal_analysis, true, false, 1)?; } writeln!(w, "}}")?; writeln!(w)?; writeln!( w, "impl> {} for O {{}}", analysis.name, analysis.trait_name, )?; Ok(()) } pub fn generate_reexports( env: &Env, analysis: &analysis::object::Info, module_name: &str, contents: &mut Vec, traits: &mut Vec, builders: &mut Vec, ) { let mut cfgs: Vec = Vec::new(); if let Some(cfg) = general::cfg_condition_string(analysis.cfg_condition.as_ref(), false, 0) { cfgs.push(cfg); } if let Some(cfg) = general::version_condition_string(env, None, analysis.version, false, 0) { cfgs.push(cfg); } if let Some(cfg) = general::cfg_deprecated_string( env, Some(analysis.type_id), analysis.deprecated_version, false, 0, ) { cfgs.push(cfg); } contents.push(String::new()); contents.extend_from_slice(&cfgs); contents.push(format!("mod {module_name};")); contents.extend_from_slice(&cfgs); contents.push(format!( "{} use self::{}::{};", analysis.visibility.export_visibility(), module_name, analysis.name, )); if analysis.need_generate_trait() { for cfg in &cfgs { traits.push(format!("\t{cfg}")); } traits.push(format!( "\tpub use super::{}::{};", module_name, analysis.trait_name )); } if has_builder_properties(&analysis.builder_properties) { for cfg in &cfgs { builders.push(format!("\t{cfg}")); } builders.push(format!( "\tpub use super::{}::{}Builder;", module_name, analysis.name )); } } gir-0.20.5/src/codegen/objects.rs000066400000000000000000000017601475434152100165630ustar00rootroot00000000000000use std::path::Path; use log::info; use crate::{env::Env, file_saver::*, nameutil::*}; pub fn generate( env: &Env, root_path: &Path, mod_rs: &mut Vec, traits: &mut Vec, builders: &mut Vec, ) { info!("Generate objects"); for class_analysis in env.analysis.objects.values() { let obj = &env.config.objects[&class_analysis.full_name]; if !obj.status.need_generate() { continue; } let mod_name = obj .module_name .clone() .unwrap_or_else(|| module_name(split_namespace_name(&class_analysis.full_name).1)); let mut path = root_path.join(&mod_name); path.set_extension("rs"); info!("Generating file {:?}", path); save_to_file(path, env.config.make_backup, |w| { super::object::generate(w, env, class_analysis) }); super::object::generate_reexports(env, class_analysis, &mod_name, mod_rs, traits, builders); } } gir-0.20.5/src/codegen/parameter.rs000066400000000000000000000031271475434152100171110ustar00rootroot00000000000000use crate::{ analysis::{ bounds::Bounds, conversion_type::ConversionType, function_parameters::CParameter, ref_mode::RefMode, rust_type::RustType, }, env::Env, traits::*, }; pub trait ToParameter { fn to_parameter(&self, env: &Env, bounds: &Bounds, r#async: bool) -> String; } impl ToParameter for CParameter { fn to_parameter(&self, env: &Env, bounds: &Bounds, r#async: bool) -> String { let ref_mode = if self.move_ { RefMode::None } else { self.ref_mode }; if self.instance_parameter { format!("{}self", ref_mode.for_rust_type()) } else { let type_str = match bounds.get_parameter_bound(&self.name) { Some(bound) => { bound.full_type_parameter_reference(ref_mode, self.nullable, r#async) } None => { let type_name = RustType::builder(env, self.typ) .direction(self.direction) .nullable(self.nullable) .ref_mode(ref_mode) .scope(self.scope) .try_from_glib(&self.try_from_glib) .try_build_param() .into_string(); match ConversionType::of(env, self.typ) { ConversionType::Unknown => format!("/*Unknown conversion*/{type_name}"), _ => type_name, } } }; format!("{}: {}", self.name, type_str) } } } gir-0.20.5/src/codegen/properties.rs000066400000000000000000000070231475434152100173240ustar00rootroot00000000000000use std::io::{Result, Write}; use super::{ general::{cfg_deprecated, doc_alias, version_condition}, property_body, }; use crate::{ analysis::{properties::Property, rust_type::RustType}, chunk::Chunk, env::Env, library, traits::IntoString, writer::{primitives::tabs, ToCode}, }; pub fn generate( w: &mut dyn Write, env: &Env, prop: &Property, in_trait: bool, only_declaration: bool, indent: usize, ) -> Result<()> { generate_prop_func(w, env, prop, in_trait, only_declaration, indent)?; Ok(()) } fn generate_prop_func( w: &mut dyn Write, env: &Env, prop: &Property, in_trait: bool, only_declaration: bool, indent: usize, ) -> Result<()> { let pub_prefix = if in_trait { "" } else { "pub " }; let decl_suffix = if only_declaration { ";" } else { " {" }; let commented = RustType::try_new(env, prop.typ).is_err(); let comment_prefix = if commented { "//" } else { "" }; writeln!(w)?; let decl = declaration(env, prop); cfg_deprecated( w, env, Some(prop.typ), prop.deprecated_version, commented, indent, )?; version_condition(w, env, None, prop.version, commented, indent)?; let add_doc_alias = if let Some(func_name_alias) = prop.func_name_alias.as_ref() { &prop.name != func_name_alias && prop.name != prop.var_name } else { prop.name != prop.var_name }; if add_doc_alias { doc_alias(w, &prop.name, comment_prefix, indent)?; } writeln!( w, "{}{}{}{}{}", tabs(indent), comment_prefix, pub_prefix, decl, decl_suffix )?; if !only_declaration { let body = body(env, prop, in_trait).to_code(env); for s in body { writeln!(w, "{}{}{}", tabs(indent), comment_prefix, s)?; } } Ok(()) } fn declaration(env: &Env, prop: &Property) -> String { let bound: String; let set_param = if prop.is_get { bound = String::new(); String::new() } else if let Some(ref set_bound) = prop.set_bound { bound = format!("<{}: IsA<{}>>", set_bound.alias, set_bound.type_str); format!(", {}: Option<&{}>", prop.var_name, set_bound.alias) } else { bound = String::new(); let dir = library::ParameterDirection::In; let param_type = RustType::builder(env, prop.typ) .direction(dir) .nullable(prop.nullable) .ref_mode(prop.set_in_ref_mode) .try_build_param() .into_string(); format!(", {}: {}", prop.var_name, param_type) }; let return_str = if prop.is_get { let dir = library::ParameterDirection::Return; let ret_type = RustType::builder(env, prop.typ) .direction(dir) .nullable(prop.nullable) .ref_mode(prop.get_out_ref_mode) .try_build_param() .into_string(); format!(" -> {ret_type}") } else { String::new() }; format!( "fn {}{}(&self{}){}", prop.func_name, bound, set_param, return_str ) } fn body(env: &Env, prop: &Property, in_trait: bool) -> Chunk { let mut builder = property_body::Builder::new(env); builder .name(&prop.name) .in_trait(in_trait) .var_name(&prop.var_name) .is_get(prop.is_get); if let Ok(type_) = RustType::try_new(env, prop.typ) { builder.type_(type_.as_str()); } else { builder.type_("/*Unknown type*/"); } builder.generate() } gir-0.20.5/src/codegen/property_body.rs000066400000000000000000000063631475434152100200370ustar00rootroot00000000000000use crate::{chunk::Chunk, env::Env, nameutil::use_gtk_type}; pub struct Builder<'a> { name: String, in_trait: bool, var_name: String, is_get: bool, is_child_property: bool, type_: String, env: &'a Env, } impl<'a> Builder<'a> { pub fn new(env: &'a Env) -> Self { Self { env, name: Default::default(), in_trait: Default::default(), var_name: Default::default(), is_get: Default::default(), is_child_property: Default::default(), type_: Default::default(), } } pub fn new_for_child_property(env: &'a Env) -> Self { Self { is_child_property: true, env, name: Default::default(), in_trait: Default::default(), var_name: Default::default(), is_get: Default::default(), type_: Default::default(), } } pub fn name(&mut self, name: &str) -> &mut Self { self.name = name.into(); self } pub fn in_trait(&mut self, value: bool) -> &mut Self { self.in_trait = value; self } pub fn var_name(&mut self, name: &str) -> &mut Self { self.var_name = name.into(); self } pub fn is_get(&mut self, value: bool) -> &mut Self { self.is_get = value; self } pub fn type_(&mut self, type_: &str) -> &mut Self { self.type_ = type_.into(); self } pub fn generate(&self) -> Chunk { let chunks = if self.is_get { self.chunks_for_get() } else { self.chunks_for_set() }; Chunk::BlockHalf(chunks) } fn chunks_for_get(&self) -> Vec { if self.is_child_property { let self_ = if self.in_trait { "self.as_ref()" } else { "self" }; vec![Chunk::Custom(format!( "{}::child_property({}, &item.clone().upcast(),\"{}\")", use_gtk_type(self.env, "prelude::ContainerExtManual"), self_, self.name, ))] } else { let self_ = if self.in_trait { "self.as_ref()" } else { "self" }; vec![Chunk::Custom(format!( "ObjectExt::property({}, \"{}\")", self_, self.name ))] } } fn chunks_for_set(&self) -> Vec { if self.is_child_property { let self_ = if self.in_trait { "self.as_ref()" } else { "self" }; vec![Chunk::Custom(format!( "{}::child_set_property({}, &item.clone().upcast(),\"{}\", &{})", use_gtk_type(self.env, "prelude::ContainerExtManual"), self_, self.name, self.var_name ))] } else { let self_ = if self.in_trait { "self.as_ref()" } else { "self" }; vec![Chunk::Custom(format!( "ObjectExt::set_property({},\"{}\", {})", self_, self.name, self.var_name ))] } } } gir-0.20.5/src/codegen/record.rs000066400000000000000000000122731475434152100164110ustar00rootroot00000000000000use std::io::{Result, Write}; use super::{function, general, trait_impls}; use crate::{ analysis::{self, record_type::RecordType, special_functions::Type}, env::Env, library, traits::MaybeRef, }; pub fn generate(w: &mut dyn Write, env: &Env, analysis: &analysis::record::Info) -> Result<()> { let type_ = analysis.type_(&env.library); general::start_comments(w, &env.config)?; general::uses(w, env, &analysis.imports, type_.version)?; if RecordType::of(env.type_(analysis.type_id).maybe_ref().unwrap()) == RecordType::AutoBoxed { if let Some((ref glib_get_type, _)) = analysis.glib_get_type { general::define_auto_boxed_type( w, env, &analysis.name, &type_.c_type, analysis.boxed_inline, &analysis.init_function_expression, &analysis.copy_into_function_expression, &analysis.clear_function_expression, glib_get_type, &analysis.derives, analysis.visibility, analysis.type_id, )?; } else { panic!( "Record {} has record_boxed=true but don't have glib:get_type function", analysis.name ); } } else if let (Some(ref_fn), Some(unref_fn)) = ( analysis.specials.traits().get(&Type::Ref), analysis.specials.traits().get(&Type::Unref), ) { general::define_shared_type( w, env, &analysis.name, &type_.c_type, &ref_fn.glib_name, &unref_fn.glib_name, analysis.glib_get_type.as_ref().map(|(f, v)| { if v > &analysis.version { (f.clone(), *v) } else { (f.clone(), None) } }), &analysis.derives, analysis.visibility, analysis.type_id, )?; } else if let (Some(copy_fn), Some(free_fn)) = ( analysis.specials.traits().get(&Type::Copy), analysis.specials.traits().get(&Type::Free), ) { general::define_boxed_type( w, env, &analysis.name, &type_.c_type, copy_fn, &free_fn.glib_name, analysis.boxed_inline, &analysis.init_function_expression, &analysis.copy_into_function_expression, &analysis.clear_function_expression, analysis.glib_get_type.as_ref().map(|(f, v)| { if v > &analysis.version { (f.clone(), *v) } else { (f.clone(), None) } }), &analysis.derives, analysis.visibility, analysis.type_id, )?; } else { panic!( "Missing memory management functions for {}", analysis.full_name ); } if analysis .functions .iter() .any(|f| f.status.need_generate() && !f.hidden) { writeln!(w)?; write!(w, "impl {} {{", analysis.name)?; for func_analysis in &analysis.functions { function::generate( w, env, Some(analysis.type_id), func_analysis, Some(&analysis.specials), analysis.version, false, false, 1, )?; } writeln!(w, "}}")?; } general::declare_default_from_new(w, env, &analysis.name, &analysis.functions, false)?; trait_impls::generate( w, env, &analysis.name, &analysis.functions, &analysis.specials, None, analysis.version, None, // There is no need for #[cfg()] since it's applied on the whole file. )?; if analysis.concurrency != library::Concurrency::None { writeln!(w)?; } match analysis.concurrency { library::Concurrency::Send | library::Concurrency::SendSync => { writeln!(w, "unsafe impl Send for {} {{}}", analysis.name)?; } _ => (), } if analysis.concurrency == library::Concurrency::SendSync { writeln!(w, "unsafe impl Sync for {} {{}}", analysis.name)?; } Ok(()) } pub fn generate_reexports( env: &Env, analysis: &analysis::record::Info, module_name: &str, contents: &mut Vec, ) { let cfg_condition = general::cfg_condition_string(analysis.cfg_condition.as_ref(), false, 0); let version_cfg = general::version_condition_string( env, Some(analysis.type_id.ns_id), analysis.version, false, 0, ); let mut cfg = String::new(); if let Some(s) = cfg_condition { cfg.push_str(&s); cfg.push('\n'); }; if let Some(s) = version_cfg { cfg.push_str(&s); cfg.push('\n'); }; contents.push(String::new()); contents.push(format!("{cfg}mod {module_name};")); contents.push(format!( "{}{} use self::{}::{};", cfg, analysis.visibility.export_visibility(), module_name, analysis.name )); } gir-0.20.5/src/codegen/records.rs000066400000000000000000000016261475434152100165740ustar00rootroot00000000000000use std::path::Path; use log::info; use crate::{env::Env, file_saver::*, nameutil::*}; pub fn generate(env: &Env, root_path: &Path, mod_rs: &mut Vec) { info!("Generate records"); for record_analysis in env.analysis.records.values() { let obj = &env.config.objects[&record_analysis.full_name]; if !obj.status.need_generate() { continue; } let mod_name = obj .module_name .clone() .unwrap_or_else(|| module_name(split_namespace_name(&record_analysis.full_name).1)); let mut path = root_path.join(&mod_name); path.set_extension("rs"); info!("Generating file {:?}", path); save_to_file(path, env.config.make_backup, |w| { super::record::generate(w, env, record_analysis) }); super::record::generate_reexports(env, record_analysis, &mod_name, mod_rs); } } gir-0.20.5/src/codegen/ref_mode.rs000066400000000000000000000005061475434152100167070ustar00rootroot00000000000000use crate::analysis::ref_mode::RefMode; impl RefMode { pub(crate) fn for_rust_type(self) -> &'static str { match self { RefMode::None | RefMode::ByRefFake => "", RefMode::ByRef | RefMode::ByRefImmut | RefMode::ByRefConst => "&", RefMode::ByRefMut => "&mut ", } } } gir-0.20.5/src/codegen/return_value.rs000066400000000000000000000222641475434152100176470ustar00rootroot00000000000000use std::cmp; use crate::{ analysis::{ self, conversion_type::ConversionType, namespaces, out_parameters::Mode, rust_type::RustType, try_from_glib::TryFromGlib, }, env::Env, library::{self, ParameterDirection, TypeId}, nameutil::{is_gstring, mangle_keywords, use_glib_type}, traits::*, }; pub trait ToReturnValue { fn to_return_value( &self, env: &Env, try_from_glib: &TryFromGlib, is_trampoline: bool, ) -> Option; } impl ToReturnValue for library::Parameter { fn to_return_value( &self, env: &Env, try_from_glib: &TryFromGlib, is_trampoline: bool, ) -> Option { let mut name = RustType::builder(env, self.typ) .direction(self.direction) .nullable(self.nullable) .scope(self.scope) .try_from_glib(try_from_glib) .try_build_param() .into_string(); if is_trampoline && self.direction == library::ParameterDirection::Return && is_gstring(&name) { name = "String".to_owned(); } let type_str = match ConversionType::of(env, self.typ) { ConversionType::Unknown => format!("/*Unknown conversion*/{name}"), // TODO: records as in gtk_container_get_path_for_child _ => name, }; Some(type_str) } } impl ToReturnValue for analysis::return_value::Info { fn to_return_value( &self, env: &Env, try_from_glib: &TryFromGlib, is_trampoline: bool, ) -> Option { let par = self.parameter.as_ref()?; par.lib_par .to_return_value(env, try_from_glib, is_trampoline) .map(|type_name| { if self.nullable_return_is_error.is_some() && type_name.starts_with("Option<") { // Change `Option` to `Result` format!( "Result<{}, {}BoolError>", &type_name[7..(type_name.len() - 1)], if env.namespaces.glib_ns_id == namespaces::MAIN { "" } else { "glib::" } ) } else { type_name } }) } } /// Returns the `TypeId` of the returned types from the provided function. pub fn out_parameter_types(analysis: &analysis::functions::Info) -> Vec { // If it returns an error, there is nothing for us to check. if analysis.ret.bool_return_is_error.is_some() || analysis.ret.nullable_return_is_error.is_some() { return Vec::new(); } if !analysis.outs.is_empty() { let num_out_args = analysis .outs .iter() .filter(|out| out.lib_par.array_length.is_none()) .count(); let num_out_sizes = analysis .outs .iter() .filter(|out| out.lib_par.array_length.is_some()) .count(); // We need to differentiate between array(s)'s size arguments and normal ones. // If we have 2 "normal" arguments and one "size" argument, we still // need to wrap them into "()" so we take that into account. If the // opposite, it means that there are two arguments in any case so // we need "()" too. let num_outs = std::cmp::max(num_out_args, num_out_sizes); match analysis.outs.mode { Mode::Normal | Mode::Combined => { let array_lengths: Vec<_> = analysis .outs .iter() .filter_map(|out| out.lib_par.array_length) .collect(); let mut ret_params = Vec::with_capacity(num_outs); for out in analysis.outs.iter().filter(|out| !out.lib_par.is_error) { // The actual return value is inserted with an empty name at position 0 if !out.lib_par.name.is_empty() { let mangled_par_name = crate::nameutil::mangle_keywords(out.lib_par.name.as_str()); let param_pos = analysis .parameters .c_parameters .iter() .enumerate() .find_map(|(pos, orig_par)| { if orig_par.name == mangled_par_name { Some(pos) } else { None } }) .unwrap(); if array_lengths.contains(&(param_pos as u32)) { continue; } } ret_params.push(out.lib_par.typ); } ret_params } _ => Vec::new(), } } else if let Some(typ) = analysis.ret.parameter.as_ref().map(|out| out.lib_par.typ) { vec![typ] } else { Vec::new() } } fn out_parameter_as_return_parts( analysis: &analysis::functions::Info, env: &Env, ) -> (&'static str, String) { use crate::analysis::out_parameters::Mode::*; let num_out_args = analysis .outs .iter() .filter(|out| out.lib_par.array_length.is_none()) .count(); let num_out_sizes = analysis .outs .iter() .filter(|out| out.lib_par.array_length.is_some()) .count(); // We need to differentiate between array(s)'s size arguments and normal ones. // If we have 2 "normal" arguments and one "size" argument, we still need to // wrap them into "()" so we take that into account. If the opposite, it // means that there are two arguments in any case so we need "()" too. let num_outs = cmp::max(num_out_args, num_out_sizes); match analysis.outs.mode { Normal | Combined => { if num_outs > 1 { ("(", ")".to_owned()) } else { ("", String::new()) } } Optional => { if num_outs > 1 { if analysis.ret.nullable_return_is_error.is_some() { ( "Result<(", format!("), {}>", use_glib_type(env, "BoolError")), ) } else { ("Option<(", ")>".to_owned()) } } else if analysis.ret.nullable_return_is_error.is_some() { ("Result<", format!(", {}>", use_glib_type(env, "BoolError"))) } else { ("Option<", ">".to_owned()) } } Throws(..) => { if num_outs == 1 + 1 { // if only one parameter except "glib::Error" ("Result<", format!(", {}>", use_glib_type(env, "Error"))) } else { ("Result<(", format!("), {}>", use_glib_type(env, "Error"))) } } None => unreachable!(), } } pub fn out_parameters_as_return(env: &Env, analysis: &analysis::functions::Info) -> String { let (prefix, suffix) = out_parameter_as_return_parts(analysis, env); let mut return_str = String::with_capacity(100); return_str.push_str(" -> "); return_str.push_str(prefix); let array_lengths: Vec<_> = analysis .outs .iter() .filter_map(|out| out.lib_par.array_length) .collect(); let mut skip = 0; for (pos, out) in analysis .outs .iter() .filter(|out| !out.lib_par.is_error) .enumerate() { // The actual return value is inserted with an empty name at position 0 if !out.lib_par.name.is_empty() { let mangled_par_name = mangle_keywords(out.lib_par.name.as_str()); let param_pos = analysis .parameters .c_parameters .iter() .enumerate() .find_map(|(pos, orig_par)| { if orig_par.name == mangled_par_name { Some(pos) } else { None } }) .unwrap(); if array_lengths.contains(&(param_pos as u32)) { skip += 1; continue; } } if pos > skip { return_str.push_str(", "); } let s = out_parameter_as_return(out, env); return_str.push_str(&s); } return_str.push_str(&suffix); return_str } fn out_parameter_as_return(out: &analysis::Parameter, env: &Env) -> String { // TODO: upcasts? let name = RustType::builder(env, out.lib_par.typ) .direction(ParameterDirection::Return) .nullable(out.lib_par.nullable) .scope(out.lib_par.scope) .try_from_glib(&out.try_from_glib) .try_build_param() .into_string(); match ConversionType::of(env, out.lib_par.typ) { ConversionType::Unknown => format!("/*Unknown conversion*/{name}"), _ => name, } } gir-0.20.5/src/codegen/signal.rs000066400000000000000000000137171475434152100164140ustar00rootroot00000000000000use std::io::{Result, Write}; use super::{ general::{cfg_deprecated, doc_alias, doc_hidden, version_condition}, signal_body, trampoline::{self, func_string}, }; use crate::{ analysis, chunk::Chunk, env::Env, writer::{primitives::tabs, ToCode}, }; pub fn generate( w: &mut dyn Write, env: &Env, analysis: &analysis::signals::Info, in_trait: bool, only_declaration: bool, indent: usize, ) -> Result<()> { let commented = analysis.trampoline.is_err(); let comment_prefix = if commented { "//" } else { "" }; let pub_prefix = if in_trait { "" } else { "pub " }; let function_type = function_type_string(env, analysis, true); let declaration = declaration(analysis, &function_type); let suffix = if only_declaration { ";" } else { " {" }; writeln!(w)?; cfg_deprecated(w, env, None, analysis.deprecated_version, commented, indent)?; version_condition(w, env, None, analysis.version, commented, indent)?; doc_hidden(w, analysis.doc_hidden, comment_prefix, indent)?; // Strip the "prefix" from "prefix::prop-name", if any. // Ex.: "notify::is-locked". doc_alias( w, analysis.signal_name.splitn(2, "::").last().unwrap(), comment_prefix, indent, )?; writeln!( w, "{}{}{}{}{}", tabs(indent), comment_prefix, pub_prefix, declaration, suffix )?; if !only_declaration { if !commented { if let Ok(ref trampoline) = analysis.trampoline { trampoline::generate(w, env, trampoline, in_trait, 2)?; } } match function_type { Some(_) => { let body = body(analysis, in_trait).to_code(env); for s in body { writeln!(w, "{}{}", tabs(indent), s)?; } } _ => { if let Err(ref errors) = analysis.trampoline { for error in errors { writeln!(w, "{}{}\t{}", tabs(indent), comment_prefix, error)?; } writeln!(w, "{}{}}}", tabs(indent), comment_prefix)?; } else { writeln!( w, "{}{}\tTODO: connect to trampoline\n{0}{1}}}", tabs(indent), comment_prefix )?; } } } } if function_type.is_none() { // Signal incomplete, can't generate emit return Ok(()); } if let Some(ref emit_name) = analysis.action_emit_name { writeln!(w)?; if !in_trait || only_declaration { cfg_deprecated(w, env, None, analysis.deprecated_version, commented, indent)?; } version_condition(w, env, None, analysis.version, commented, indent)?; let function_type = function_type_string(env, analysis, false); writeln!( w, "{}{}{}fn {}{}{}", tabs(indent), comment_prefix, pub_prefix, emit_name, function_type.unwrap(), suffix )?; if !only_declaration { let trampoline = analysis.trampoline.as_ref().unwrap_or_else(|_| { panic!( "Internal error: can't find trampoline for signal '{}'", analysis.signal_name, ) }); let mut args = String::with_capacity(100); for (pos, par) in trampoline.parameters.rust_parameters.iter().enumerate() { // Skip the self parameter if pos == 0 { continue; } if pos > 1 { args.push_str(", "); } args.push('&'); args.push_str(&par.name); } if trampoline.ret.typ != Default::default() { writeln!( w, "{}self.emit_by_name(\"{}\", &[{}])", tabs(indent + 1), analysis.signal_name, args, )?; } else { writeln!( w, "{}self.emit_by_name::<()>(\"{}\", &[{}]);", tabs(indent + 1), analysis.signal_name, args, )?; } writeln!(w, "{}}}", tabs(indent))?; } } Ok(()) } fn function_type_string( env: &Env, analysis: &analysis::signals::Info, closure: bool, ) -> Option { analysis.trampoline.as_ref().ok()?; let trampoline = analysis.trampoline.as_ref().unwrap_or_else(|_| { panic!( "Internal error: can't find trampoline for signal '{}'", analysis.signal_name ) }); let type_ = func_string( env, trampoline, Some(if closure { "Self" } else { "self" }), closure, ); Some(type_) } fn declaration(analysis: &analysis::signals::Info, function_type: &Option) -> String { let bounds = bounds(function_type); let param_str = if !analysis.is_detailed { "&self, f: F" } else { "&self, detail: Option<&str>, f: F" }; let return_str = " -> SignalHandlerId"; format!( "fn {}<{}>({}){}", analysis.connect_name, bounds, param_str, return_str ) } fn bounds(function_type: &Option) -> String { match function_type { Some(type_) => format!("F: {type_}"), _ => "Unsupported or ignored types".to_owned(), } } fn body(analysis: &analysis::signals::Info, in_trait: bool) -> Chunk { let mut builder = signal_body::Builder::new(); builder .signal_name(&analysis.signal_name) .trampoline_name(&analysis.trampoline.as_ref().unwrap().name) .in_trait(in_trait) .is_detailed(analysis.is_detailed); builder.generate() } gir-0.20.5/src/codegen/signal_body.rs000066400000000000000000000027351475434152100174270ustar00rootroot00000000000000use crate::chunk::Chunk; #[derive(Default)] pub struct Builder { signal_name: String, trampoline_name: String, in_trait: bool, is_detailed: bool, } impl Builder { pub fn new() -> Self { Default::default() } pub fn signal_name(&mut self, name: &str) -> &mut Self { self.signal_name = name.into(); self } pub fn trampoline_name(&mut self, name: &str) -> &mut Self { self.trampoline_name = name.into(); self } pub fn in_trait(&mut self, value: bool) -> &mut Self { self.in_trait = value; self } // https://github.com/rust-lang/rust-clippy/issues/8480 pub fn is_detailed(&mut self, value: bool) -> &mut Self { self.is_detailed = value; self } pub fn generate(&self) -> Chunk { let unsafe_ = Chunk::Unsafe(vec![self.let_func(), self.connect()]); Chunk::BlockHalf(vec![unsafe_]) } fn let_func(&self) -> Chunk { let type_ = "Box_".to_string(); Chunk::Let { name: "f".to_string(), is_mut: false, value: Box::new(Chunk::Custom("Box_::new(f)".to_owned())), type_: Some(Box::new(Chunk::Custom(type_))), } } fn connect(&self) -> Chunk { Chunk::Connect { signal: self.signal_name.clone(), trampoline: self.trampoline_name.clone(), in_trait: self.in_trait, is_detailed: self.is_detailed, } } } gir-0.20.5/src/codegen/special_functions.rs000066400000000000000000000027211475434152100206400ustar00rootroot00000000000000use std::io::{Result, Write}; use super::general::version_condition; use crate::{ analysis::{self, special_functions::FunctionType}, version::Version, Env, }; pub(super) fn generate( w: &mut dyn Write, env: &Env, function: &analysis::functions::Info, specials: &analysis::special_functions::Infos, scope_version: Option, ) -> Result { if let Some(special) = specials.functions().get(&function.glib_name) { match special.type_ { FunctionType::StaticStringify => { generate_static_to_str(w, env, function, scope_version) } } .map(|()| true) } else { Ok(false) } } pub(super) fn generate_static_to_str( w: &mut dyn Write, env: &Env, function: &analysis::functions::Info, scope_version: Option, ) -> Result<()> { writeln!(w)?; let version = Version::if_stricter_than(function.version, scope_version); version_condition(w, env, None, version, false, 1)?; writeln!( w, "\ \t{visibility} fn {rust_fn_name}<'a>(self) -> &'a GStr {{ \t\tunsafe {{ \t\t\tGStr::from_ptr( \t\t\t\t{ns}::{glib_fn_name}(self.into_glib()) \t\t\t\t\t.as_ref() \t\t\t\t\t.expect(\"{glib_fn_name} returned NULL\"), \t\t\t) \t\t}} \t}}", visibility = function.visibility, rust_fn_name = function.codegen_name(), ns = env.main_sys_crate_name(), glib_fn_name = function.glib_name, )?; Ok(()) } gir-0.20.5/src/codegen/sys/000077500000000000000000000000001475434152100153765ustar00rootroot00000000000000gir-0.20.5/src/codegen/sys/build.rs000066400000000000000000000044111475434152100170430ustar00rootroot00000000000000use std::io::{Result, Write}; use log::info; use super::collect_versions; use crate::{codegen::general, env::Env, file_saver::save_to_file}; pub fn generate(env: &Env) { info!( "Generating sys build script for {}", env.config.library_name ); let split_build_rs = env.config.split_build_rs; let path = env.config.target_path.join("build.rs"); if !split_build_rs || !path.exists() { info!("Generating file {:?}", path); save_to_file(&path, env.config.make_backup, |w| { generate_build_script(w, env, split_build_rs) }); } if split_build_rs { let path = env.config.target_path.join("build_version.rs"); info!("Generating file {:?}", path); save_to_file(&path, env.config.make_backup, |w| { generate_build_version(w, env) }); } } #[allow(clippy::write_literal)] fn generate_build_script(w: &mut dyn Write, env: &Env, split_build_rs: bool) -> Result<()> { if !split_build_rs { general::start_comments(w, &env.config)?; writeln!(w)?; } writeln!( w, "{}", r#"#[cfg(not(docsrs))] use std::process;"# )?; if split_build_rs { writeln!(w)?; writeln!(w, "mod build_version;")?; } write!( w, "{}", r#" #[cfg(docsrs)] fn main() {} // prevent linking libraries to avoid documentation failure #[cfg(not(docsrs))] fn main() { if let Err(s) = system_deps::Config::new().probe() { println!("cargo:warning={s}"); process::exit(1); } } "# ) } fn generate_build_version(w: &mut dyn Write, env: &Env) -> Result<()> { general::start_comments(w, &env.config)?; writeln!(w)?; writeln!(w, "pub fn version() -> &'static str {{")?; write_version(w, env, false)?; writeln!(w, "}}") } fn write_version(w: &mut dyn Write, env: &Env, for_let: bool) -> Result<()> { let versions = collect_versions(env); for (version, lib_version) in versions.iter().rev() { write!( w, "if cfg!({}) {{\n\t\t\"{}\"\n\t}} else ", version.to_cfg(None), lib_version )?; } let end = if for_let { ";" } else { "" }; writeln!(w, "{{\n\t\t\"{}\"\n\t}}{}", env.config.min_cfg_version, end) } gir-0.20.5/src/codegen/sys/cargo_toml.rs000066400000000000000000000213261475434152100200760ustar00rootroot00000000000000use std::{collections::HashMap, fs::File, io::prelude::*}; use log::info; use toml::{self, value::Table, Value}; use super::collect_versions; use crate::{config::Config, env::Env, file_saver::save_to_file, nameutil, version::Version}; pub fn generate(env: &Env) -> String { info!("Generating sys Cargo.toml for {}", env.config.library_name); let path = env.config.target_path.join("Cargo.toml"); let mut toml_str = String::new(); if let Ok(mut file) = File::open(&path) { file.read_to_string(&mut toml_str).unwrap(); } let empty = toml_str.trim().is_empty(); let mut root_table = toml::from_str(&toml_str).unwrap_or_else(|_| Table::new()); let crate_name = get_crate_name(&env.config, &root_table); if empty { fill_empty(&mut root_table, env, &crate_name); } fill_in(&mut root_table, env); save_to_file(&path, env.config.make_backup, |w| { w.write_all(toml::to_string(&root_table).unwrap().as_bytes()) }); crate_name } fn fill_empty(root: &mut Table, env: &Env, crate_name: &str) { let package_name = nameutil::exported_crate_name(crate_name); { let package = upsert_table(root, "package"); set_string(package, "name", package_name); set_string(package, "version", "0.0.1"); set_string(package, "edition", "2021"); } { let lib = upsert_table(root, "lib"); set_string(lib, "name", crate_name); } let deps = upsert_table(root, "dependencies"); for ext_lib in &env.config.external_libraries { let ext_package = if ext_lib.crate_name == "cairo" { format!("{}-sys-rs", ext_lib.crate_name) } else if ext_lib.crate_name == "gdk_pixbuf" { "gdk-pixbuf-sys".into() } else { format!("{}-sys", ext_lib.crate_name) }; let repo_url = match ext_package.as_str() { "cairo-sys-rs" | "gdk-pixbuf-sys" | "gio-sys" | "gobject-sys" | "glib-sys" | "graphene-sys" | "pango-sys" | "pangocairo-sys" => { "https://github.com/gtk-rs/gtk-rs-core" } "atk-sys" | "gdk-sys" | "gdkwayland-sys" | "gdkx11-sys" | "gtk-sys" => { "https://github.com/gtk-rs/gtk3-rs" } "gdk4-wayland-sys" | "gdk4-x11-sys" | "gdk4-sys" | "gsk4-sys" | "gtk4-sys" => { "https://github.com/gtk-rs/gtk4-rs" } "gstreamer-sys" | "gstreamer-app-sys" | "gstreamer-audio-sys" | "gstreamer-base-sys" | "gstreamer-check-sys" | "gstreamer-controller-sys" | "gstreamer-editing-services-sys" | "gstreamer-gl-sys" | "gstreamer-gl-egl-sys" | "gstreamer-gl-wayland-sys" | "gstreamer-gl-x11-sys" | "gstreamer-mpegts-sys" | "gstreamer-net-sys" | "gstreamer-pbutils-sys" | "gstreamer-player-sys" | "gstreamer-rtp-sys" | "gstreamer-rtsp-sys" | "gstreamer-rtsp-server-sys" | "gstreamer-sdp-sys" | "gstreamer-tag-sys" | "gstreamer-video-sys" | "gstreamer-webrtc-sys" | "gstreamer-allocators-sys" => "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", &_ => "ADD GIT REPOSITORY URL HERE", }; let dep = upsert_table(deps, ext_package); set_string(dep, "git", repo_url); } } fn fill_in(root: &mut Table, env: &Env) { { let package = upsert_table(root, "package"); package .entry("build") .or_insert_with(|| Value::String("build.rs".into())); // set_string(package, "version", "0.2.0"); } { let deps = upsert_table(root, "dependencies"); set_string(deps, "libc", "0.2"); } { let build_deps = upsert_table(root, "build-dependencies"); set_string(build_deps, "system-deps", "7"); } { let dev_deps = upsert_table(root, "dev-dependencies"); set_string(dev_deps, "shell-words", "1.0.0"); set_string(dev_deps, "tempfile", "3"); unset(dev_deps, "tempdir"); } { let features = upsert_table(root, "features"); let versions = collect_versions(env); versions.keys().fold(None::, |prev, &version| { let prev_array: Vec = get_feature_dependencies(version, prev, &env.config.feature_dependencies) .iter() .map(|s| Value::String(s.clone())) .collect(); features.insert(version.to_feature(), Value::Array(prev_array)); Some(version) }); } { let meta = upsert_table(root, "package"); let meta = upsert_table(meta, "metadata"); let meta = upsert_table(meta, "system-deps"); let ns = env.namespaces.main(); if let Some(lib_name) = ns.package_names.first() { let meta = upsert_table(meta, nameutil::lib_name_to_toml(lib_name)); // Allow both the name and version of a system dep to be overridden by hand meta.entry("name") .or_insert_with(|| Value::String(lib_name.clone())); meta.entry("version") .or_insert_with(|| Value::String(env.config.min_cfg_version.to_string())); // Old version API unset(meta, "feature-versions"); collect_versions(env) .iter() .filter(|(&v, _)| v > env.config.min_cfg_version) .for_each(|(v, lib_version)| { let version_section = upsert_table(meta, v.to_feature()); // Allow system-deps version for this feature level to be overridden by hand version_section .entry("version") .or_insert_with(|| Value::String(lib_version.to_string())); }); } } { // Small trick to prevent having double quotes around it since toml doesn't like // having '.' let docs_rs_metadata = upsert_table(root, "package"); let docs_rs_metadata = upsert_table(docs_rs_metadata, "metadata"); let docs_rs_metadata = upsert_table(docs_rs_metadata, "docs"); let docs_rs_metadata = upsert_table(docs_rs_metadata, "rs"); // Set the rustc and rustdoc args to be able to build the docs on docs.rs without the libraries docs_rs_metadata.entry("rustc-args").or_insert_with(|| { Value::Array(vec![ Value::String("--cfg".to_string()), Value::String("docsrs".to_string()), ]) }); docs_rs_metadata.entry("rustdoc-args").or_insert_with(|| { Value::Array(vec![ Value::String("--cfg".to_string()), Value::String("docsrs".to_string()), Value::String("--generate-link-to-definition".to_string()), ]) }); // Generate docs for all features unless a list of features to be activated on docs.rs was specified if let toml::map::Entry::Vacant(_) = docs_rs_metadata.entry("features") { docs_rs_metadata.insert("all-features".to_string(), Value::Boolean(true)); } } } fn get_feature_dependencies( version: Version, prev_version: Option, feature_dependencies: &HashMap>, ) -> Vec { let mut vec = Vec::with_capacity(10); if let Some(v) = prev_version { vec.push(v.to_feature()); } if let Some(dependencies) = feature_dependencies.get(&version) { vec.extend_from_slice(dependencies); } vec } /// Returns the name of crate being currently generated. fn get_crate_name(config: &Config, root: &Table) -> String { if let Some(Value::Table(lib)) = root.get("lib") { if let Some(Value::String(lib_name)) = lib.get("name") { // Converting don't needed as library target names cannot contain hyphens return lib_name.clone(); } } if let Some(Value::Table(package)) = root.get("package") { if let Some(Value::String(package_name)) = package.get("name") { return nameutil::crate_name(package_name); } } format!("{}_sys", nameutil::crate_name(&config.library_name)) } fn set_string>(table: &mut Table, name: &str, new_value: S) { table.insert(name.into(), Value::String(new_value.into())); } fn unset(table: &mut Table, name: &str) { table.remove(name); } fn upsert_table>(parent: &mut Table, name: S) -> &mut Table { if let Value::Table(table) = parent .entry(name.into()) .or_insert_with(|| Value::Table(toml::map::Map::new())) { table } else { unreachable!() } } gir-0.20.5/src/codegen/sys/ffi_type.rs000066400000000000000000000207611475434152100175570ustar00rootroot00000000000000use log::{info, trace, warn}; use crate::{ analysis::{ c_type::{implements_c_type, rustify_pointers}, namespaces, rust_type::{Result, TypeError}, }, env::Env, library::{self, *}, traits::*, }; // FIXME: This module needs redundant allocations audit // TODO: ffi_type computations should be cached pub fn ffi_type(env: &Env, tid: library::TypeId, c_type: &str) -> Result { let (ptr, inner) = rustify_pointers(c_type); let res = if ptr.is_empty() { if let Some(c_tid) = env.library.find_type(0, c_type) { // Fast track plain basic types avoiding some checks if env.library.type_(c_tid).maybe_ref_as::().is_some() { match *env.library.type_(tid) { Type::FixedArray(inner_tid, size, ref inner_c_type) => { let inner_c_type = inner_c_type.as_ref().map_or(c_type, String::as_str); ffi_type(env, inner_tid, inner_c_type).map_any(|rust_type| { rust_type.alter_type(|typ_| format!("[{typ_}; {size}]")) }) } Type::Class(Class { c_type: ref expected, .. }) | Type::Interface(Interface { c_type: ref expected, .. }) if c_type == "gpointer" => { info!("[c:type `gpointer` instead of `*mut {}`, fixing]", expected); ffi_inner(env, tid, expected.clone()).map_any(|rust_type| { rust_type.alter_type(|typ_| format!("*mut {typ_}")) }) } _ => ffi_inner(env, c_tid, c_type.into()), } } else { // c_type isn't Basic ffi_inner(env, tid, inner) } } else { // c_type doesn't match any type in the library by name ffi_inner(env, tid, inner) } } else { // ptr not empty ffi_inner(env, tid, inner) .map_any(|rust_type| rust_type.alter_type(|typ_| format!("{ptr} {typ_}"))) }; trace!("ffi_type({:?}, {}) -> {:?}", tid, c_type, res); res } fn ffi_inner(env: &Env, tid: library::TypeId, mut inner: String) -> Result { let volatile = inner.starts_with("volatile "); if volatile { inner = inner["volatile ".len()..].into(); } let typ = env.library.type_(tid); let res = match *typ { Type::Basic(fund) => { use crate::library::Basic::*; let inner = match fund { None => "c_void", Boolean => "gboolean", Int8 => "i8", UInt8 => "u8", Int16 => "i16", UInt16 => "u16", Int32 => "i32", UInt32 => "u32", Int64 => "i64", UInt64 => "u64", Char => "c_char", UChar => "c_uchar", Short => "c_short", UShort => "c_ushort", Int => "c_int", UInt => "c_uint", Long => "c_long", ULong => "c_ulong", Size => "size_t", SSize => "ssize_t", TimeT => "time_t", OffT => "off_t", DevT => "dev_t", GidT => "gid_t", PidT => "pid_t", SockLenT => "socklen_t", UidT => "uid_t", Float => "c_float", Double => "c_double", UniChar => "u32", Utf8 => "c_char", Filename => "c_char", OsString => "c_char", Type => "GType", Pointer => { match inner.as_str() { "void" => "c_void", // TODO: try use time:Tm "tm" => return Err(TypeError::Unimplemented(inner)), _ => &*inner, } } IntPtr => "intptr_t", UIntPtr => "uintptr_t", Bool => "bool", Unsupported => return Err(TypeError::Unimplemented(inner)), VarArgs => panic!("Should not reach here"), }; Ok(inner.into()) } Type::Record(..) | Type::Alias(..) | Type::Function(..) => { if let Some(declared_c_type) = typ.get_glib_name() { if declared_c_type != inner { let msg = format!( "[c:type mismatch `{}` != `{}` of `{}`]", inner, declared_c_type, typ.get_name() ); warn!("{}", msg); return Err(TypeError::Mismatch(msg)); } } else { warn!("Type `{}` missing c_type", typ.get_name()); } fix_name(env, tid, &inner) } Type::CArray(inner_tid) => ffi_inner(env, inner_tid, inner), Type::FixedArray(inner_tid, size, ref inner_c_type) => { let inner_c_type = inner_c_type .as_ref() .map_or_else(|| inner.as_str(), String::as_str); ffi_type(env, inner_tid, inner_c_type) .map_any(|rust_type| rust_type.alter_type(|typ_| format!("[{typ_}; {size}]"))) } Type::Array(..) | Type::PtrArray(..) | Type::List(..) | Type::SList(..) | Type::HashTable(..) => fix_name(env, tid, &inner), _ => { if let Some(glib_name) = env.library.type_(tid).get_glib_name() { if inner != glib_name { if inner == "gpointer" { fix_name(env, tid, glib_name).map_any(|rust_type| { rust_type.alter_type(|typ_| format!("*mut {typ_}")) }) } else if implements_c_type(env, tid, &inner) { info!( "[c:type {} of {} <: {}, fixing]", glib_name, env.library.type_(tid).get_name(), inner ); fix_name(env, tid, glib_name) } else { let msg = format!( "[c:type mismatch {} != {} of {}]", inner, glib_name, env.library.type_(tid).get_name() ); warn!("{}", msg); Err(TypeError::Mismatch(msg)) } } else { fix_name(env, tid, &inner) } } else { let msg = format!( "[Missing glib_name of {}, can't match != {}]", env.library.type_(tid).get_name(), inner ); warn!("{}", msg); Err(TypeError::Mismatch(msg)) } } }; if volatile { res.map(|rust_type| rust_type.alter_type(|typ_| format!("/*volatile*/{typ_}"))) } else { res } } fn fix_name(env: &Env, type_id: library::TypeId, name: &str) -> Result { if type_id.ns_id == library::INTERNAL_NAMESPACE { match env.library.type_(type_id) { Type::Array(..) | Type::PtrArray(..) | Type::List(..) | Type::SList(..) | Type::HashTable(..) => { if env.namespaces.glib_ns_id == namespaces::MAIN { Ok(name.into()) } else { Ok(format!( "{}::{}", &env.namespaces[env.namespaces.glib_ns_id].crate_name, name ) .into()) } } _ => Ok(name.into()), } } else { let name_with_prefix = if type_id.ns_id == namespaces::MAIN { name.into() } else { format!("{}::{}", &env.namespaces[type_id.ns_id].crate_name, name) }; if env .type_status_sys(&type_id.full_name(&env.library)) .ignored() { Err(TypeError::Ignored(name_with_prefix)) } else { Ok(name_with_prefix.into()) } } } gir-0.20.5/src/codegen/sys/fields.rs000066400000000000000000000121111475434152100172060ustar00rootroot00000000000000use crate::{ analysis::{rust_type::*, types::*}, codegen::sys::{ffi_type::ffi_type, functions::function_signature}, env::Env, library::*, traits::{IntoString, MaybeRefAs}, }; pub struct Fields { /// Name of union, class, or a record that contains the fields. pub name: String, /// Is this external type? pub external: bool, /// Reason for truncating the representation, if any. pub truncated: Option, derives_copy: bool, /// "struct" or "union" pub kind: &'static str, /// specified GObject cfg condition pub cfg_condition: Option, pub fields: Vec, } pub struct FieldInfo { /// Rust field name pub name: String, /// Rust type name pub typ: String, /// Does access to this field require unsafe block? unsafe_access: bool, /// Include this field in Debug impl? pub debug: bool, } impl Fields { /// List of derived traits pub fn derived_traits(&self) -> Vec<&'static str> { let mut traits = Vec::new(); if self.derives_copy { traits.push("Copy"); traits.push("Clone"); } traits } } impl FieldInfo { /// Generates a string that accesses the field in the context of &self /// receiver. pub fn access_str(&self) -> String { let mut s = format!("&self.{}", self.name); if self.unsafe_access { s = format!("unsafe {{ {s} }}"); } s } } pub fn from_record(env: &Env, record: &Record) -> Fields { let (fields, truncated) = analyze_fields(env, false, &record.fields); let derives_copy = truncated.is_none() && record.derives_copy(&env.library); Fields { name: record.c_type.clone(), external: record.is_external(&env.library), truncated, derives_copy, kind: "struct", cfg_condition: get_gobject_cfg_condition(env, &record.name), fields, } } pub fn from_class(env: &Env, klass: &Class) -> Fields { let (fields, truncated) = analyze_fields(env, false, &klass.fields); let derives_copy = truncated.is_none() && klass.derives_copy(&env.library); Fields { name: klass.c_type.clone(), external: klass.is_external(&env.library), truncated, derives_copy, kind: "struct", cfg_condition: get_gobject_cfg_condition(env, &klass.name), fields, } } pub fn from_union(env: &Env, union: &Union) -> Fields { let (fields, truncated) = analyze_fields(env, true, &union.fields); let derives_copy = truncated.is_none() && union.derives_copy(&env.library); Fields { name: union.c_type.as_ref().unwrap().clone(), external: union.is_external(&env.library), truncated, derives_copy, kind: "union", cfg_condition: None, fields, } } fn analyze_fields( env: &Env, unsafe_access: bool, fields: &[Field], ) -> (Vec, Option) { let mut truncated = None; let mut infos = Vec::with_capacity(fields.len()); let mut is_bitfield = false; for field in fields { // See IsIncomplete for &[Field]. if is_bitfield && field.bits.is_some() { truncated = Some(format!("field {} has incomplete type", &field.name)); break; } is_bitfield = field.bits.is_some(); let typ = match field_ffi_type(env, field) { e @ Err(..) => { truncated = Some(e.into_string()); break; } Ok(typ) => typ, }; // Skip private fields from Debug impl. Ignore volatile as well, // they are usually used as synchronization primites, // so we wouldn't want to introduce additional reads. let debug = !field.private && !field.is_volatile() && field.implements_debug(&env.library); infos.push(FieldInfo { name: field.name.clone(), typ: typ.into_string(), debug, unsafe_access, }); } (infos, truncated) } fn field_ffi_type(env: &Env, field: &Field) -> Result { if field.is_incomplete(&env.library) { return Err(TypeError::Ignored(format!( "field {} has incomplete type", &field.name ))); } if let Some(ref c_type) = field.c_type { ffi_type(env, field.typ, c_type) } else if let Some(func) = env.library.type_(field.typ).maybe_ref_as::() { let (failure, signature) = function_signature(env, func, true); let signature = format!("Option"); if failure { Err(TypeError::Unimplemented(signature)) } else { Ok(signature.into()) } } else { Err(TypeError::Ignored(format!( "field {} has empty c:type", &field.name ))) } } fn get_gobject_cfg_condition(env: &Env, name: &str) -> Option { let full_name = format!("{}.{}", env.namespaces.main().name, name); if let Some(obj) = env.config.objects.get(&full_name) { obj.cfg_condition.clone() } else { None } } gir-0.20.5/src/codegen/sys/functions.rs000066400000000000000000000223301475434152100177540ustar00rootroot00000000000000use std::{ io::{Result, Write}, sync::OnceLock, }; use super::ffi_type::*; use crate::{ codegen::general::{cfg_condition, version_condition}, config::{functions::Function, gobjects::GObject}, env::Env, library, nameutil, traits::*, }; // used as glib:get-type in GLib-2.0.gir const INTERN: &str = "intern"; fn default_obj() -> &'static GObject { static OBJ: OnceLock = OnceLock::new(); OBJ.get_or_init(Default::default) } pub fn generate_records_funcs( w: &mut dyn Write, env: &Env, records: &[&library::Record], ) -> Result<()> { let intern_str = INTERN.to_string(); for record in records { let name = format!("{}.{}", env.config.library_name, record.name); let obj = env.config.objects.get(&name).unwrap_or(default_obj()); let version = obj.version.or(record.version); let glib_get_type = record.glib_get_type.as_ref().unwrap_or(&intern_str); generate_object_funcs( w, env, obj, version, &record.c_type, glib_get_type, &record.functions, )?; } Ok(()) } pub fn generate_classes_funcs( w: &mut dyn Write, env: &Env, classes: &[&library::Class], ) -> Result<()> { for klass in classes { let name = format!("{}.{}", env.config.library_name, klass.name); let obj = env.config.objects.get(&name).unwrap_or(default_obj()); let version = obj.version.or(klass.version); generate_object_funcs( w, env, obj, version, &klass.c_type, &klass.glib_get_type, &klass.functions, )?; } Ok(()) } pub fn generate_bitfields_funcs( w: &mut dyn Write, env: &Env, bitfields: &[&library::Bitfield], ) -> Result<()> { let intern_str = INTERN.to_string(); for bitfield in bitfields { let name = format!("{}.{}", env.config.library_name, bitfield.name); let obj = env.config.objects.get(&name).unwrap_or(default_obj()); let version = obj.version.or(bitfield.version); let glib_get_type = bitfield.glib_get_type.as_ref().unwrap_or(&intern_str); generate_object_funcs( w, env, obj, version, &bitfield.c_type, glib_get_type, &bitfield.functions, )?; } Ok(()) } pub fn generate_enums_funcs( w: &mut dyn Write, env: &Env, enums: &[&library::Enumeration], ) -> Result<()> { let intern_str = INTERN.to_string(); for en in enums { let name = format!("{}.{}", env.config.library_name, en.name); let obj = env.config.objects.get(&name).unwrap_or(default_obj()); let version = obj.version.or(en.version); let glib_get_type = en.glib_get_type.as_ref().unwrap_or(&intern_str); generate_object_funcs( w, env, obj, version, &en.c_type, glib_get_type, &en.functions, )?; } Ok(()) } pub fn generate_unions_funcs( w: &mut dyn Write, env: &Env, unions: &[&library::Union], ) -> Result<()> { let intern_str = INTERN.to_string(); for union in unions { let Some(ref c_type) = union.c_type else { return Ok(()); }; let name = format!("{}.{}", env.config.library_name, union.name); let obj = env.config.objects.get(&name).unwrap_or(default_obj()); let glib_get_type = union.glib_get_type.as_ref().unwrap_or(&intern_str); generate_object_funcs( w, env, obj, obj.version, c_type, glib_get_type, &union.functions, )?; } Ok(()) } pub fn generate_interfaces_funcs( w: &mut dyn Write, env: &Env, interfaces: &[&library::Interface], ) -> Result<()> { for interface in interfaces { let name = format!("{}.{}", env.config.library_name, interface.name); let obj = env.config.objects.get(&name).unwrap_or(default_obj()); let version = obj.version.or(interface.version); generate_object_funcs( w, env, obj, version, &interface.c_type, &interface.glib_get_type, &interface.functions, )?; } Ok(()) } pub fn generate_other_funcs( w: &mut dyn Write, env: &Env, functions: &[library::Function], ) -> Result<()> { let name = format!("{}.*", env.config.library_name); let obj = env.config.objects.get(&name).unwrap_or(default_obj()); generate_object_funcs(w, env, obj, None, "Other functions", INTERN, functions) } fn generate_cfg_configure( w: &mut dyn Write, configured_functions: &[&Function], commented: bool, ) -> Result<()> { let cfg_condition_ = configured_functions .iter() .find_map(|f| f.cfg_condition.as_ref()); cfg_condition(w, cfg_condition_, commented, 1)?; Ok(()) } fn generate_object_funcs( w: &mut dyn Write, env: &Env, obj: &GObject, version: Option, c_type: &str, glib_get_type: &str, functions: &[library::Function], ) -> Result<()> { let write_get_type = glib_get_type != INTERN; if write_get_type || !functions.is_empty() { writeln!(w)?; writeln!( w, " //=========================================================================" )?; writeln!(w, " // {c_type}")?; writeln!( w, " //=========================================================================" )?; } if write_get_type { let configured_functions = obj.functions.matched("get_type"); if configured_functions .iter() .all(|f| f.status.need_generate()) { let version = std::iter::once(version) .chain(configured_functions.iter().map(|f| f.version)) .max() .flatten(); version_condition(w, env, None, version, false, 1)?; generate_cfg_configure(w, &configured_functions, false)?; writeln!(w, " pub fn {glib_get_type}() -> GType;")?; } } for func in functions { let configured_functions = obj.functions.matched(&func.name); if !configured_functions .iter() .all(|f| f.status.need_generate()) { continue; } let (commented, sig) = function_signature(env, func, false); let comment = if commented { "//" } else { "" }; // If a version was configured for this function specifically then use that, // otherwise use the (fixed up!) version of the function, if any, otherwise // use the version of the type. let version = configured_functions .iter() .map(|f| f.version) .max() .flatten() .or(func.version) .or(version); version_condition(w, env, None, version, commented, 1)?; let name = func.c_identifier.as_ref().unwrap(); generate_cfg_configure(w, &configured_functions, commented)?; writeln!(w, " {comment}pub fn {name}{sig};")?; } Ok(()) } pub fn generate_callbacks( w: &mut dyn Write, env: &Env, callbacks: &[&library::Function], ) -> Result<()> { if !callbacks.is_empty() { writeln!(w, "// Callbacks")?; } for func in callbacks { let (commented, sig) = function_signature(env, func, true); let comment = if commented { "//" } else { "" }; writeln!( w, "{}pub type {} = Option;", comment, func.c_identifier.as_ref().unwrap(), sig )?; } if !callbacks.is_empty() { writeln!(w)?; } Ok(()) } pub fn function_signature(env: &Env, func: &library::Function, bare: bool) -> (bool, String) { let (mut commented, ret_str) = function_return_value(env, func); let mut parameter_strs: Vec = Vec::new(); for par in &func.parameters { let (c, par_str) = function_parameter(env, par, bare); parameter_strs.push(par_str); if c { commented = true; } } ( commented, format!("({}){}", parameter_strs.join(", "), ret_str), ) } fn function_return_value(env: &Env, func: &library::Function) -> (bool, String) { if func.ret.typ == Default::default() { return (false, String::new()); } let ffi_type = ffi_type(env, func.ret.typ, &func.ret.c_type); let commented = ffi_type.is_err(); (commented, format!(" -> {}", ffi_type.into_string())) } fn function_parameter(env: &Env, par: &library::Parameter, bare: bool) -> (bool, String) { if let library::Type::Basic(library::Basic::VarArgs) = env.library.type_(par.typ) { return (false, "...".into()); } let ffi_type = ffi_type(env, par.typ, &par.c_type); let commented = ffi_type.is_err(); let res = if bare { ffi_type.into_string() } else { format!( "{}: {}", nameutil::mangle_keywords(&*par.name), ffi_type.into_string() ) }; (commented, res) } gir-0.20.5/src/codegen/sys/lib_.rs000066400000000000000000000432031475434152100166530ustar00rootroot00000000000000use std::{ fs, io::{Error, ErrorKind, Result, Write}, }; use log::info; use super::{ffi_type::ffi_type, fields, functions, statics}; use crate::{ codegen::general::{self, cfg_condition, version_condition}, config::constants, env::Env, file_saver::*, library::*, nameutil::*, traits::*, }; pub fn generate(env: &Env) { info!("Generating sys for {}", env.config.library_name); let path = env.config.auto_path.join(file_name_sys("lib")); info!("Generating file {:?}", path); save_to_file(&path, env.config.make_backup, |w| generate_lib(w, env)); } fn generate_lib(w: &mut dyn Write, env: &Env) -> Result<()> { general::start_comments(w, &env.config)?; statics::begin(w)?; generate_extern_crates(w, env)?; include_custom_modules(w, env)?; statics::after_extern_crates(w)?; if env.config.library_name != "GLib" { statics::use_glib(w)?; } match &*env.config.library_name { "GLib" => statics::only_for_glib(w)?, "GObject" => statics::only_for_gobject(w)?, "Gtk" => statics::only_for_gtk(w)?, _ => (), } writeln!(w)?; let ns = env.library.namespace(MAIN_NAMESPACE); let records = prepare(ns); let classes = prepare(ns); let interfaces = prepare(ns); let bitfields = prepare(ns); let enums = prepare(ns); let unions = prepare(ns); generate_aliases(w, env, &prepare(ns))?; generate_enums(w, env, &enums)?; generate_constants(w, env, &ns.constants)?; generate_bitfields(w, env, &bitfields)?; generate_unions(w, env, &unions)?; functions::generate_callbacks(w, env, &prepare(ns))?; generate_records(w, env, &records)?; generate_classes_structs(w, env, &classes)?; generate_interfaces_structs(w, env, &interfaces)?; if env.namespaces.main().shared_libs.is_empty() && !(records.iter().all(|x| x.functions.is_empty()) && classes.iter().all(|x| x.functions.is_empty()) && interfaces.iter().all(|x| x.functions.is_empty()) && bitfields.iter().all(|x| x.functions.is_empty()) && enums.iter().all(|x| x.functions.is_empty()) && unions.iter().all(|x| x.functions.is_empty())) { return Err(Error::new( ErrorKind::Other, "No shared library found, but functions were found", )); } if !env.namespaces.main().shared_libs.is_empty() { writeln!(w, "extern \"C\" {{")?; functions::generate_enums_funcs(w, env, &enums)?; functions::generate_bitfields_funcs(w, env, &bitfields)?; functions::generate_unions_funcs(w, env, &unions)?; functions::generate_records_funcs(w, env, &records)?; functions::generate_classes_funcs(w, env, &classes)?; functions::generate_interfaces_funcs(w, env, &interfaces)?; functions::generate_other_funcs(w, env, &ns.functions)?; writeln!(w, "\n}}")?; } Ok(()) } fn generate_extern_crates(w: &mut dyn Write, env: &Env) -> Result<()> { for library in &env.config.external_libraries { w.write_all( format!( "use {}_sys as {};\n", library.crate_name.replace('-', "_"), crate_name(&library.namespace) ) .as_bytes(), )?; } Ok(()) } fn include_custom_modules(w: &mut dyn Write, env: &Env) -> Result<()> { let modules = find_modules(env)?; if !modules.is_empty() { writeln!(w)?; for module in &modules { writeln!(w, "mod {module};")?; } writeln!(w)?; for module in &modules { writeln!(w, "pub use {module}::*;")?; } } Ok(()) } fn find_modules(env: &Env) -> Result> { let mut vec = Vec::::new(); for entry in fs::read_dir(&env.config.auto_path)? { let path = entry?.path(); let Some(ext) = path.extension() else { continue; }; if ext != "rs" { continue; } let file_stem = path.file_stem().expect("No file name"); if file_stem == "lib" { continue; } let file_stem = file_stem .to_str() .expect("Can't convert file name to string") .to_owned(); vec.push(file_stem); } vec.sort(); Ok(vec) } fn prepare(ns: &Namespace) -> Vec<&T> where Type: MaybeRef, { let mut vec: Vec<&T> = Vec::with_capacity(ns.types.len()); for typ in ns.types.iter().filter_map(Option::as_ref) { if let Some(x) = typ.maybe_ref() { vec.push(x); } } vec.sort(); vec } fn generate_aliases(w: &mut dyn Write, env: &Env, items: &[&Alias]) -> Result<()> { if !items.is_empty() { writeln!(w, "// Aliases")?; } for item in items { let full_name = format!("{}.{}", env.namespaces.main().name, item.name); if !env.type_status_sys(&full_name).need_generate() { continue; } let (comment, c_type) = match ffi_type(env, item.typ, &item.target_c_type) { Ok(x) => ("", x.into_string()), x @ Err(..) => ("//", x.into_string()), }; let cfg_condition_ = env .config .objects .get(&full_name) .and_then(|obj| obj.cfg_condition.as_ref()); cfg_condition(w, cfg_condition_, false, 0)?; writeln!(w, "{}pub type {} = {};", comment, item.c_identifier, c_type)?; } if !items.is_empty() { writeln!(w)?; } Ok(()) } fn generate_bitfields(w: &mut dyn Write, env: &Env, items: &[&Bitfield]) -> Result<()> { if !items.is_empty() { writeln!(w, "// Flags")?; } for item in items { let full_name = format!("{}.{}", env.namespaces.main().name, item.name); let config = env.config.objects.get(&full_name); if let Some(false) = config.map(|c| c.status.need_generate()) { continue; } writeln!(w, "pub type {} = c_uint;", item.c_type)?; for member in &item.members { let member_config = config .as_ref() .map_or_else(Vec::new, |c| c.members.matched(&member.name)); let version = member_config .iter() .find_map(|m| m.version) .or(member.version); if member_config.iter().any(|m| m.status.ignored()) { continue; } let val: i64 = member.value.parse().unwrap(); version_condition(w, env, None, version, false, 0)?; writeln!( w, "pub const {}: {} = {};", member.c_identifier, item.c_type, val as u32, )?; } writeln!(w)?; } Ok(()) } fn generate_constant_cfg_configure( w: &mut dyn Write, configured_constants: &[&constants::Constant], commented: bool, ) -> Result<()> { let cfg_condition_ = configured_constants .iter() .find_map(|f| f.cfg_condition.as_ref()); cfg_condition(w, cfg_condition_, commented, 1)?; Ok(()) } fn generate_constants(w: &mut dyn Write, env: &Env, constants: &[Constant]) -> Result<()> { if !constants.is_empty() { writeln!(w, "// Constants")?; } for constant in constants { let full_name = format!("{}.{}", env.namespaces.main().name, constant.name); let config = env.config.objects.get(&full_name); if let Some(false) = config.map(|c| c.status.need_generate()) { continue; } let (comment, mut type_) = match ffi_type(env, constant.typ, &constant.c_type) { Ok(x) => ("", x.into_string()), x @ Err(..) => ("//", x.into_string()), }; let mut value = constant.value.clone(); if type_ == "*mut c_char" { type_ = "&[u8]".into(); value = format!("b\"{}\\0\"", general::escape_string(&value)); } else if type_ == "gboolean" { value = if value == "true" { use_glib_if_needed(env, "GTRUE") } else { use_glib_if_needed(env, "GFALSE") }; } else if env .library .type_(constant.typ) .maybe_ref_as::() .is_some() { let val: i64 = constant.value.parse().unwrap(); value = (val as u32).to_string(); } if let Some(obj) = config { let configured_constants = obj.constants.matched(&full_name); generate_constant_cfg_configure(w, &configured_constants, !comment.is_empty())?; } writeln!( w, "{}pub const {}: {} = {};", comment, constant.c_identifier, type_, value )?; } if !constants.is_empty() { writeln!(w)?; } Ok(()) } fn generate_enums(w: &mut dyn Write, env: &Env, items: &[&Enumeration]) -> Result<()> { if !items.is_empty() { writeln!(w, "// Enums")?; } for item in items { let full_name = format!("{}.{}", env.namespaces.main().name, item.name); let config = env.config.objects.get(&full_name); if let Some(false) = config.map(|c| c.status.need_generate()) { continue; } let cfg_condition_ = env .config .objects .get(&full_name) .and_then(|obj| obj.cfg_condition.as_ref()); cfg_condition(w, cfg_condition_, false, 0)?; writeln!(w, "pub type {} = c_int;", item.c_type)?; for member in &item.members { let member_config = config .as_ref() .map_or_else(Vec::new, |c| c.members.matched(&member.name)); let version = member_config .iter() .find_map(|m| m.version) .or(member.version); if member_config.iter().any(|m| m.status.ignored()) { continue; } cfg_condition(w, cfg_condition_, false, 0)?; version_condition(w, env, None, version, false, 0)?; writeln!( w, "pub const {}: {} = {};", member.c_identifier, item.c_type, member.value, )?; } writeln!(w)?; } Ok(()) } fn generate_unions(w: &mut dyn Write, env: &Env, unions: &[&Union]) -> Result<()> { if !unions.is_empty() { writeln!(w, "// Unions")?; } for union in unions { if union.c_type.is_none() { continue; } let full_name = format!("{}.{}", env.namespaces.main().name, union.name); let config = env.config.objects.get(&full_name); if let Some(false) = config.map(|c| c.status.need_generate()) { continue; } let align = config.and_then(|c| c.align); let fields = fields::from_union(env, union); generate_from_fields(w, &fields, align)?; } Ok(()) } fn generate_debug_impl(w: &mut dyn Write, name: &str, impl_content: &str) -> Result<()> { writeln!( w, "impl ::std::fmt::Debug for {name} {{\n\ \tfn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {{\n\ \t\t{impl_content}\n\ \t}}\n\ }}\n" ) } fn generate_classes_structs(w: &mut dyn Write, env: &Env, classes: &[&Class]) -> Result<()> { if !classes.is_empty() { writeln!(w, "// Classes")?; } for class in classes { let full_name = format!("{}.{}", env.namespaces.main().name, class.name); let config = env.config.objects.get(&full_name); if let Some(false) = config.map(|c| c.status.need_generate()) { continue; } let align = config.and_then(|c| c.align); let fields = fields::from_class(env, class); generate_from_fields(w, &fields, align)?; } Ok(()) } fn generate_opaque_type(w: &mut dyn Write, name: &str) -> Result<()> { writeln!( w, r#"#[repr(C)] #[allow(dead_code)] pub struct {name} {{ _data: [u8; 0], _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, }} "# ) } fn generate_interfaces_structs( w: &mut dyn Write, env: &Env, interfaces: &[&Interface], ) -> Result<()> { if !interfaces.is_empty() { writeln!(w, "// Interfaces")?; } for interface in interfaces { let full_name = format!("{}.{}", env.namespaces.main().name, interface.name); if !env.type_status_sys(&full_name).need_generate() { continue; } let cfg_condition_ = env .config .objects .get(&full_name) .and_then(|obj| obj.cfg_condition.as_ref()); cfg_condition(w, cfg_condition_, false, 0)?; generate_opaque_type(w, &interface.c_type)?; cfg_condition(w, cfg_condition_, false, 0)?; generate_debug_impl( w, &interface.c_type, &format!( "write!(f, \"{name} @ {{self:p}}\")", name = interface.c_type ), )?; } if !interfaces.is_empty() { writeln!(w)?; } Ok(()) } fn generate_records(w: &mut dyn Write, env: &Env, records: &[&Record]) -> Result<()> { if !records.is_empty() { writeln!(w, "// Records")?; } for record in records { let full_name = format!("{}.{}", env.namespaces.main().name, record.name); let config = env.config.objects.get(&full_name); if let Some(false) = config.map(|c| c.status.need_generate()) { continue; } if record.c_type == "GHookList" { // 1. GHookList is useful. // 2. GHookList contains bitfields. // 3. Bitfields are unrepresentable in Rust. // 4. ... // 5. Thus, we use custom generated GHookList. // Hopefully someone will profit from all this. generate_ghooklist(w)?; } else if record.disguised || record.pointer { generate_disguised(w, env, record)?; } else { let align = config.and_then(|c| c.align); let fields = fields::from_record(env, record); generate_from_fields(w, &fields, align)?; } } Ok(()) } fn generate_ghooklist(w: &mut dyn Write) -> Result<()> { w.write_all( br#"#[repr(C)] #[derive(Copy, Clone)] pub struct GHookList { pub seq_id: c_ulong, #[cfg(any(not(windows), not(target_pointer_width = "64")))] pub hook_size_and_setup: gpointer, #[cfg(all(windows, target_pointer_width = "64"))] pub hook_size_and_setup: c_ulong, pub hooks: *mut GHook, pub dummy3: gpointer, pub finalize_hook: GHookFinalizeFunc, pub dummy: [gpointer; 2], } impl ::std::fmt::Debug for GHookList { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { write!(f, "GHookList @ {self:p}") } } "#, ) } fn generate_disguised(w: &mut dyn Write, env: &Env, record: &Record) -> Result<()> { let full_name = format!("{}.{}", env.namespaces.main().name, record.name); let cfg_condition_ = env .config .objects .get(&full_name) .and_then(|obj| obj.cfg_condition.as_ref()); cfg_condition(w, cfg_condition_, false, 0)?; generate_opaque_type(w, &format!("_{}", record.c_type))?; cfg_condition(w, cfg_condition_, false, 0)?; if record.pointer { writeln!(w, "pub type {name} = *mut _{name};", name = record.c_type)?; } else { writeln!(w, "pub type {name} = _{name};", name = record.c_type)?; } writeln!(w) } fn generate_from_fields( w: &mut dyn Write, fields: &fields::Fields, align: Option, ) -> Result<()> { cfg_condition(w, fields.cfg_condition.as_ref(), false, 0)?; if let Some(align) = align { writeln!(w, "#[repr(align({align}))]")?; } let traits = fields.derived_traits().join(", "); if !traits.is_empty() { writeln!(w, "#[derive({traits})]")?; } if fields.external { // It would be nice to represent those using extern types // from RFC 1861, once they are available in stable Rust. // https://github.com/rust-lang/rust/issues/43467 generate_opaque_type(w, &fields.name)?; } else { writeln!(w, "#[repr(C)]")?; if fields.truncated.is_some() { writeln!(w, "#[allow(dead_code)]")?; } writeln!( w, "pub {kind} {name} {{", kind = fields.kind, name = &fields.name )?; for field in &fields.fields { writeln!( w, "\tpub {field_name}: {field_type},", field_name = &field.name, field_type = &field.typ )?; } if let Some(ref reason) = fields.truncated { writeln!(w, "\t_truncated_record_marker: c_void,")?; writeln!(w, "\t// {reason}")?; } writeln!(w, "}}\n")?; } cfg_condition(w, fields.cfg_condition.as_ref(), false, 0)?; writeln!( w, "impl ::std::fmt::Debug for {name} {{", name = &fields.name )?; writeln!( w, "\tfn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {{" )?; writeln!( w, "\t\tf.debug_struct(&format!(\"{name} @ {{self:p}}\"))", name = &fields.name )?; for field in fields.fields.iter().filter(|f| f.debug) { // TODO: We should generate debug for field manually if automatic one is not // available. writeln!( w, "\t\t .field(\"{field_name}\", {field_get})", field_name = &field.name, field_get = &field.access_str() )?; } writeln!(w, "\t\t .finish()")?; writeln!(w, "\t}}")?; writeln!(w, "}}")?; writeln!(w) } gir-0.20.5/src/codegen/sys/mod.rs000066400000000000000000000016631475434152100165310ustar00rootroot00000000000000use std::collections::BTreeMap; use crate::{codegen::generate_single_version_file, env::Env, version::Version}; mod build; mod cargo_toml; pub mod ffi_type; mod fields; mod functions; mod lib_; mod statics; mod tests; pub fn generate(env: &Env) { generate_single_version_file(env); lib_::generate(env); build::generate(env); let crate_name = cargo_toml::generate(env); tests::generate(env, &crate_name); } pub fn collect_versions(env: &Env) -> BTreeMap { let mut versions: BTreeMap = env .namespaces .main() .versions .iter() .filter(|v| **v > env.config.min_cfg_version) .map(|v| (*v, *v)) .collect(); for v in &env.config.extra_versions { versions.insert(*v, *v); } for (version, lib_version) in &env.config.lib_version_overrides { versions.insert(*version, *lib_version); } versions } gir-0.20.5/src/codegen/sys/statics.rs000066400000000000000000000066401475434152100174240ustar00rootroot00000000000000use std::io::{Result, Write}; use super::super::general::write_vec; pub fn begin(w: &mut dyn Write) -> Result<()> { let v = vec![ "", "#![allow(non_camel_case_types, non_upper_case_globals, non_snake_case)]", "#![allow(clippy::approx_constant, clippy::type_complexity, clippy::unreadable_literal, clippy::upper_case_acronyms)]", "#![cfg_attr(docsrs, feature(doc_cfg))]", "", ]; write_vec(w, &v) } pub fn after_extern_crates(w: &mut dyn Write) -> Result<()> { let v = vec![ "", "#[allow(unused_imports)]", "use std::ffi::{c_int, c_char, c_uchar, c_float, c_uint, c_double,", " c_short, c_ushort, c_long, c_ulong, c_void};", "#[allow(unused_imports)]", "use libc::{size_t, ssize_t, time_t, off_t, intptr_t, uintptr_t, FILE};", "#[cfg(unix)]", "#[allow(unused_imports)]", "use libc::{dev_t, gid_t, pid_t, socklen_t, uid_t};", ]; write_vec(w, &v) } pub fn use_glib(w: &mut dyn Write) -> Result<()> { let v = vec![ "", "#[allow(unused_imports)]", "use glib::{gboolean, gconstpointer, gpointer, GType};", ]; write_vec(w, &v) } pub fn only_for_glib(w: &mut dyn Write) -> Result<()> { let v = vec![ "", "pub type gboolean = c_int;", "pub const GFALSE: c_int = 0;", "pub const GTRUE: c_int = 1;", "", "pub type gconstpointer = *const c_void;", "pub type gpointer = *mut c_void;", "", ]; write_vec(w, &v) } pub fn only_for_gobject(w: &mut dyn Write) -> Result<()> { let v = vec![ "", "pub const G_TYPE_INVALID: GType = 0 << G_TYPE_FUNDAMENTAL_SHIFT;", "pub const G_TYPE_NONE: GType = 1 << G_TYPE_FUNDAMENTAL_SHIFT;", "pub const G_TYPE_INTERFACE: GType = 2 << G_TYPE_FUNDAMENTAL_SHIFT;", "pub const G_TYPE_CHAR: GType = 3 << G_TYPE_FUNDAMENTAL_SHIFT;", "pub const G_TYPE_UCHAR: GType = 4 << G_TYPE_FUNDAMENTAL_SHIFT;", "pub const G_TYPE_BOOLEAN: GType = 5 << G_TYPE_FUNDAMENTAL_SHIFT;", "pub const G_TYPE_INT: GType = 6 << G_TYPE_FUNDAMENTAL_SHIFT;", "pub const G_TYPE_UINT: GType = 7 << G_TYPE_FUNDAMENTAL_SHIFT;", "pub const G_TYPE_LONG: GType = 8 << G_TYPE_FUNDAMENTAL_SHIFT;", "pub const G_TYPE_ULONG: GType = 9 << G_TYPE_FUNDAMENTAL_SHIFT;", "pub const G_TYPE_INT64: GType = 10 << G_TYPE_FUNDAMENTAL_SHIFT;", "pub const G_TYPE_UINT64: GType = 11 << G_TYPE_FUNDAMENTAL_SHIFT;", "pub const G_TYPE_ENUM: GType = 12 << G_TYPE_FUNDAMENTAL_SHIFT;", "pub const G_TYPE_FLAGS: GType = 13 << G_TYPE_FUNDAMENTAL_SHIFT;", "pub const G_TYPE_FLOAT: GType = 14 << G_TYPE_FUNDAMENTAL_SHIFT;", "pub const G_TYPE_DOUBLE: GType = 15 << G_TYPE_FUNDAMENTAL_SHIFT;", "pub const G_TYPE_STRING: GType = 16 << G_TYPE_FUNDAMENTAL_SHIFT;", "pub const G_TYPE_POINTER: GType = 17 << G_TYPE_FUNDAMENTAL_SHIFT;", "pub const G_TYPE_BOXED: GType = 18 << G_TYPE_FUNDAMENTAL_SHIFT;", "pub const G_TYPE_PARAM: GType = 19 << G_TYPE_FUNDAMENTAL_SHIFT;", "pub const G_TYPE_OBJECT: GType = 20 << G_TYPE_FUNDAMENTAL_SHIFT;", "pub const G_TYPE_VARIANT: GType = 21 << G_TYPE_FUNDAMENTAL_SHIFT;", ]; write_vec(w, &v) } pub fn only_for_gtk(w: &mut dyn Write) -> Result<()> { let v = vec!["", "pub const GTK_ENTRY_BUFFER_MAX_SIZE: u16 = u16::MAX;"]; write_vec(w, &v) } gir-0.20.5/src/codegen/sys/tests.rs000066400000000000000000000400071475434152100171070ustar00rootroot00000000000000use std::{ io::{self, prelude::*}, path::Path, }; use log::info; use crate::{ analysis::types::IsIncomplete, codegen::general, config::gobjects::GStatus, env::Env, file_saver::save_to_file, library::{self, Bitfield, Enumeration, Namespace, Type, MAIN_NAMESPACE}, }; #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] struct CType { /// Name of type, as used in C. name: String, /// Expression describing when type is available (when defined only /// conditionally). cfg_condition: Option, } #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] struct CConstant { /// Identifier in C. name: String, /// Stringified value. value: String, status: GStatus, } pub fn generate(env: &Env, crate_name: &str) { let ctypes = prepare_ctypes(env); let cconsts = prepare_cconsts(env); if ctypes.is_empty() && cconsts.is_empty() { return; } let tests = env.config.target_path.join("tests"); let manual_h = tests.join("manual.h"); if !manual_h.exists() { save_to_file(&manual_h, env.config.make_backup, |w| { generate_manual_h(env, &manual_h, w) }); } let layout_c = tests.join("layout.c"); save_to_file(&layout_c, env.config.make_backup, |w| { generate_layout_c(env, &layout_c, w, &ctypes) }); let constant_c = tests.join("constant.c"); save_to_file(&constant_c, env.config.make_backup, |w| { generate_constant_c(env, &constant_c, w, &cconsts) }); let abi_rs = tests.join("abi.rs"); save_to_file(&abi_rs, env.config.make_backup, |w| { generate_abi_rs(env, &abi_rs, w, crate_name, &ctypes, &cconsts) }); } fn prepare_ctypes(env: &Env) -> Vec { let ns = env.library.namespace(MAIN_NAMESPACE); let mut types: Vec = ns .types .iter() .filter_map(Option::as_ref) .filter(|t| !t.is_incomplete(&env.library)) .filter_map(|t| match t { Type::Record(library::Record { disguised: false, .. }) => prepare_ctype(env, ns, t), Type::Alias(_) | Type::Class(_) | Type::Union(_) | Type::Enumeration(_) | Type::Bitfield(_) | Type::Interface(_) => prepare_ctype(env, ns, t), _ => None, }) .collect(); types.sort(); types } fn prepare_ctype(env: &Env, ns: &Namespace, t: &Type) -> Option { let full_name = format!("{}.{}", ns.name, t.get_name()); if env.type_status_sys(&full_name).ignored() { return None; } let name = t.get_glib_name()?; if is_name_made_up(name) { return None; } let object = env.config.objects.get(&full_name); Some(CType { name: name.to_owned(), cfg_condition: object.and_then(|obj| obj.cfg_condition.clone()), }) } fn prepare_cconsts(env: &Env) -> Vec { let ns = env.library.namespace(MAIN_NAMESPACE); let mut constants: Vec = ns .constants .iter() .filter_map(|constant| { let full_name = format!("{}.{}", &ns.name, constant.name); if env.type_status_sys(&full_name).ignored() { return None; } let value = match constant { c if c.c_type == "gboolean" && c.value == "true" => "1", c if c.c_type == "gboolean" && c.value == "false" => "0", c => &c.value, }; Some(CConstant { name: constant.c_identifier.clone(), value: value.to_owned(), status: GStatus::Generate, // Assume generate because we skip ignored ones above }) }) .collect(); for typ in &ns.types { let typ = if let Some(typ) = typ { typ } else { continue; }; let full_name = format!("{}.{}", &ns.name, typ.get_name()); if env.type_status_sys(&full_name).ignored() { continue; } match typ { Type::Bitfield(Bitfield { members, .. }) => { for member in members { // GLib assumes that bitflags are unsigned integers, // see the GValue machinery around them for example constants.push(CConstant { name: format!("(guint) {}", member.c_identifier), value: member .value .parse::() .map(|i| (i as u32).to_string()) .unwrap_or_else(|_| member.value.clone()), status: member.status, }); } } Type::Enumeration(Enumeration { members, .. }) => { for member in members { // GLib assumes that enums are signed integers, // see the GValue machinery around them for example constants.push(CConstant { name: format!("(gint) {}", member.c_identifier), value: member.value.clone(), status: member.status, }); } } _ => {} } } constants.sort_by(|a, b| { fn strip_cast(x: &CConstant) -> &str { if x.name.starts_with("(gint) ") { &x.name[7..] } else if x.name.starts_with("(guint) ") { &x.name[8..] } else { x.name.as_str() } } strip_cast(a).cmp(strip_cast(b)) }); constants } /// Checks if type name is unlikely to correspond to a real C type name. fn is_name_made_up(name: &str) -> bool { // Unnamed types are assigned name during parsing, those names contain an // underscore. name.contains('_') && !name.ends_with("_t") } fn generate_manual_h(env: &Env, path: &Path, w: &mut dyn Write) -> io::Result<()> { info!("Generating file {:?}", path); writeln!( w, "// Feel free to edit this file, it won't be regenerated by gir generator unless removed." )?; writeln!(w)?; let ns = env.library.namespace(MAIN_NAMESPACE); for include in &ns.c_includes { writeln!(w, "#include <{include}>")?; } Ok(()) } #[allow(clippy::write_literal)] fn generate_layout_c( env: &Env, path: &Path, w: &mut dyn Write, ctypes: &[CType], ) -> io::Result<()> { info!("Generating file {:?}", path); general::start_comments(w, &env.config)?; writeln!(w)?; writeln!(w, "#include \"manual.h\"")?; writeln!(w, "#include ")?; writeln!(w, "#include ")?; writeln!(w)?; writeln!(w, "{}", r"int main() {")?; for ctype in ctypes { writeln!( w, " printf(\"%s;%zu;%zu\\n\", \"{ctype}\", sizeof({ctype}), alignof({ctype}));", ctype = ctype.name )?; } writeln!(w, " return 0;")?; writeln!(w, "{}", r"}") } #[allow(clippy::write_literal)] fn generate_constant_c( env: &Env, path: &Path, w: &mut dyn Write, cconsts: &[CConstant], ) -> io::Result<()> { info!("Generating file {:?}", path); general::start_comments(w, &env.config)?; writeln!(w)?; writeln!(w, "#include \"manual.h\"")?; writeln!(w, "#include ")?; writeln!( w, "{}", r#" #define PRINT_CONSTANT(CONSTANT_NAME) \ printf("%s;", #CONSTANT_NAME); \ printf(_Generic((CONSTANT_NAME), \ char *: "%s", \ const char *: "%s", \ char: "%c", \ signed char: "%hhd", \ unsigned char: "%hhu", \ short int: "%hd", \ unsigned short int: "%hu", \ int: "%d", \ unsigned int: "%u", \ long: "%ld", \ unsigned long: "%lu", \ long long: "%lld", \ unsigned long long: "%llu", \ float: "%f", \ double: "%f", \ long double: "%ld"), \ CONSTANT_NAME); \ printf("\n"); "# )?; writeln!(w, "{}", r"int main() {")?; for cconst in cconsts { if cconst.status.ignored() { continue; } writeln!(w, " PRINT_CONSTANT({name});", name = cconst.name,)?; } writeln!(w, " return 0;")?; writeln!(w, "{}", r"}") } #[allow(clippy::write_literal)] fn generate_abi_rs( env: &Env, path: &Path, w: &mut dyn Write, crate_name: &str, ctypes: &[CType], cconsts: &[CConstant], ) -> io::Result<()> { let ns = env.library.namespace(MAIN_NAMESPACE); let mut package_names = ns.package_names.join("\", \""); if !package_names.is_empty() { package_names = format!("\"{package_names}\""); } info!("Generating file {:?}", path); general::start_comments(w, &env.config)?; writeln!(w)?; writeln!(w, "#![cfg(unix)]")?; writeln!(w)?; if !ctypes.is_empty() { writeln!(w, "use {crate_name}::*;")?; writeln!(w, "use std::mem::{{align_of, size_of}};")?; } writeln!(w, "use std::env;")?; writeln!(w, "use std::error::Error;")?; writeln!(w, "use std::ffi::OsString;")?; writeln!(w, "use std::path::Path;")?; writeln!(w, "use std::process::{{Command, Stdio}};")?; writeln!(w, "use std::str;")?; writeln!(w, "use tempfile::Builder;")?; writeln!(w)?; writeln!(w, "static PACKAGES: &[&str] = &[{package_names}];")?; writeln!( w, "{}", r#" #[derive(Clone, Debug)] struct Compiler { pub args: Vec, } impl Compiler { pub fn new() -> Result> { let mut args = get_var("CC", "cc")?; args.push("-Wno-deprecated-declarations".to_owned()); // For _Generic args.push("-std=c11".to_owned()); // For %z support in printf when using MinGW. args.push("-D__USE_MINGW_ANSI_STDIO".to_owned()); args.extend(get_var("CFLAGS", "")?); args.extend(get_var("CPPFLAGS", "")?); args.extend(pkg_config_cflags(PACKAGES)?); Ok(Self { args }) } pub fn compile(&self, src: &Path, out: &Path) -> Result<(), Box> { let mut cmd = self.to_command(); cmd.arg(src); cmd.arg("-o"); cmd.arg(out); let status = cmd.spawn()?.wait()?; if !status.success() { return Err(format!("compilation command {cmd:?} failed, {status}").into()); } Ok(()) } fn to_command(&self) -> Command { let mut cmd = Command::new(&self.args[0]); cmd.args(&self.args[1..]); cmd } } fn get_var(name: &str, default: &str) -> Result, Box> { match env::var(name) { Ok(value) => Ok(shell_words::split(&value)?), Err(env::VarError::NotPresent) => Ok(shell_words::split(default)?), Err(err) => Err(format!("{name} {err}").into()), } } fn pkg_config_cflags(packages: &[&str]) -> Result, Box> { if packages.is_empty() { return Ok(Vec::new()); } let pkg_config = env::var_os("PKG_CONFIG") .unwrap_or_else(|| OsString::from("pkg-config")); let mut cmd = Command::new(pkg_config); cmd.arg("--cflags"); cmd.args(packages); cmd.stderr(Stdio::inherit()); let out = cmd.output()?; if !out.status.success() { let (status, stdout) = (out.status, String::from_utf8_lossy(&out.stdout)); return Err(format!("command {cmd:?} failed, {status:?}\nstdout: {stdout}").into()); } let stdout = str::from_utf8(&out.stdout)?; Ok(shell_words::split(stdout.trim())?) } #[derive(Copy, Clone, Debug, Eq, PartialEq)] struct Layout { size: usize, alignment: usize, } #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] struct Results { /// Number of successfully completed tests. passed: usize, /// Total number of failed tests (including those that failed to compile). failed: usize, } impl Results { fn record_passed(&mut self) { self.passed += 1; } fn record_failed(&mut self) { self.failed += 1; } fn summary(&self) -> String { format!("{} passed; {} failed", self.passed, self.failed) } fn expect_total_success(&self) { if self.failed == 0 { println!("OK: {}", self.summary()); } else { panic!("FAILED: {}", self.summary()); }; } } #[test] fn cross_validate_constants_with_c() { let mut c_constants: Vec<(String, String)> = Vec::new(); for l in get_c_output("constant").unwrap().lines() { let (name, value) = l.split_once(';').expect("Missing ';' separator"); c_constants.push((name.to_owned(), value.to_owned())); } let mut results = Results::default(); for ((rust_name, rust_value), (c_name, c_value)) in RUST_CONSTANTS.iter().zip(c_constants.iter()) { if rust_name != c_name { results.record_failed(); eprintln!("Name mismatch:\nRust: {rust_name:?}\nC: {c_name:?}"); continue; } if rust_value != c_value { results.record_failed(); eprintln!( "Constant value mismatch for {rust_name}\nRust: {rust_value:?}\nC: {c_value:?}", ); continue; } results.record_passed(); } results.expect_total_success(); } #[test] fn cross_validate_layout_with_c() { let mut c_layouts = Vec::new(); for l in get_c_output("layout").unwrap().lines() { let (name, value) = l.split_once(';').expect("Missing first ';' separator"); let (size, alignment) = value.split_once(';').expect("Missing second ';' separator"); let size = size.parse().expect("Failed to parse size"); let alignment = alignment.parse().expect("Failed to parse alignment"); c_layouts.push((name.to_owned(), Layout { size, alignment })); } let mut results = Results::default(); for ((rust_name, rust_layout), (c_name, c_layout)) in RUST_LAYOUTS.iter().zip(c_layouts.iter()) { if rust_name != c_name { results.record_failed(); eprintln!("Name mismatch:\nRust: {rust_name:?}\nC: {c_name:?}"); continue; } if rust_layout != c_layout { results.record_failed(); eprintln!( "Layout mismatch for {rust_name}\nRust: {rust_layout:?}\nC: {c_layout:?}", ); continue; } results.record_passed(); } results.expect_total_success(); } fn get_c_output(name: &str) -> Result> { let tmpdir = Builder::new().prefix("abi").tempdir()?; let exe = tmpdir.path().join(name); let c_file = Path::new("tests").join(name).with_extension("c"); let cc = Compiler::new().expect("configured compiler"); cc.compile(&c_file, &exe)?; let mut cmd = Command::new(exe); cmd.stderr(Stdio::inherit()); let out = cmd.output()?; if !out.status.success() { let (status, stdout) = (out.status, String::from_utf8_lossy(&out.stdout)); return Err(format!("command {cmd:?} failed, {status:?}\nstdout: {stdout}").into()); } Ok(String::from_utf8(out.stdout)?) } const RUST_LAYOUTS: &[(&str, Layout)] = &["# )?; for ctype in ctypes { general::cfg_condition(w, ctype.cfg_condition.as_ref(), false, 1)?; writeln!(w, " (\"{ctype}\", Layout {{size: size_of::<{ctype}>(), alignment: align_of::<{ctype}>()}}),", ctype=ctype.name)?; } writeln!( w, "{}", r#"]; const RUST_CONSTANTS: &[(&str, &str)] = &["# )?; for cconst in cconsts { if cconst.status.ignored() { continue; } writeln!( w, " (\"{name}\", \"{value}\"),", name = cconst.name, value = &general::escape_string(&cconst.value) )?; } writeln!( w, "{}", r#"]; "# ) } gir-0.20.5/src/codegen/trait_impls.rs000066400000000000000000000165061475434152100174650ustar00rootroot00000000000000use std::io::{Result, Write}; use crate::{ analysis::{ functions::Info, special_functions::{Infos, Type}, }, codegen::general::{cfg_condition_no_doc, version_condition}, version::Version, Env, }; pub fn generate( w: &mut dyn Write, env: &Env, type_name: &str, functions: &[Info], specials: &Infos, trait_name: Option<&str>, scope_version: Option, cfg_condition: Option<&str>, ) -> Result<()> { for (type_, special_info) in specials.traits().iter() { if let Some(info) = lookup(functions, &special_info.glib_name) { match type_ { Type::Compare => { if !specials.has_trait(Type::Equal) { generate_eq_compare( w, env, type_name, info, trait_name, scope_version, cfg_condition, )?; } generate_ord( w, env, type_name, info, trait_name, scope_version, cfg_condition, )?; } Type::Equal => generate_eq( w, env, type_name, info, trait_name, scope_version, cfg_condition, )?, Type::Display => generate_display( w, env, type_name, info, trait_name, scope_version, cfg_condition, )?, Type::Hash => generate_hash( w, env, type_name, info, trait_name, scope_version, cfg_condition, )?, _ => {} } } } Ok(()) } fn lookup<'a>(functions: &'a [Info], name: &str) -> Option<&'a Info> { functions .iter() .find(|f| !f.status.ignored() && f.glib_name == name) } fn generate_call(func_name: &str, args: &[&str], trait_name: Option<&str>) -> String { let mut args_string = String::new(); let in_trait = trait_name.is_some(); if in_trait { args_string.push_str("self"); } if !args.is_empty() { if in_trait { args_string.push_str(", "); } args_string.push_str(&args.join(", ")); } if let Some(trait_name) = trait_name { format!("{trait_name}::{func_name}({args_string})") } else { format!("self.{func_name}({args_string})") } } fn generate_display( w: &mut dyn Write, env: &Env, type_name: &str, func: &Info, trait_name: Option<&str>, scope_version: Option, cfg_condition: Option<&str>, ) -> Result<()> { use crate::analysis::out_parameters::Mode; writeln!(w)?; let version = Version::if_stricter_than(func.version, scope_version); version_condition(w, env, None, version, false, 0)?; cfg_condition_no_doc(w, cfg_condition, false, 0)?; let call = generate_call(func.codegen_name(), &[], trait_name); let body = if let Mode::Throws(_) = func.outs.mode { format!( "\ if let Ok(val) = {call} {{ f.write_str(val) }} else {{ Err(fmt::Error) }}" ) } else { format!("f.write_str(&{call})") }; writeln!( w, "\ impl std::fmt::Display for {type_name} {{ #[inline] fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {{ {body} }} }}" ) } fn generate_hash( w: &mut dyn Write, env: &Env, type_name: &str, func: &Info, trait_name: Option<&str>, scope_version: Option, cfg_condition: Option<&str>, ) -> Result<()> { writeln!(w)?; let version = Version::if_stricter_than(func.version, scope_version); version_condition(w, env, None, version, false, 0)?; cfg_condition_no_doc(w, cfg_condition, false, 0)?; let call = generate_call(func.codegen_name(), &[], trait_name); writeln!( w, "\ impl std::hash::Hash for {type_name} {{ #[inline] fn hash(&self, state: &mut H) where H: std::hash::Hasher {{ std::hash::Hash::hash(&{call}, state) }} }}" ) } fn generate_eq( w: &mut dyn Write, env: &Env, type_name: &str, func: &Info, trait_name: Option<&str>, scope_version: Option, cfg_condition: Option<&str>, ) -> Result<()> { writeln!(w)?; let version = Version::if_stricter_than(func.version, scope_version); version_condition(w, env, None, version, false, 0)?; cfg_condition_no_doc(w, cfg_condition, false, 0)?; let call = generate_call(func.codegen_name(), &["other"], trait_name); writeln!( w, "\ impl PartialEq for {type_name} {{ #[inline] fn eq(&self, other: &Self) -> bool {{ {call} }} }}" )?; version_condition(w, env, None, version, false, 0)?; cfg_condition_no_doc(w, cfg_condition, false, 0)?; writeln!(w)?; writeln!( w, "\ impl Eq for {type_name} {{}}" ) } fn generate_eq_compare( w: &mut dyn Write, env: &Env, type_name: &str, func: &Info, trait_name: Option<&str>, scope_version: Option, cfg_condition: Option<&str>, ) -> Result<()> { writeln!(w)?; let version = Version::if_stricter_than(func.version, scope_version); version_condition(w, env, None, version, false, 0)?; cfg_condition_no_doc(w, cfg_condition, false, 0)?; let call = generate_call(func.codegen_name(), &["other"], trait_name); writeln!( w, "\ impl PartialEq for {type_name} {{ #[inline] fn eq(&self, other: &Self) -> bool {{ {call} == 0 }} }}" )?; version_condition(w, env, None, version, false, 0)?; cfg_condition_no_doc(w, cfg_condition, false, 0)?; writeln!(w)?; writeln!( w, "\ impl Eq for {type_name} {{}}" ) } fn generate_ord( w: &mut dyn Write, env: &Env, type_name: &str, func: &Info, trait_name: Option<&str>, scope_version: Option, cfg_condition: Option<&str>, ) -> Result<()> { writeln!(w)?; let version = Version::if_stricter_than(func.version, scope_version); version_condition(w, env, None, version, false, 0)?; cfg_condition_no_doc(w, cfg_condition, false, 0)?; let call = generate_call(func.codegen_name(), &["other"], trait_name); writeln!( w, "\ impl PartialOrd for {type_name} {{ #[inline] fn partial_cmp(&self, other: &Self) -> Option {{ Some(self.cmp(other)) }} }}" )?; version_condition(w, env, None, version, false, 0)?; cfg_condition_no_doc(w, cfg_condition, false, 0)?; writeln!(w)?; writeln!( w, "\ impl Ord for {type_name} {{ #[inline] fn cmp(&self, other: &Self) -> std::cmp::Ordering {{ {call}.cmp(&0) }} }}" ) } gir-0.20.5/src/codegen/trampoline.rs000066400000000000000000000173051475434152100173060ustar00rootroot00000000000000use std::{ fmt::Write as FWrite, io::{Result, Write}, }; use log::error; use super::{ return_value::ToReturnValue, trampoline_from_glib::TrampolineFromGlib, trampoline_to_glib::TrampolineToGlib, }; use crate::{ analysis::{ bounds::Bounds, ffi_type::ffi_type, ref_mode::RefMode, rust_type::RustType, trampoline_parameters::*, trampolines::Trampoline, try_from_glib::TryFromGlib, }, consts::TYPE_PARAMETERS_START, env::Env, library, nameutil::{use_glib_if_needed, use_glib_type, use_gtk_type}, traits::IntoString, writer::primitives::tabs, }; pub fn generate( w: &mut dyn Write, env: &Env, analysis: &Trampoline, in_trait: bool, indent: usize, ) -> Result<()> { let (self_bound, fn_self_bound) = in_trait .then(|| { ( format!("{}: IsA<{}>, ", TYPE_PARAMETERS_START, analysis.type_name), Some(TYPE_PARAMETERS_START.to_string()), ) }) .unwrap_or_default(); let prepend = tabs(indent); let params_str = trampoline_parameters(env, analysis); let func_str = func_string(env, analysis, fn_self_bound, true); let ret_str = trampoline_returns(env, analysis); writeln!( w, "{}unsafe extern \"C\" fn {}<{}F: {}>({}, f: {}){} {{", prepend, analysis.name, self_bound, func_str, params_str, use_glib_if_needed(env, "ffi::gpointer"), ret_str, )?; writeln!(w, "{prepend}\tlet f: &F = &*(f as *const F);")?; transformation_vars(w, env, analysis, &prepend)?; let call = trampoline_call_func(env, analysis, in_trait); writeln!(w, "{prepend}\t{call}")?; writeln!(w, "{prepend}}}")?; Ok(()) } pub fn func_string( env: &Env, analysis: &Trampoline, replace_self_bound: Option>, closure: bool, ) -> String { let param_str = func_parameters(env, analysis, replace_self_bound, closure); let return_str = func_returns(env, analysis); if closure { let concurrency_str = match analysis.concurrency { // If an object can be Send to other threads, this means that // our callback will be called from whatever thread the object // is sent to. But it will only be ever owned by a single thread // at a time, so signals can only be emitted from one thread at // a time and Sync is not needed library::Concurrency::Send => " + Send", // If an object is Sync, it can be shared between threads, and as // such our callback can be called from arbitrary threads and needs // to be Send *AND* Sync library::Concurrency::SendSync => " + Send + Sync", library::Concurrency::None => "", }; format!("Fn({param_str}){return_str}{concurrency_str} + 'static") } else { format!("({param_str}){return_str}",) } } fn func_parameters( env: &Env, analysis: &Trampoline, replace_self_bound: Option>, closure: bool, ) -> String { let mut param_str = String::with_capacity(100); for (pos, par) in analysis.parameters.rust_parameters.iter().enumerate() { if pos == 0 { if let Some(replace_self_bound) = &replace_self_bound { param_str.push_str(par.ref_mode.for_rust_type()); param_str.push_str(replace_self_bound.as_ref()); continue; } } else { param_str.push_str(", "); if !closure { write!(param_str, "{}: ", par.name).unwrap(); } } let s = func_parameter(env, par, &analysis.bounds); param_str.push_str(&s); } param_str } fn func_parameter(env: &Env, par: &RustParameter, bounds: &Bounds) -> String { // TODO: restore mutable support let ref_mode = if par.ref_mode == RefMode::ByRefMut { RefMode::ByRef } else { par.ref_mode }; match bounds.get_parameter_bound(&par.name) { // TODO: ASYNC?? Some(bound) => bound.full_type_parameter_reference(ref_mode, par.nullable, false), // TODO // Some((None, _)) => panic!("Trampoline expects type name"), None => RustType::builder(env, par.typ) .direction(par.direction) .nullable(par.nullable) .ref_mode(ref_mode) .try_build_param() .into_string(), } } fn func_returns(env: &Env, analysis: &Trampoline) -> String { if analysis.ret.typ == Default::default() { String::new() } else if analysis.inhibit { format!(" -> {inhibit}", inhibit = use_glib_type(env, "Propagation")) } else if let Some(return_type) = analysis .ret .to_return_value(env, &TryFromGlib::default(), true) { format!(" -> {return_type}") } else { String::new() } } fn trampoline_parameters(env: &Env, analysis: &Trampoline) -> String { if analysis.is_notify { return format!( "{}, _param_spec: {}", trampoline_parameter(env, &analysis.parameters.c_parameters[0]), use_glib_if_needed(env, "ffi::gpointer"), ); } let mut parameter_strs: Vec = Vec::new(); for par in &analysis.parameters.c_parameters { let par_str = trampoline_parameter(env, par); parameter_strs.push(par_str); } parameter_strs.join(", ") } fn trampoline_parameter(env: &Env, par: &CParameter) -> String { let ffi_type = ffi_type(env, par.typ, &par.c_type); format!("{}: {}", par.name, ffi_type.into_string()) } fn trampoline_returns(env: &Env, analysis: &Trampoline) -> String { if analysis.ret.typ == Default::default() { String::new() } else { let ffi_type = ffi_type(env, analysis.ret.typ, &analysis.ret.c_type); format!(" -> {}", ffi_type.into_string()) } } fn transformation_vars( w: &mut dyn Write, env: &Env, analysis: &Trampoline, prepend: &str, ) -> Result<()> { use crate::analysis::trampoline_parameters::TransformationType::*; for transform in &analysis.parameters.transformations { match transform.transformation { None => (), Borrow => (), TreePath => { let c_par = &analysis.parameters.c_parameters[transform.ind_c]; writeln!( w, "{}\tlet {} = from_glib_full({}({}));", prepend, transform.name, use_gtk_type(env, "ffi::gtk_tree_path_new_from_string"), c_par.name )?; } } } Ok(()) } fn trampoline_call_func(env: &Env, analysis: &Trampoline, in_trait: bool) -> String { let params = trampoline_call_parameters(env, analysis, in_trait); let ret = if analysis.ret.typ == Default::default() { String::new() } else { analysis.ret.trampoline_to_glib(env) }; format!("f({params}){ret}") } fn trampoline_call_parameters(env: &Env, analysis: &Trampoline, in_trait: bool) -> String { let mut need_downcast = in_trait; let mut parameter_strs: Vec = Vec::new(); for (ind, par) in analysis.parameters.rust_parameters.iter().enumerate() { let transformation = match analysis.parameters.get(ind) { Some(transformation) => transformation, None => { error!("No transformation for {}", par.name); continue; } }; let par_str = transformation.trampoline_from_glib(env, need_downcast, *par.nullable); parameter_strs.push(par_str); need_downcast = false; // Only downcast first parameter } parameter_strs.join(", ") } gir-0.20.5/src/codegen/trampoline_from_glib.rs000066400000000000000000000062161475434152100213250ustar00rootroot00000000000000use crate::{ analysis::{rust_type::RustType, trampoline_parameters::Transformation}, env::Env, library, nameutil::is_gstring, traits::*, }; pub trait TrampolineFromGlib { fn trampoline_from_glib(&self, env: &Env, need_downcast: bool, nullable: bool) -> String; } impl TrampolineFromGlib for Transformation { fn trampoline_from_glib(&self, env: &Env, need_downcast: bool, nullable: bool) -> String { use crate::analysis::conversion_type::ConversionType::*; let need_type_name = need_downcast || is_need_type_name(env, self.typ); match self.conversion_type { Direct => self.name.clone(), Scalar | Option | Result { .. } => format!("from_glib({})", self.name), Borrow | Pointer => { let is_borrow = self.conversion_type == Borrow; let need_type_name = need_type_name || (is_borrow && nullable); let (mut left, mut right) = from_glib_xxx(self.transfer, is_borrow); let type_name = RustType::try_new(env, self.typ).into_string(); if need_type_name { if is_borrow && nullable { left = format!("Option::<{type_name}>::{left}"); } else { left = format!("{type_name}::{left}"); } } if !nullable { left = format!( "{}{}", if need_downcast && is_borrow { "" } else { "&" }, left ); } else if nullable && is_borrow { if is_gstring(&type_name) { right = format!("{right}.as_ref().as_ref().map(|s| s.as_str())"); } else { right = format!("{right}.as_ref().as_ref()"); } } else if is_gstring(&type_name) { right = format!("{right}.as_ref().map(|s| s.as_str())"); } else { right = format!("{right}.as_ref()"); } if need_downcast && is_borrow { right = format!("{right}.unsafe_cast_ref()"); } else if need_downcast { right = format!("{right}.unsafe_cast()"); } format!("{}{}{}", left, self.name, right) } Unknown => format!("/*Unknown conversion*/{}", self.name), } } } pub fn from_glib_xxx(transfer: library::Transfer, is_borrow: bool) -> (String, String) { use crate::library::Transfer::*; match transfer { None if is_borrow => ("from_glib_borrow(".into(), ")".into()), None => ("from_glib_none(".into(), ")".into()), Full => ("from_glib_full(".into(), ")".into()), Container => ("from_glib_container(".into(), ")".into()), } } fn is_need_type_name(env: &Env, type_id: library::TypeId) -> bool { if type_id.ns_id == library::INTERNAL_NAMESPACE { use crate::library::{Basic::*, Type::*}; matches!(env.type_(type_id), Basic(Utf8 | Filename | OsString)) } else { false } } gir-0.20.5/src/codegen/trampoline_to_glib.rs000066400000000000000000000017341475434152100210040ustar00rootroot00000000000000use crate::{analysis::conversion_type::ConversionType, env, library}; pub trait TrampolineToGlib { fn trampoline_to_glib(&self, env: &env::Env) -> String; } impl TrampolineToGlib for library::Parameter { fn trampoline_to_glib(&self, env: &env::Env) -> String { use crate::analysis::conversion_type::ConversionType::*; match ConversionType::of(env, self.typ) { Direct => String::new(), Scalar | Option | Result { .. } => ".into_glib()".to_owned(), Pointer => to_glib_xxx(self.transfer).to_owned(), Borrow => "/*Not applicable conversion Borrow*/".to_owned(), Unknown => "/*Unknown conversion*/".to_owned(), } } } fn to_glib_xxx(transfer: library::Transfer) -> &'static str { use crate::library::Transfer::*; match transfer { None => "/*Not checked*/.to_glib_none().0", Full => ".to_glib_full()", Container => "/*Not checked*/.to_glib_container().0", } } gir-0.20.5/src/codegen/translate_from_glib.rs000066400000000000000000000155271475434152100211550ustar00rootroot00000000000000use crate::{ analysis::{ self, conversion_type::ConversionType, rust_type::RustType, try_from_glib::TryFromGlib, }, chunk::conversion_from_glib::Mode, env::Env, library, nameutil::use_glib_type, traits::*, }; pub trait TranslateFromGlib { fn translate_from_glib_as_function( &self, env: &Env, array_length: Option<&str>, ) -> (String, String); } impl TranslateFromGlib for Mode { fn translate_from_glib_as_function( &self, env: &Env, array_length: Option<&str>, ) -> (String, String) { use crate::analysis::conversion_type::ConversionType::*; match ConversionType::of(env, self.typ) { Direct => (String::new(), String::new()), Scalar => match env.library.type_(self.typ) { library::Type::Basic(library::Basic::UniChar) => ( "std::convert::TryFrom::try_from(".into(), ").expect(\"conversion from an invalid Unicode value attempted\")".into(), ), _ => ("from_glib(".into(), ")".into()), }, Option => { let (pre, post) = match &self.try_from_glib { TryFromGlib::Option => ("from_glib(", ")"), TryFromGlib::OptionMandatory => ( "try_from_glib(", ").expect(\"mandatory glib value is None\")", ), other => panic!("Unexpected {other:?} for ConversionType::Option"), }; (pre.to_string(), post.to_string()) } Result { .. } => { let (pre, post) = match &self.try_from_glib { TryFromGlib::Result { .. } => ("try_from_glib(", ")"), TryFromGlib::ResultInfallible { .. } => ( "try_from_glib(", ").unwrap_or_else(|err| panic!(\"infallible {}\", err))", ), other => panic!("Unexpected {other:?} for ConversionType::Result"), }; (pre.to_string(), post.to_string()) } Pointer => { let trans = from_glib_xxx(self.transfer, array_length); match env.type_(self.typ) { library::Type::List(..) | library::Type::SList(..) | library::Type::PtrArray(..) | library::Type::CArray(..) => { if array_length.is_some() { (format!("FromGlibContainer::{}", trans.0), trans.1) } else { (format!("FromGlibPtrContainer::{}", trans.0), trans.1) } } _ => trans, } } Borrow => ("/*TODO: conversion Borrow*/".into(), String::new()), Unknown => ("/*Unknown conversion*/".into(), String::new()), } } } impl TranslateFromGlib for analysis::return_value::Info { fn translate_from_glib_as_function( &self, env: &Env, array_length: Option<&str>, ) -> (String, String) { match self.parameter { Some(ref par) => match self.base_tid { Some(tid) => { let rust_type = RustType::builder(env, tid) .direction(par.lib_par.direction) .try_from_glib(&par.try_from_glib) .try_build(); let from_glib_xxx = from_glib_xxx(par.lib_par.transfer, None); let prefix = if *par.lib_par.nullable { format!("Option::<{}>::{}", rust_type.into_string(), from_glib_xxx.0) } else { format!("{}::{}", rust_type.into_string(), from_glib_xxx.0) }; let suffix_function = if *par.lib_par.nullable { "map(|o| o.unsafe_cast())" } else { "unsafe_cast()" }; if let Some(ref msg) = self.nullable_return_is_error { assert!(*par.lib_par.nullable); ( prefix, format!( "{}.{}.ok_or_else(|| {}(\"{}\"))", from_glib_xxx.1, suffix_function, use_glib_type(env, "bool_error!"), msg ), ) } else { (prefix, format!("{}.{}", from_glib_xxx.1, suffix_function)) } } None if self.bool_return_is_error.is_some() => ( use_glib_type(env, "result_from_gboolean!("), format!(", \"{}\")", self.bool_return_is_error.as_ref().unwrap()), ), None if self.nullable_return_is_error.is_some() => { let res = Mode::from(par).translate_from_glib_as_function(env, array_length); if let Some(ref msg) = self.nullable_return_is_error { assert!(*par.lib_par.nullable); ( format!("Option::<_>::{}", res.0), format!( "{}.ok_or_else(|| {}(\"{}\"))", res.1, use_glib_type(env, "bool_error!"), msg ), ) } else { res } } None => Mode::from(par).translate_from_glib_as_function(env, array_length), }, None => (String::new(), ";".into()), } } } fn from_glib_xxx(transfer: library::Transfer, array_length: Option<&str>) -> (String, String) { use crate::library::Transfer; let good_print = |name: &str| format!(", {name}.assume_init() as _)"); match (transfer, array_length) { (Transfer::None, None) => ("from_glib_none(".into(), ")".into()), (Transfer::Full, None) => ("from_glib_full(".into(), ")".into()), (Transfer::Container, None) => ("from_glib_container(".into(), ")".into()), (Transfer::None, Some(array_length_name)) => { ("from_glib_none_num(".into(), good_print(array_length_name)) } (Transfer::Full, Some(array_length_name)) => { ("from_glib_full_num(".into(), good_print(array_length_name)) } (Transfer::Container, Some(array_length_name)) => ( "from_glib_container_num(".into(), good_print(array_length_name), ), } } gir-0.20.5/src/codegen/translate_to_glib.rs000066400000000000000000000066301475434152100206270ustar00rootroot00000000000000use crate::{ analysis::{function_parameters::TransformationType, ref_mode::RefMode}, library::Transfer, }; pub trait TranslateToGlib { fn translate_to_glib(&self) -> String; } impl TranslateToGlib for TransformationType { fn translate_to_glib(&self) -> String { use self::TransformationType::*; match *self { ToGlibDirect { ref name } => name.clone(), ToGlibScalar { ref name, needs_into, .. } => { let pre_into = if needs_into { ".into()" } else { "" }; format!("{}{}{}", name, pre_into, ".into_glib()") } ToGlibPointer { ref name, instance_parameter, transfer, ref_mode, ref to_glib_extra, ref pointer_cast, ref explicit_target_type, in_trait, move_, .. } => { let (left, right) = to_glib_xxx(transfer, ref_mode, explicit_target_type, move_); if instance_parameter { format!( "{}self{}{}{}", left, if in_trait { to_glib_extra } else { "" }, right, pointer_cast ) } else { format!("{left}{name}{to_glib_extra}{right}{pointer_cast}") } } ToGlibBorrow => "/*Not applicable conversion Borrow*/".to_owned(), ToGlibUnknown { ref name } => format!("/*Unknown conversion*/{name}"), ToSome(ref name) => format!("Some({name})"), IntoRaw(ref name) => format!("Box_::into_raw({name}) as *mut _"), _ => unreachable!("Unexpected transformation type {:?}", self), } } } fn to_glib_xxx( transfer: Transfer, ref_mode: RefMode, explicit_target_type: &str, move_: bool, ) -> (String, &'static str) { use self::Transfer::*; match transfer { None => { match ref_mode { RefMode::None => (String::new(), ".to_glib_none_mut().0"), // unreachable!(), RefMode::ByRef => match (move_, explicit_target_type.is_empty()) { (true, true) => (String::new(), ".into_glib_ptr()"), (true, false) => ( format!("ToGlibPtr::<{explicit_target_type}>::into_glib_ptr("), ")", ), (false, true) => (String::new(), ".to_glib_none().0"), (false, false) => ( format!("ToGlibPtr::<{explicit_target_type}>::to_glib_none("), ").0", ), }, RefMode::ByRefMut => (String::new(), ".to_glib_none_mut().0"), RefMode::ByRefImmut => ("mut_override(".into(), ".to_glib_none().0)"), RefMode::ByRefConst => ("const_override(".into(), ".to_glib_none().0)"), RefMode::ByRefFake => (String::new(), ""), // unreachable!(), } } Full => { if move_ { ("".into(), ".into_glib_ptr()") } else { ("".into(), ".to_glib_full()") } } Container => ("".into(), ".to_glib_container().0"), } } gir-0.20.5/src/codegen/visibility.rs000066400000000000000000000024541475434152100173220ustar00rootroot00000000000000use std::{fmt, str::FromStr}; #[derive(Default, Debug, Clone, Copy, Eq, PartialEq)] pub enum Visibility { #[default] Public, Crate, Super, Private, } impl Visibility { pub fn is_public(self) -> bool { self == Self::Public } pub fn export_visibility(self) -> &'static str { match self { Self::Public => "pub", Self::Private => "", Self::Crate => "pub(crate)", Self::Super => "pub(super)", } } } impl fmt::Display for Visibility { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(self.export_visibility()) } } #[derive(Debug)] pub struct ParseVisibilityError(String); impl std::error::Error for ParseVisibilityError {} impl fmt::Display for ParseVisibilityError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } } impl FromStr for Visibility { type Err = ParseVisibilityError; fn from_str(s: &str) -> Result { match s { "pub" => Ok(Self::Public), "super" => Ok(Self::Super), "private" => Ok(Self::Private), "crate" => Ok(Self::Crate), e => Err(ParseVisibilityError(format!("Wrong visibility type '{e}'"))), } } } gir-0.20.5/src/config/000077500000000000000000000000001475434152100144215ustar00rootroot00000000000000gir-0.20.5/src/config/child_properties.rs000066400000000000000000000152031475434152100203270ustar00rootroot00000000000000use log::error; use toml::Value; use super::{error::TomlHelper, parsable::Parse}; #[derive(Clone, Debug)] pub struct ChildProperty { pub name: String, pub rename_getter: Option, pub type_name: String, pub doc_hidden: bool, pub generate_doc: bool, } impl Parse for ChildProperty { fn parse(toml: &Value, object_name: &str) -> Option { let name = toml .lookup("name") .and_then(Value::as_str) .map(ToOwned::to_owned); let name = if let Some(name) = name { name } else { error!("No child property name for `{}`", object_name); return None; }; toml.check_unwanted( &["name", "type", "doc_hidden", "rename_getter"], &format!("child property {object_name}"), ); let type_name = toml .lookup("type") .and_then(Value::as_str) .map(ToOwned::to_owned); let type_name = if let Some(type_name) = type_name { type_name } else { error!( "No type for child property `{}` for `{}`", name, object_name ); return None; }; let doc_hidden = toml .lookup("doc_hidden") .and_then(Value::as_bool) .unwrap_or(false); let rename_getter = toml .lookup("rename_getter") .and_then(Value::as_str) .map(ToOwned::to_owned); let generate_doc = toml .lookup("generate_doc") .and_then(Value::as_bool) .unwrap_or(true); Some(Self { name, rename_getter, type_name, doc_hidden, generate_doc, }) } } #[derive(Clone, Debug)] pub struct ChildProperties { pub child_name: Option, pub child_type: Option, pub properties: Vec, } impl Parse for ChildProperties { fn parse(toml_object: &Value, object_name: &str) -> Option { let child_name = toml_object .lookup("child_name") .and_then(Value::as_str) .map(ToOwned::to_owned); let child_type = toml_object .lookup("child_type") .and_then(Value::as_str) .map(ToOwned::to_owned); let mut properties: Vec = Vec::new(); if let Some(configs) = toml_object.lookup("child_prop").and_then(Value::as_array) { for config in configs { if let Some(item) = ChildProperty::parse(config, object_name) { properties.push(item); } } } if !properties.is_empty() { Some(Self { child_name, child_type, properties, }) } else { if child_name.is_some() { error!("`{}` has child_name but no child_prop's", object_name); } if child_type.is_some() { error!("`{}` has child_type but no child_prop's", object_name); } None } } } #[cfg(test)] mod tests { use super::{super::parsable::Parse, *}; fn toml(input: &str) -> ::toml::Value { let value = ::toml::from_str(input); assert!(value.is_ok()); value.unwrap() } #[test] fn child_property_parse() { let toml = toml( r#" name = "prop" type = "prop_type" "#, ); let child = ChildProperty::parse(&toml, "a").unwrap(); assert_eq!("prop", child.name); assert_eq!("prop_type", child.type_name); } #[test] fn child_property_parse_generate_doc() { let r = toml( r#" name = "prop" type = "prop_type" generate_doc = false "#, ); let child = ChildProperty::parse(&r, "a").unwrap(); assert!(!child.generate_doc); // Ensure that the default value is "true". let r = toml( r#" name = "prop" type = "prop_type" "#, ); let child = ChildProperty::parse(&r, "a").unwrap(); assert!(child.generate_doc); } #[test] fn child_property_parse_not_all() { let tml = toml( r#" name = "prop" "#, ); assert!(ChildProperty::parse(&tml, "a").is_none()); let tml = toml( r#" type_name = "prop_type" "#, ); assert!(ChildProperty::parse(&tml, "a").is_none()); } #[test] fn child_properties_parse() { let toml = toml( r#" child_name = "child_name" child_type = "child_type" [[child_prop]] name = "prop" type = "prop_type" [[child_prop]] name = "prop2" type = "prop_type2" "#, ); let props = ChildProperties::parse(&toml, "a").unwrap(); assert_eq!(Some("child_name".into()), props.child_name); assert_eq!(Some("child_type".into()), props.child_type); assert_eq!(2, props.properties.len()); assert_eq!("prop", props.properties[0].name); assert_eq!("prop_type", props.properties[0].type_name); assert_eq!("prop2", props.properties[1].name); assert_eq!("prop_type2", props.properties[1].type_name); } #[test] fn child_property_no_parse_without_children() { let toml = toml( r#" child_name = "child_name" child_type = "child_type" "#, ); let props = ChildProperties::parse(&toml, "a"); assert!(props.is_none()); } #[test] fn child_properties_parse_without_child_type_name() { let toml = toml( r#" [[child_prop]] name = "prop" type = "prop_type" "#, ); let props = ChildProperties::parse(&toml, "a").unwrap(); assert_eq!(None, props.child_name); assert_eq!(None, props.child_type); assert_eq!(1, props.properties.len()); } #[test] fn child_properties_parse_without_child_type() { let toml = toml( r#" child_name = "child_name" [[child_prop]] name = "prop" type = "prop_type" "#, ); let props = ChildProperties::parse(&toml, "a").unwrap(); assert_eq!(Some("child_name".into()), props.child_name); assert_eq!(None, props.child_type); assert_eq!(1, props.properties.len()); } #[test] fn child_properties_parse_without_child_name() { let toml = toml( r#" child_type = "child_type" [[child_prop]] name = "prop" type = "prop_type" "#, ); let props = ChildProperties::parse(&toml, "a").unwrap(); assert_eq!(None, props.child_name); assert_eq!(Some("child_type".into()), props.child_type); assert_eq!(1, props.properties.len()); } } gir-0.20.5/src/config/config.rs000066400000000000000000000446031475434152100162430ustar00rootroot00000000000000use std::{ collections::HashMap, fs, ops::Index, path::{Component, Path, PathBuf}, str::FromStr, }; use log::warn; use super::{ external_libraries::{read_external_libraries, ExternalLibrary}, gobjects, WorkMode, }; use crate::{ analysis::namespaces::{self, Namespace, NsId}, config::error::TomlHelper, env::Env, git::{repo_hash, repo_remote_url, toplevel}, library::{self, Library}, nameutil::set_crate_name_overrides, version::Version, }; /// Performs canonicalization by removing `foo/../` and `./` components /// from `path`, without hitting the file system. It does not turn relative /// paths into absolute paths. fn normalize_path(path: impl AsRef) -> PathBuf { let mut parts: Vec> = vec![]; for component in path.as_ref().components() { match (component, parts.last()) { (Component::CurDir, _) | (Component::ParentDir, Some(Component::RootDir)) => {} (Component::ParentDir, None | Some(Component::ParentDir)) => { parts.push(Component::ParentDir); } (Component::ParentDir, Some(_)) => { parts .pop() .expect("Cannot navigate outside of base directory!"); } (c, _) => parts.push(c), } } parts.iter().collect() } #[test] fn test_normalize_path() { assert_eq!(normalize_path("foo/../bar").as_os_str(), "bar"); assert_eq!(normalize_path("foo/./bar").as_os_str(), "foo/bar"); assert_eq!(normalize_path("./foo").as_os_str(), "foo"); assert_eq!( normalize_path("foo/../bar/baz/../qux").as_os_str(), "bar/qux" ); assert_eq!( normalize_path("foo/bar/baz/../../qux").as_os_str(), "foo/qux" ); assert_eq!(normalize_path("/foo/../bar").as_os_str(), "/bar"); assert_eq!(normalize_path("/../bar").as_os_str(), "/bar"); assert_eq!(normalize_path("foo/../../bar").as_os_str(), "../bar"); } #[derive(Debug)] pub struct GirVersion { pub gir_dir: PathBuf, hash: Option, url: Option, } impl GirVersion { fn new(gir_dir: impl AsRef) -> Self { let gir_dir = normalize_path(gir_dir); let is_submodule = toplevel(&gir_dir) != Path::new(".").canonicalize().ok(); Self { hash: is_submodule.then(|| repo_hash(&gir_dir).unwrap_or_else(|| "???".to_string())), url: is_submodule.then(|| repo_remote_url(&gir_dir)).flatten(), gir_dir, } } pub fn get_hash(&self) -> Option<&str> { self.hash.as_deref() } pub fn get_repository_url(&self) -> Option<&str> { self.url.as_deref() } } #[derive(Debug)] pub struct Config { pub work_mode: WorkMode, pub girs_dirs: Vec, // Version in girs_dirs, detected by git pub girs_version: Vec, pub library_name: String, pub library_version: String, pub target_path: PathBuf, /// Path where files generated in normal and sys mode pub auto_path: PathBuf, pub doc_target_path: PathBuf, pub external_libraries: Vec, pub objects: gobjects::GObjects, pub min_cfg_version: Version, pub use_gi_docgen: bool, pub make_backup: bool, pub generate_safety_asserts: bool, pub deprecate_by_min_version: bool, pub show_statistics: bool, pub concurrency: library::Concurrency, pub single_version_file: Option, pub trust_return_value_nullability: bool, pub disable_format: bool, pub split_build_rs: bool, pub extra_versions: Vec, pub lib_version_overrides: HashMap, pub feature_dependencies: HashMap>, /// An url that will be inserted into the docs as link that links /// to another doc source, for example when builds on docs.rs /// are limited due to license issues. pub external_docs_url: Option, } impl Config { pub fn new<'a, S, W>( config_file: S, work_mode: W, girs_dirs: &[String], library_name: S, library_version: S, target_path: S, doc_target_path: S, make_backup: bool, show_statistics: bool, disable_format: bool, ) -> Result where S: Into>, W: Into>, { let config_file: PathBuf = match config_file.into() { Some("") | None => "Gir.toml", Some(a) => a, } .into(); let config_dir = match config_file.parent() { Some(path) => path.into(), None => PathBuf::new(), }; let toml = match read_toml(&config_file) { Ok(toml) => toml, Err(e) => { return Err(format!( "Error while reading \"{}\": {}", config_file.display(), e )) } }; let overrides = read_crate_name_overrides(&toml); if !overrides.is_empty() { set_crate_name_overrides(overrides); } let work_mode = match work_mode.into() { Some(w) => w, None => { let s = match toml.lookup_str("options.work_mode", "No options.work_mode") { Ok(s) => s, Err(e) => { return Err(format!( "Invalid toml file \"{}\": {}", config_file.display(), e )) } }; WorkMode::from_str(s)? } }; let mut girs_dirs: Vec = girs_dirs .iter() .filter(|x| !x.is_empty()) .map(|x| PathBuf::from(&x)) .collect(); if girs_dirs.is_empty() { let dirs = toml.lookup_vec("options.girs_directories", "No options.girs_directories")?; for dir in dirs { let dir = dir.as_str().ok_or_else(|| { "options.girs_dirs expected to be array of string".to_string() })?; girs_dirs.push(config_dir.join(dir)); } } let mut girs_version = girs_dirs.iter().map(GirVersion::new).collect::>(); girs_version.sort_by(|a, b| a.gir_dir.partial_cmp(&b.gir_dir).unwrap()); let (library_name, library_version) = match (library_name.into(), library_version.into()) { (Some(""), Some("")) | (None, None) => ( toml.lookup_str("options.library", "No options.library")? .to_owned(), toml.lookup_str("options.version", "No options.version")? .to_owned(), ), (Some(""), Some(_)) | (Some(_), Some("")) | (None, Some(_)) | (Some(_), None) => { return Err("Library and version can not be specified separately".to_owned()) } (Some(a), Some(b)) => (a.to_owned(), b.to_owned()), }; let target_path: PathBuf = match target_path.into() { Some("") | None => { let path = toml.lookup_str("options.target_path", "No target path specified")?; config_dir.join(path) } Some(a) => a.into(), }; let generate_builder: bool = toml .lookup("options.generate_builder") .and_then(|a| a.as_bool()) .unwrap_or(false); let auto_path = match toml.lookup("options.auto_path") { Some(p) => target_path.join(p.as_result_str("options.auto_path")?), None if work_mode == WorkMode::Normal => target_path.join("src").join("auto"), None => target_path.join("src"), }; if work_mode == WorkMode::Normal && auto_path.exists() { std::fs::remove_dir_all(&auto_path) .map_err(|e| format!("remove_dir_all failed: {e:?}"))?; } let doc_target_path: PathBuf = match doc_target_path.into() { Some("") | None => match toml.lookup("options.doc_target_path") { Some(p) => config_dir.join(p.as_result_str("options.doc_target_path")?), None => target_path.join("vendor.md"), }, Some(p) => config_dir.join(p), }; let concurrency = match toml.lookup("options.concurrency") { Some(v) => v.as_result_str("options.concurrency")?.parse()?, None => Default::default(), }; let trust_return_value_nullability = match toml.lookup("options.trust_return_value_nullability") { Some(v) => v.as_result_bool("options.trust_return_value_nullability")?, None => false, }; // options.concurrency is the default of all objects if nothing // else is configured let mut objects = toml .lookup("object") .map(|t| { gobjects::parse_toml( t, concurrency, generate_builder, trust_return_value_nullability, ) }) .unwrap_or_default(); gobjects::parse_status_shorthands( &mut objects, &toml, concurrency, generate_builder, trust_return_value_nullability, ); let external_libraries = read_external_libraries(&toml)?; let min_cfg_version = match toml.lookup("options.min_cfg_version") { Some(v) => v.as_result_str("options.min_cfg_version")?.parse()?, None => Default::default(), }; let use_gi_docgen = match toml.lookup("options.use_gi_docgen") { Some(v) => v.as_result_bool("options.use_gi_docgen")?, None => false, }; let generate_safety_asserts = match toml.lookup("options.generate_safety_asserts") { Some(v) => v.as_result_bool("options.generate_safety_asserts")?, None => false, }; let deprecate_by_min_version = match toml.lookup("options.deprecate_by_min_version") { Some(v) => v.as_result_bool("options.deprecate_by_min_version")?, None => false, }; let single_version_file = match toml.lookup("options.single_version_file") { Some(v) => match v.as_result_bool("options.single_version_file") { Ok(false) => None, Ok(true) => Some(make_single_version_file(None, &target_path)), Err(_) => match v.as_str() { Some(p) => Some(make_single_version_file(Some(p), &target_path)), None => return Err("single_version_file must be bool or string path".into()), }, }, None => None, }; let disable_format: bool = if disable_format { true } else { match toml.lookup("options.disable_format") { Some(v) => v.as_result_bool("options.disable_format")?, None => true, } }; let split_build_rs = match toml.lookup("options.split_build_rs") { Some(v) => v.as_result_bool("options.split_build_rs")?, None => false, }; let extra_versions = read_extra_versions(&toml)?; let lib_version_overrides = read_lib_version_overrides(&toml)?; let feature_dependencies = read_feature_dependencies(&toml)?; let external_docs_url = read_external_docs_url(&toml)?; Ok(Self { work_mode, girs_dirs, girs_version, library_name, library_version, target_path, auto_path, doc_target_path, external_libraries, objects, min_cfg_version, use_gi_docgen, make_backup, generate_safety_asserts, deprecate_by_min_version, show_statistics, concurrency, single_version_file, trust_return_value_nullability, disable_format, split_build_rs, extra_versions, lib_version_overrides, feature_dependencies, external_docs_url, }) } pub fn library_full_name(&self) -> String { format!("{}-{}", self.library_name, self.library_version) } pub fn filter_version(&self, version: Option) -> Option { version.and_then(|v| { if v > self.min_cfg_version { Some(v) } else { None } }) } pub fn find_ext_library(&self, namespace: &Namespace) -> Option<&ExternalLibrary> { self.external_libraries .iter() .find(|lib| lib.crate_name == namespace.crate_name) } pub fn min_required_version(&self, env: &Env, ns_id: Option) -> Option { let ns_id = ns_id.unwrap_or(namespaces::MAIN); if ns_id == namespaces::MAIN { Some(env.config.min_cfg_version) } else { let namespace = env.namespaces.index(ns_id); self.find_ext_library(namespace) .and_then(|lib| lib.min_version) } } pub fn resolve_type_ids(&mut self, library: &Library) { gobjects::resolve_type_ids(&mut self.objects, library); } pub fn check_disable_format(&mut self) { if !self.disable_format && !crate::fmt::check_fmt() { warn!("Formatter not found, options.disable_format set to true"); self.disable_format = true; } } } fn read_toml>(filename: P) -> Result { if !filename.as_ref().is_file() { return Err("Config don't exists or not file".to_owned()); } let input = fs::read(&filename) .map_err(|e| format!("Failed to read file \"{:?}\": {}", filename.as_ref(), e))?; let input = String::from_utf8(input) .map_err(|e| format!("File is not valid UTF-8 \"{:?}\": {}", filename.as_ref(), e))?; toml::from_str(&input).map_err(|e| { format!( "Invalid toml format in \"{}\": {}", filename.as_ref().display(), e ) }) } fn make_single_version_file(configured: Option<&str>, target_path: &Path) -> PathBuf { let file_dir = match configured { None | Some("") => target_path.join("src").join("auto"), Some(path) => target_path.join(path), }; if file_dir.extension().is_some() { file_dir } else { file_dir.join("versions.txt") } } fn read_crate_name_overrides(toml: &toml::Value) -> HashMap { let mut overrides = HashMap::new(); if let Some(a) = toml .lookup("crate_name_overrides") .and_then(toml::Value::as_table) { for (key, value) in a { if let Some(s) = value.as_str() { overrides.insert(key.clone(), s.to_string()); } } }; overrides } fn read_extra_versions(toml: &toml::Value) -> Result, String> { match toml.lookup("options.extra_versions") { Some(a) => a .as_result_vec("options.extra_versions")? .iter() .map(|v| { v.as_str().ok_or_else(|| { "options.extra_versions expected to be array of string".to_string() }) }) .map(|s| s.and_then(str::parse)) .collect(), None => Ok(Vec::new()), } } fn read_lib_version_overrides(toml: &toml::Value) -> Result, String> { let v = match toml.lookup("lib_version_overrides") { Some(a) => a.as_result_vec("lib_version_overrides")?, None => return Ok(Default::default()), }; let mut map = HashMap::with_capacity(v.len()); for o in v { let cfg = o .lookup_str("version", "No version in lib_version_overrides")? .parse()?; let lib = o .lookup_str("lib_version", "No lib_version in lib_version_overrides")? .parse()?; map.insert(cfg, lib); } Ok(map) } fn read_feature_dependencies(toml: &toml::Value) -> Result>, String> { let v = match toml.lookup("feature_dependencies") { Some(a) => a.as_result_vec("feature_dependencies")?, None => return Ok(Default::default()), }; let mut map = HashMap::with_capacity(v.len()); for o in v { let cfg = o .lookup_str("version", "No version in feature_dependencies")? .parse()?; let dependencies: Result, String> = o .lookup_vec("dependencies", "No dependencies in feature_dependencies")? .iter() .map(|v| { v.as_str() .ok_or_else(|| { "feature_dependencies.dependencies expected to be array of string" .to_string() }) .map(str::to_owned) }) .collect(); map.insert(cfg, dependencies?); } Ok(map) } fn read_external_docs_url(toml: &toml::Value) -> Result, String> { Ok( if let Some(value) = toml.lookup("options.external_docs_url") { let value = value.as_result_str("options.external_docs_url")?; Some(value.to_string()) } else { None }, ) } #[cfg(test)] mod tests { use super::*; #[test] fn test_make_single_version_file() { let target_path = Path::new("/tmp/glib"); assert_eq!( make_single_version_file(None, target_path), PathBuf::from("/tmp/glib/src/auto/versions.txt") ); assert_eq!( make_single_version_file(Some(""), target_path), PathBuf::from("/tmp/glib/src/auto/versions.txt") ); assert_eq!( make_single_version_file(Some("src"), target_path), PathBuf::from("/tmp/glib/src/versions.txt") ); assert_eq!( make_single_version_file(Some("src/vers.txt"), target_path), PathBuf::from("/tmp/glib/src/vers.txt") ); assert_eq!( make_single_version_file(Some("."), target_path), PathBuf::from("/tmp/glib/versions.txt") ); assert_eq!( make_single_version_file(Some("./_vers.dat"), target_path), PathBuf::from("/tmp/glib/_vers.dat") ); } } gir-0.20.5/src/config/constants.rs000066400000000000000000000054671475434152100170170ustar00rootroot00000000000000use log::error; use toml::Value; use super::{error::TomlHelper, gobjects::GStatus, ident::Ident, parsable::Parse}; use crate::version::Version; #[derive(Clone, Debug, PartialEq, Eq)] pub struct Constant { pub ident: Ident, pub status: GStatus, pub version: Option, pub cfg_condition: Option, pub generate_doc: bool, } impl Parse for Constant { fn parse(toml: &Value, object_name: &str) -> Option { let Some(ident) = Ident::parse(toml, object_name, "constant") else { error!( "No 'name' or 'pattern' given for constant for object {}", object_name ); return None; }; toml.check_unwanted( &[ "ignore", "manual", "name", "version", "cfg_condition", "pattern", "generate_doc", ], &format!("function {object_name}"), ); let version = toml .lookup("version") .and_then(Value::as_str) .and_then(|s| s.parse().ok()); let cfg_condition = toml .lookup("cfg_condition") .and_then(Value::as_str) .map(ToOwned::to_owned); let status = { if toml .lookup("ignore") .and_then(Value::as_bool) .unwrap_or(false) { GStatus::Ignore } else if toml .lookup("manual") .and_then(Value::as_bool) .unwrap_or(false) { GStatus::Manual } else { GStatus::Generate } }; let generate_doc = toml .lookup("generate_doc") .and_then(Value::as_bool) .unwrap_or(true); Some(Self { ident, status, version, cfg_condition, generate_doc, }) } } impl AsRef for Constant { fn as_ref(&self) -> &Ident { &self.ident } } pub type Constants = Vec; #[cfg(test)] mod tests { use super::{super::parsable::Parse, *}; fn toml(input: &str) -> ::toml::Value { let value = ::toml::from_str(input); assert!(value.is_ok()); value.unwrap() } #[test] fn child_property_parse_generate_doc() { let r = toml( r#" name = "prop" generate_doc = false "#, ); let constant = Constant::parse(&r, "a").unwrap(); assert!(!constant.generate_doc); // Ensure that the default value is "true". let r = toml( r#" name = "prop" "#, ); let constant = Constant::parse(&r, "a").unwrap(); assert!(constant.generate_doc); } } gir-0.20.5/src/config/derives.rs000066400000000000000000000020351475434152100164300ustar00rootroot00000000000000use log::error; use toml::Value; use super::{error::TomlHelper, parsable::Parse}; #[derive(Clone, Debug)] pub struct Derive { pub names: Vec, pub cfg_condition: Option, } impl Parse for Derive { fn parse(toml: &Value, object_name: &str) -> Option { let names = match toml.lookup("name").and_then(Value::as_str) { Some(names) => names, None => { error!("No 'name' given for derive for object {}", object_name); return None; } }; toml.check_unwanted(&["name", "cfg_condition"], &format!("derive {object_name}")); let cfg_condition = toml .lookup("cfg_condition") .and_then(Value::as_str) .map(ToOwned::to_owned); let mut names_vec = Vec::new(); for name in names.split(',') { names_vec.push(name.trim().into()); } Some(Self { names: names_vec, cfg_condition, }) } } pub type Derives = Vec; gir-0.20.5/src/config/error.rs000066400000000000000000000052331475434152100161230ustar00rootroot00000000000000use log::error; pub trait TomlHelper where Self: Sized, { fn check_unwanted(&self, options: &[&str], err_msg: &str); fn lookup<'a>(&'a self, option: &str) -> Option<&'a toml::Value>; fn lookup_str<'a>(&'a self, option: &'a str, err: &str) -> Result<&'a str, String>; fn lookup_vec<'a>(&'a self, option: &'a str, err: &str) -> Result<&'a Vec, String>; fn as_result_str<'a>(&'a self, option: &'a str) -> Result<&'a str, String>; fn as_result_vec<'a>(&'a self, option: &'a str) -> Result<&'a Vec, String>; fn as_result_bool<'a>(&'a self, option: &'a str) -> Result; } impl TomlHelper for toml::Value { fn check_unwanted(&self, options: &[&str], err_msg: &str) { let mut ret = Vec::new(); let Some(table) = self.as_table() else { return }; for (key, _) in table.iter() { if !options.contains(&key.as_str()) { ret.push(key.clone()); } } if !ret.is_empty() { error!( "\"{}\": Unknown key{}: {:?}", err_msg, if ret.len() > 1 { "s" } else { "" }, ret ); } } fn lookup<'a>(&'a self, option: &str) -> Option<&'a toml::Value> { let mut value = self; for opt in option.split('.') { let table = value.as_table()?; value = table.get(opt)?; } Some(value) } fn lookup_str<'a>(&'a self, option: &'a str, err: &str) -> Result<&'a str, String> { let value = self.lookup(option).ok_or(err)?; value.as_result_str(option) } fn lookup_vec<'a>(&'a self, option: &'a str, err: &str) -> Result<&'a Vec, String> { let value = self.lookup(option).ok_or(err)?; value.as_result_vec(option) } fn as_result_str<'a>(&'a self, option: &'a str) -> Result<&'a str, String> { self.as_str().ok_or_else(|| { format!( "Invalid `{}` value, expected a string, found {}", option, self.type_str() ) }) } fn as_result_vec<'a>(&'a self, option: &'a str) -> Result<&'a Vec, String> { self.as_array().ok_or_else(|| { format!( "Invalid `{}` value, expected a array, found {}", option, self.type_str() ) }) } fn as_result_bool<'a>(&'a self, option: &'a str) -> Result { self.as_bool().ok_or_else(|| { format!( "Invalid `{}` value, expected a boolean, found {}", option, self.type_str() ) }) } } gir-0.20.5/src/config/external_libraries.rs000066400000000000000000000130671475434152100206540ustar00rootroot00000000000000use std::str::FromStr; use super::error::*; use crate::{nameutil::crate_name, version::Version}; #[derive(Clone, Debug, Eq, PartialEq)] pub struct ExternalLibrary { pub namespace: String, pub crate_name: String, pub lib_name: String, pub min_version: Option, } pub fn read_external_libraries(toml: &toml::Value) -> Result, String> { let mut external_libraries = match toml.lookup("options.external_libraries") { Some(a) => a .as_result_vec("options.external_libraries")? .iter() .filter_map(|v| v.as_str().map(String::from)) .map(|namespace| { let crate_name_ = crate_name(&namespace); ExternalLibrary { crate_name: crate_name_.clone(), lib_name: crate_name_, min_version: None, namespace, } }) .collect(), None => Vec::new(), }; let custom_libs = toml .lookup("external_libraries") .and_then(toml::Value::as_table); if let Some(custom_libs) = custom_libs { for custom_lib in custom_libs { if let Some(info) = custom_lib.1.as_table() { let namespace = custom_lib.0.as_str(); let crate_name_ = info.get("crate").map_or_else( || crate_name(namespace), |c| c.as_str().expect("crate name must be a string").to_string(), ); let min_version = info .get("min_version") .map(|v| v.as_str().expect("min required version must be a string")) .map(|v| Version::from_str(v).expect("Invalid version number")); let lib = ExternalLibrary { namespace: namespace.to_owned(), crate_name: crate_name_, lib_name: crate_name(namespace), min_version, }; external_libraries.push(lib); } else if let Some(namespace) = custom_lib.1.as_str() { let crate_name_ = custom_lib.0; let lib = ExternalLibrary { namespace: namespace.to_owned(), crate_name: crate_name_.clone(), lib_name: crate_name(custom_lib.1.as_str().expect("No custom lib name set")), min_version: None, }; external_libraries.push(lib); } else { return Err(format!( "For external library \"{:#?}\" namespace must be string or a table", custom_lib.0 )); } } } Ok(external_libraries) } #[cfg(test)] mod tests { use super::*; fn toml(input: &str) -> ::toml::Value { let value = ::toml::from_str(input); assert!(value.is_ok()); value.unwrap() } #[test] fn test_read_external_libraries() { let toml = toml( r#" [options] external_libraries = [ "GLib", "Gdk", "GdkPixbuf", ] [external_libraries] coollib="CoolLib" other-lib="OtherLib" "#, ); let libs = read_external_libraries(&toml).unwrap(); assert_eq!( libs[0], ExternalLibrary { namespace: "GLib".to_owned(), crate_name: "glib".to_owned(), lib_name: "glib".to_owned(), min_version: None, } ); assert_eq!( libs[1], ExternalLibrary { namespace: "Gdk".to_owned(), crate_name: "gdk".to_owned(), lib_name: "gdk".to_owned(), min_version: None, } ); assert_eq!( libs[2], ExternalLibrary { namespace: "GdkPixbuf".to_owned(), crate_name: "gdk_pixbuf".to_owned(), lib_name: "gdk_pixbuf".to_owned(), min_version: None, } ); // Sorted alphabetically assert_eq!( libs[3], ExternalLibrary { namespace: "CoolLib".to_owned(), crate_name: "coollib".to_owned(), lib_name: "cool_lib".to_owned(), min_version: None, } ); assert_eq!( libs[4], ExternalLibrary { namespace: "OtherLib".to_owned(), crate_name: "other-lib".to_owned(), lib_name: "other_lib".to_owned(), min_version: None, } ); } #[test] fn test_read_external_libraries_with_min_version() { let toml = toml( r#" [external_libraries] CoolLib={crate = "coollib", min_version = "0.3.0"} OtherLib={min_version = "0.4.0"} "#, ); let libs = read_external_libraries(&toml).unwrap(); // Sorted alphabetically assert_eq!( libs[0], ExternalLibrary { namespace: "CoolLib".to_owned(), crate_name: "coollib".to_owned(), lib_name: "cool_lib".to_owned(), min_version: Some(Version::from_str("0.3.0").unwrap()), } ); assert_eq!( libs[1], ExternalLibrary { namespace: "OtherLib".to_owned(), crate_name: "other_lib".to_owned(), lib_name: "other_lib".to_owned(), min_version: Some(Version::from_str("0.4.0").unwrap()), } ); } } gir-0.20.5/src/config/functions.rs000066400000000000000000000610441475434152100170040ustar00rootroot00000000000000use std::{collections::HashSet, str::FromStr}; use log::error; use toml::Value; use super::{ error::TomlHelper, gobjects::GStatus, ident::Ident, parameter_matchable::Functionlike, parsable::{Parsable, Parse}, string_type::StringType, }; use crate::{ analysis::safety_assertion_mode::SafetyAssertionMode, codegen::Visibility, library::{Infallible, Mandatory, Nullable}, version::Version, }; #[derive(Clone, Debug)] pub struct CallbackParameter { pub ident: Ident, pub nullable: Option, } pub type CallbackParameters = Vec; impl Parse for CallbackParameter { fn parse(toml: &Value, object_name: &str) -> Option { let Some(ident) = Ident::parse(toml, object_name, "callback parameter") else { error!( "No 'name' or 'pattern' given for parameter for object {}", object_name ); return None; }; toml.check_unwanted(&["nullable"], &format!("callback parameter {object_name}")); let nullable = toml .lookup("nullable") .and_then(Value::as_bool) .map(Nullable); Some(Self { ident, nullable }) } } impl AsRef for CallbackParameter { fn as_ref(&self) -> &Ident { &self.ident } } #[derive(Clone, Debug)] pub struct Parameter { pub ident: Ident, // true - parameter don't changed in FFI function, // false(default) - parameter can be changed in FFI function pub constant: bool, pub move_: Option, pub nullable: Option, pub mandatory: Option, pub infallible: Option, pub length_of: Option, pub string_type: Option, pub callback_parameters: CallbackParameters, } impl Parse for Parameter { fn parse(toml: &Value, object_name: &str) -> Option { let Some(ident) = Ident::parse(toml, object_name, "function parameter") else { error!( "No 'name' or 'pattern' given for parameter for object {}", object_name ); return None; }; toml.check_unwanted( &[ "const", "nullable", "mandatory", "infallible", "length_of", "name", "move", "pattern", "string_type", "callback_parameter", ], &format!("function parameter {object_name}"), ); let constant = toml .lookup("const") .and_then(Value::as_bool) .unwrap_or(false); let move_ = toml.lookup("move").and_then(Value::as_bool); let nullable = toml .lookup("nullable") .and_then(Value::as_bool) .map(Nullable); let mandatory = toml .lookup("mandatory") .and_then(Value::as_bool) .map(Mandatory); let infallible = toml .lookup("infallible") .and_then(Value::as_bool) .map(Infallible); let length_of = toml .lookup("length_of") .and_then(Value::as_str) .map(|s| if s == "return" { "" } else { s }) .map(ToOwned::to_owned); let string_type = toml.lookup("string_type").and_then(Value::as_str); let string_type = match string_type { None => None, Some(val) => match StringType::from_str(val) { Ok(val) => Some(val), Err(error_str) => { error!( "Error: {} for parameter for object {}", error_str, object_name ); None } }, }; let callback_parameters = CallbackParameters::parse(toml.lookup("callback_parameter"), object_name); Some(Self { ident, constant, move_, nullable, mandatory, infallible, length_of, string_type, callback_parameters, }) } } impl AsRef for Parameter { fn as_ref(&self) -> &Ident { &self.ident } } pub type Parameters = Vec; #[derive(Clone, Debug)] pub struct Return { pub nullable: Option, pub mandatory: Option, pub infallible: Option, pub bool_return_is_error: Option, pub nullable_return_is_error: Option, pub use_return_for_result: Option, pub string_type: Option, pub type_name: Option, } impl Return { pub fn parse(toml: Option<&Value>, object_name: &str) -> Self { if toml.is_none() { return Self { nullable: None, mandatory: None, infallible: None, bool_return_is_error: None, nullable_return_is_error: None, use_return_for_result: None, string_type: None, type_name: None, }; } let v = toml.unwrap(); v.check_unwanted( &[ "nullable", "mandatory", "infallible", "bool_return_is_error", "nullable_return_is_error", "use_return_for_result", "string_type", "type", ], "return", ); let nullable = v.lookup("nullable").and_then(Value::as_bool).map(Nullable); let mandatory = v .lookup("mandatory") .and_then(Value::as_bool) .map(Mandatory); let infallible = v .lookup("infallible") .and_then(Value::as_bool) .map(Infallible); let bool_return_is_error = v .lookup("bool_return_is_error") .and_then(Value::as_str) .map(ToOwned::to_owned); let nullable_return_is_error = v .lookup("nullable_return_is_error") .and_then(Value::as_str) .map(ToOwned::to_owned); let use_return_for_result = v.lookup("use_return_for_result").and_then(Value::as_bool); let string_type = v.lookup("string_type").and_then(Value::as_str); let string_type = match string_type { None => None, Some(v) => match StringType::from_str(v) { Ok(v) => Some(v), Err(error_str) => { error!("Error: {} for return", error_str); None } }, }; let type_name = v .lookup("type") .and_then(Value::as_str) .map(ToOwned::to_owned); if string_type.is_some() && type_name.is_some() { error!( "\"string_type\" and \"type\" parameters can't be passed at the same time for \ object {}, only \"type\" will be applied in this case", object_name ); } Self { nullable, mandatory, infallible, bool_return_is_error, nullable_return_is_error, use_return_for_result, string_type, type_name, } } } pub fn check_rename(rename: &Option, object_name: &str, function_name: &Ident) -> bool { if let Some(rename) = rename { for c in &["\t", "\n", " "] { if rename.contains(c) { error!( "Invalid 'rename' value given to {}::{}: forbidden character '{:?}'", object_name, function_name, c ); return false; } } } true } #[derive(Clone, Debug)] pub struct Function { pub ident: Ident, pub status: GStatus, pub version: Option, pub cfg_condition: Option, pub parameters: Parameters, pub ret: Return, pub doc_hidden: bool, pub doc_ignore_parameters: HashSet, pub disable_length_detect: bool, pub doc_trait_name: Option, pub doc_struct_name: Option, pub no_future: bool, pub unsafe_: bool, pub rename: Option, pub visibility: Option, pub bypass_auto_rename: bool, pub is_constructor: Option, pub assertion: Option, pub generate_doc: bool, } impl Parse for Function { fn parse(toml: &Value, object_name: &str) -> Option { let Some(ident) = Ident::parse(toml, object_name, "function") else { error!( "No 'name' or 'pattern' given for function for object {}", object_name ); return None; }; toml.check_unwanted( &[ "ignore", "manual", "version", "cfg_condition", "parameter", "return", "name", "doc_hidden", "doc_ignore_parameters", "disable_length_detect", "pattern", "doc_trait_name", "doc_struct_name", "no_future", "unsafe", "rename", "bypass_auto_rename", "constructor", "assertion", "visibility", "generate_doc", ], &format!("function {object_name}"), ); let status = { if toml .lookup("ignore") .and_then(Value::as_bool) .unwrap_or(false) { GStatus::Ignore } else if toml .lookup("manual") .and_then(Value::as_bool) .unwrap_or(false) { GStatus::Manual } else { GStatus::Generate } }; let version = toml .lookup("version") .and_then(Value::as_str) .and_then(|s| s.parse().ok()); let cfg_condition = toml .lookup("cfg_condition") .and_then(Value::as_str) .map(ToOwned::to_owned); let parameters = Parameters::parse(toml.lookup("parameter"), object_name); let ret = Return::parse(toml.lookup("return"), object_name); let doc_hidden = toml .lookup("doc_hidden") .and_then(Value::as_bool) .unwrap_or(false); let doc_ignore_parameters = toml .lookup_vec("doc_ignore_parameters", "Invalid doc_ignore_parameters") .map(|v| { v.iter() .filter_map(|v| v.as_str().map(String::from)) .collect() }) .unwrap_or_default(); let disable_length_detect = toml .lookup("disable_length_detect") .and_then(Value::as_bool) .unwrap_or(false); let doc_trait_name = toml .lookup("doc_trait_name") .and_then(Value::as_str) .map(ToOwned::to_owned); let doc_struct_name = toml .lookup("doc_struct_name") .and_then(Value::as_str) .map(ToOwned::to_owned); let no_future = toml .lookup("no_future") .and_then(Value::as_bool) .unwrap_or(false); let unsafe_ = toml .lookup("unsafe") .and_then(Value::as_bool) .unwrap_or(false); let rename = toml .lookup("rename") .and_then(Value::as_str) .map(ToOwned::to_owned); if !check_rename(&rename, object_name, &ident) { return None; } let bypass_auto_rename = toml .lookup("bypass_auto_rename") .and_then(Value::as_bool) .unwrap_or(false); let is_constructor = toml.lookup("constructor").and_then(Value::as_bool); let assertion = toml .lookup("assertion") .and_then(Value::as_str) .map(|s| s.parse::()) .transpose(); if let Err(ref err) = assertion { error!("{}", err); } let assertion = assertion.ok().flatten(); let visibility = toml .lookup("visibility") .and_then(Value::as_str) .map(std::str::FromStr::from_str) .transpose(); if let Err(ref err) = visibility { error!("{}", err); } let visibility = visibility.ok().flatten(); let generate_doc = toml .lookup("generate_doc") .and_then(Value::as_bool) .unwrap_or(true); Some(Self { ident, status, version, cfg_condition, parameters, ret, doc_hidden, doc_ignore_parameters, disable_length_detect, doc_trait_name, doc_struct_name, no_future, unsafe_, rename, visibility, bypass_auto_rename, is_constructor, assertion, generate_doc, }) } } impl Functionlike for Function { type Parameter = self::Parameter; fn parameters(&self) -> &[Self::Parameter] { &self.parameters } } impl AsRef for Function { fn as_ref(&self) -> &Ident { &self.ident } } pub type Functions = Vec; #[cfg(test)] mod tests { use super::{ super::{ ident::Ident, matchable::Matchable, parameter_matchable::ParameterMatchable, parsable::{Parsable, Parse}, }, *, }; use crate::{library::Nullable, version::Version}; fn functions_toml(input: &str) -> ::toml::Value { let mut value: ::toml::value::Table = ::toml::from_str(input).unwrap(); value.remove("f").unwrap() } fn toml(input: &str) -> ::toml::Value { let value = ::toml::from_str(input); assert!(value.is_ok()); value.unwrap() } #[test] fn function_parse_ignore() { let toml = toml( r#" name = "func1" ignore = true "#, ); let f = Function::parse(&toml, "a").unwrap(); assert_eq!(f.ident, Ident::Name("func1".into())); assert!(f.status.ignored()); } #[test] fn function_parse_manual() { let toml = toml( r#" name = "func1" manual = true "#, ); let f = Function::parse(&toml, "a").unwrap(); assert_eq!(f.ident, Ident::Name("func1".into())); assert!(f.status.manual()); } #[test] fn function_parse_version_default() { let toml = toml( r#" name = "func1" "#, ); let f = Function::parse(&toml, "a").unwrap(); assert_eq!(f.version, None); assert!(f.status.need_generate()); } #[test] fn function_parse_version() { let toml = toml( r#" name = "func1" version = "3.20" "#, ); let f = Function::parse(&toml, "a").unwrap(); assert_eq!(f.version, Some(Version::new(3, 20, 0))); } #[test] fn function_parse_cfg_condition_default() { let toml = toml( r#" name = "func1" "#, ); let f = Function::parse(&toml, "a").unwrap(); assert_eq!(f.cfg_condition, None); } #[test] fn function_parse_cfg_condition() { let toml = toml( r#" name = "func1" cfg_condition = 'unix' "#, ); let f = Function::parse(&toml, "a").unwrap(); assert_eq!(f.cfg_condition, Some("unix".to_string())); } #[test] fn function_parse_return_nullable_default1() { let toml = toml( r#" name = "func1" "#, ); let f = Function::parse(&toml, "a").unwrap(); assert_eq!(f.ret.nullable, None); } #[test] fn function_parse_return_nullable_default2() { let toml = toml( r#" name = "func1" [return] "#, ); let f = Function::parse(&toml, "a").unwrap(); assert_eq!(f.ret.nullable, None); } #[test] fn function_parse_parameters() { let toml = toml( r#" name = "func1" [[parameter]] name = "par1" [[parameter]] name = "par2" const = false nullable = false [[parameter]] name = "par3" const = true nullable = true [[parameter]] pattern = "par4" const = true "#, ); let f = Function::parse(&toml, "a").unwrap(); let pars = f.parameters; assert_eq!(pars.len(), 4); assert_eq!(pars[0].ident, Ident::Name("par1".into())); assert!(!pars[0].constant); assert_eq!(pars[0].nullable, None); assert_eq!(pars[1].ident, Ident::Name("par2".into())); assert!(!pars[1].constant); assert_eq!(pars[1].nullable, Some(Nullable(false))); assert_eq!(pars[2].ident, Ident::Name("par3".into())); assert!(pars[2].constant); assert_eq!(pars[2].nullable, Some(Nullable(true))); assert!(matches!(pars[3].ident, Ident::Pattern(_))); assert!(pars[3].constant); assert_eq!(pars[3].nullable, None); } #[test] fn function_parse_return_nullable_false() { let toml = toml( r#" name = "func1" [return] nullable = false "#, ); let f = Function::parse(&toml, "a").unwrap(); assert_eq!(f.ret.nullable, Some(Nullable(false))); } #[test] fn function_parse_return_nullable_true() { let toml = toml( r#" name = "func1" [return] nullable = true "#, ); let f = Function::parse(&toml, "a").unwrap(); assert_eq!(f.ret.nullable, Some(Nullable(true))); } #[test] fn function_parse_generate_doc() { let r = toml( r#" name = "prop" generate_doc = false "#, ); let f = Function::parse(&r, "a").unwrap(); assert!(!f.generate_doc); // Ensure that the default value is "true". let r = toml( r#" name = "prop" "#, ); let f = Function::parse(&r, "a").unwrap(); assert!(f.generate_doc); } #[test] fn functions_parse_empty_for_none() { let fns = Functions::parse(None, "a"); assert!(fns.is_empty()); } #[test] fn functions_parse_ident() { let toml = functions_toml( r#" [[f]] name = "func1" [[f]] not_name = "func1.5" [[f]] name = "func2" [[f]] pattern = 'func3\w+' [[f]] pattern = 'bad_func4[\w+' "#, ); let fns = Functions::parse(Some(&toml), "a"); assert_eq!(fns.len(), 3); assert_eq!(fns[0].ident, Ident::Name("func1".into())); assert_eq!(fns[1].ident, Ident::Name("func2".into())); assert!(matches!(fns[2].ident, Ident::Pattern(_))); } #[test] fn functions_parse_matches() { let toml = functions_toml( r#" [[f]] name = "func1" [[f]] name = "f1.5" [[f]] name = "func2" [[f]] pattern = 'func\d+' "#, ); let fns = Functions::parse(Some(&toml), "a"); assert_eq!(fns.len(), 3); assert_eq!(fns.matched("func1").len(), 2); assert_eq!(fns.matched("func2").len(), 2); assert_eq!(fns.matched("func3").len(), 1); // "f1.5" is not a valid name assert_eq!(fns.matched("f1.5").len(), 0); assert_eq!(fns.matched("none").len(), 0); } #[test] fn functions_parse_matched_parameters() { let toml = functions_toml( r#" [[f]] name = "func" [[f.parameter]] name="par1" [[f.parameter]] name="par2" [[f.parameter]] pattern='par\d+' [[f]] name = "func" [[f.parameter]] name="par2" [[f.parameter]] name="par3" [[f.parameter]] pattern='par\d+' "#, ); let fns = Functions::parse(Some(&toml), "a"); assert_eq!(fns.len(), 2); let m = fns.matched("func"); assert_eq!(m.len(), 2); assert_eq!(m.matched_parameters("param").len(), 0); assert_eq!(m.matched_parameters("par1").len(), 3); assert_eq!(m.matched_parameters("par2").len(), 4); assert_eq!(m.matched_parameters("par3").len(), 3); assert_eq!(m.matched_parameters("par4").len(), 2); } #[test] fn functions_parse_rename() { let toml = toml( r#" name = "func1" rename = "another" "#, ); let f = Function::parse(&toml, "a").unwrap(); assert_eq!(f.rename, Some("another".to_owned())); } #[test] fn functions_parse_rename_fail() { let toml = toml( r#" name = "func1" rename = "anoth er" "#, ); let f = Function::parse(&toml, "a"); assert!(f.is_none()); } #[test] fn function_bypass_auto_rename() { let toml = toml( r#" name = "func1" bypass_auto_rename = true "#, ); let f = Function::parse(&toml, "a").unwrap(); assert_eq!(f.ident, Ident::Name("func1".into())); assert!(f.bypass_auto_rename); } #[test] fn parse_return_mandatory_default() { let toml = toml( r#" name = "func1" "#, ); let f = Function::parse(&toml, "a"); let f = f.unwrap(); assert!(f.ret.mandatory.is_none()); } #[test] fn parse_return_mandatory() { let toml = toml( r#" name = "func1" [return] mandatory = true "#, ); let f = Function::parse(&toml, "a"); let f = f.unwrap(); assert_eq!(f.ret.mandatory, Some(Mandatory(true))); } #[test] fn parse_return_non_mandatory() { let toml = toml( r#" name = "func1" [return] mandatory = false "#, ); let f = Function::parse(&toml, "a"); let f = f.unwrap(); assert_eq!(f.ret.mandatory, Some(Mandatory(false))); } #[test] fn parse_parameter_mandatory_default() { let toml = toml( r#" name = "func1" [[parameter]] name = "param1" "#, ); let f = Function::parse(&toml, "a"); let f = f.unwrap(); let param1 = &f.parameters[0]; assert!(param1.mandatory.is_none()); } #[test] fn parse_parameter_mandatory() { let toml = toml( r#" name = "func1" [[parameter]] name = "param1" mandatory = true "#, ); let f = Function::parse(&toml, "a"); let f = f.unwrap(); let param1 = &f.parameters[0]; assert_eq!(param1.mandatory, Some(Mandatory(true))); } #[test] fn parse_parameter_non_mandatory() { let toml = toml( r#" name = "func1" [[parameter]] name = "param1" mandatory = false "#, ); let f = Function::parse(&toml, "a"); let f = f.unwrap(); let param1 = &f.parameters[0]; assert_eq!(param1.mandatory, Some(Mandatory(false))); } #[test] fn parse_return_infallible_default() { let toml = toml( r#" name = "func1" "#, ); let f = Function::parse(&toml, "a"); let f = f.unwrap(); assert!(f.ret.infallible.is_none()); } #[test] fn parse_return_infallible() { let toml = toml( r#" name = "func1" [return] infallible = true "#, ); let f = Function::parse(&toml, "a"); let f = f.unwrap(); assert_eq!(f.ret.infallible, Some(Infallible(true))); } #[test] fn parse_return_faillible() { let toml = toml( r#" name = "func1" [return] infallible = false "#, ); let f = Function::parse(&toml, "a"); let f = f.unwrap(); assert_eq!(f.ret.infallible, Some(Infallible(false))); } #[test] fn parse_parameter_infallible_default() { let toml = toml( r#" name = "func1" [[parameter]] name = "param1" "#, ); let f = Function::parse(&toml, "a"); let f = f.unwrap(); let param1 = &f.parameters[0]; assert!(param1.infallible.is_none()); } #[test] fn parse_parameter_infallible() { let toml = toml( r#" name = "func1" [[parameter]] name = "param1" infallible = true "#, ); let f = Function::parse(&toml, "a"); let f = f.unwrap(); let param1 = &f.parameters[0]; assert_eq!(param1.infallible, Some(Infallible(true))); } #[test] fn parse_parameter_faillible() { let toml = toml( r#" name = "func1" [[parameter]] name = "param1" infallible = false "#, ); let f = Function::parse(&toml, "a"); let f = f.unwrap(); let param1 = &f.parameters[0]; assert_eq!(param1.infallible, Some(Infallible(false))); } } gir-0.20.5/src/config/gobjects.rs000066400000000000000000000543741475434152100166040ustar00rootroot00000000000000use std::{ collections::{BTreeMap, HashSet}, str::FromStr, sync::Arc, }; use log::{error, warn}; use toml::Value; use super::{ child_properties::ChildProperties, constants::Constants, derives::Derives, functions::Functions, ident::Ident, members::Members, properties::Properties, signals::{Signal, Signals}, virtual_methods::VirtualMethods, }; use crate::{ analysis::{conversion_type::ConversionType, ref_mode}, codegen::Visibility, config::{ error::TomlHelper, parsable::{Parsable, Parse}, }, library::{self, Library, TypeId, MAIN_NAMESPACE}, version::Version, }; #[derive(Default, Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)] pub enum GStatus { Manual, Generate, #[default] Ignore, } impl GStatus { pub fn ignored(self) -> bool { self == Self::Ignore } pub fn manual(self) -> bool { self == Self::Manual } pub fn need_generate(self) -> bool { self == Self::Generate } } impl FromStr for GStatus { type Err = String; fn from_str(s: &str) -> Result { match s { "manual" => Ok(Self::Manual), "generate" => Ok(Self::Generate), "ignore" => Ok(Self::Ignore), e => Err(format!("Wrong object status: \"{e}\"")), } } } /// Info about `GObject` descendant #[derive(Clone, Debug)] pub struct GObject { pub name: String, pub functions: Functions, pub virtual_methods: VirtualMethods, pub constants: Constants, pub signals: Signals, pub members: Members, pub properties: Properties, pub derives: Option, pub status: GStatus, pub module_name: Option, pub version: Option, pub cfg_condition: Option, pub type_id: Option, pub final_type: Option, pub fundamental_type: Option, pub exhaustive: bool, pub trait_name: Option, pub child_properties: Option, pub concurrency: library::Concurrency, pub ref_mode: Option, pub must_use: bool, pub conversion_type: Option, pub trust_return_value_nullability: bool, pub manual_traits: Vec, pub align: Option, pub generate_builder: bool, pub builder_postprocess: Option, pub boxed_inline: bool, pub init_function_expression: Option, pub copy_into_function_expression: Option, pub clear_function_expression: Option, pub visibility: Visibility, pub default_value: Option, pub generate_doc: bool, } impl Default for GObject { fn default() -> GObject { GObject { name: "Default".into(), functions: Functions::new(), virtual_methods: VirtualMethods::new(), constants: Constants::new(), signals: Signals::new(), members: Members::new(), properties: Properties::new(), derives: None, status: Default::default(), module_name: None, version: None, cfg_condition: None, type_id: None, final_type: None, fundamental_type: None, exhaustive: false, trait_name: None, child_properties: None, concurrency: Default::default(), ref_mode: None, must_use: false, conversion_type: None, trust_return_value_nullability: false, manual_traits: Vec::default(), align: None, generate_builder: false, builder_postprocess: None, boxed_inline: false, init_function_expression: None, copy_into_function_expression: None, clear_function_expression: None, visibility: Default::default(), default_value: None, generate_doc: true, } } } // TODO: ?change to HashMap pub type GObjects = BTreeMap; pub fn parse_toml( toml_objects: &Value, concurrency: library::Concurrency, generate_builder: bool, trust_return_value_nullability: bool, ) -> GObjects { let mut objects = GObjects::new(); for toml_object in toml_objects.as_array().unwrap() { let gobject = parse_object( toml_object, concurrency, generate_builder, trust_return_value_nullability, ); objects.insert(gobject.name.clone(), gobject); } objects } pub fn parse_conversion_type(toml: Option<&Value>, object_name: &str) -> Option { use crate::analysis::conversion_type::ConversionType::*; let v = toml?; v.check_unwanted(&["variant", "ok_type", "err_type"], "conversion_type"); let (conversion_type, ok_type, err_type) = match &v { Value::Table(table) => { let conversion_type = table.get("variant").and_then(Value::as_str); if conversion_type.is_none() { error!("Missing `variant` for {}.conversion_type", object_name); return None; } let ok_type = Some(Arc::from( table .get("ok_type") .and_then(Value::as_str) .unwrap_or(object_name), )); let err_type = table.get("err_type").and_then(Value::as_str); (conversion_type.unwrap(), ok_type, err_type) } Value::String(conversion_type) => (conversion_type.as_str(), None, None), _ => { error!("Unexpected toml item for {}.conversion_type", object_name); return None; } }; let get_err_type = || -> Arc { err_type.map_or_else( || { error!("Missing `err_type` for {}.conversion_type", object_name); Arc::from("MissingErrorType") }, Arc::from, ) }; match conversion_type { "direct" => Some(Direct), "scalar" => Some(Scalar), "Option" => Some(Option), "Result" => Some(Result { ok_type: ok_type.expect("Missing `ok_type`"), err_type: get_err_type(), }), "pointer" => Some(Pointer), "borrow" => Some(Borrow), "unknown" => Some(Unknown), unexpected => { error!( "Unexpected {} for {}.conversion_type", unexpected, object_name ); None } } } fn parse_object( toml_object: &Value, concurrency: library::Concurrency, generate_builder: bool, trust_return_value_nullability: bool, ) -> GObject { let name: String = toml_object .lookup("name") .expect("Object name not defined") .as_str() .unwrap() .into(); // Also checks for ChildProperties toml_object.check_unwanted( &[ "name", "status", "function", "constant", "signal", "member", "property", "derive", "module_name", "version", "concurrency", "ref_mode", "conversion_type", "child_prop", "child_name", "child_type", "final_type", "fundamental_type", "exhaustive", "trait", "trait_name", "cfg_condition", "must_use", "trust_return_value_nullability", "manual_traits", "align", "generate_builder", "builder_postprocess", "boxed_inline", "init_function_expression", "copy_into_function_expression", "clear_function_expression", "visibility", "default_value", "generate_doc", ], &format!("object {name}"), ); let status = match toml_object.lookup("status") { Some(value) => { GStatus::from_str(value.as_str().unwrap()).unwrap_or_else(|_| Default::default()) } None => Default::default(), }; let constants = Constants::parse(toml_object.lookup("constant"), &name); let functions = Functions::parse(toml_object.lookup("function"), &name); let mut function_names = HashSet::new(); for f in &functions { if let Ident::Name(name) = &f.ident { assert!(function_names.insert(name), "{name} already defined!"); } } let virtual_methods = VirtualMethods::parse(toml_object.lookup("virtual_method"), &name); let mut virtual_methods_names = HashSet::new(); for f in &virtual_methods { if let Ident::Name(name) = &f.ident { assert!( virtual_methods_names.insert(name), "{name} already defined!" ); } } let signals = { let mut v = Vec::new(); if let Some(configs) = toml_object.lookup("signal").and_then(Value::as_array) { for config in configs { if let Some(item) = Signal::parse(config, &name, concurrency) { v.push(item); } } } v }; let members = Members::parse(toml_object.lookup("member"), &name); let properties = Properties::parse(toml_object.lookup("property"), &name); let derives = toml_object .lookup("derive") .map(|derives| Derives::parse(Some(derives), &name)); let module_name = toml_object .lookup("module_name") .and_then(Value::as_str) .map(ToOwned::to_owned); let version = toml_object .lookup("version") .and_then(Value::as_str) .and_then(|s| s.parse().ok()); let cfg_condition = toml_object .lookup("cfg_condition") .and_then(Value::as_str) .map(ToOwned::to_owned); let generate_trait = toml_object.lookup("trait").and_then(Value::as_bool); let final_type = toml_object .lookup("final_type") .and_then(Value::as_bool) .or_else(|| generate_trait.map(|t| !t)); let fundamental_type = toml_object .lookup("fundamental_type") .and_then(Value::as_bool); let exhaustive = toml_object .lookup("exhaustive") .and_then(Value::as_bool) .unwrap_or(false); let trait_name = toml_object .lookup("trait_name") .and_then(Value::as_str) .map(ToOwned::to_owned); let concurrency = toml_object .lookup("concurrency") .and_then(Value::as_str) .and_then(|v| v.parse().ok()) .unwrap_or(concurrency); let ref_mode = toml_object .lookup("ref_mode") .and_then(Value::as_str) .and_then(|v| v.parse().ok()); let conversion_type = parse_conversion_type(toml_object.lookup("conversion_type"), &name); let child_properties = ChildProperties::parse(toml_object, &name); let must_use = toml_object .lookup("must_use") .and_then(Value::as_bool) .unwrap_or(false); let trust_return_value_nullability = toml_object .lookup("trust_return_value_nullability") .and_then(Value::as_bool) .unwrap_or(trust_return_value_nullability); let manual_traits = toml_object .lookup_vec("manual_traits", "IGNORED ERROR") .into_iter() .flatten() .filter_map(|v| v.as_str().map(String::from)) .collect(); let align = toml_object .lookup("align") .and_then(Value::as_integer) .and_then(|v| { if v.count_ones() != 1 || v > i64::from(u32::MAX) || v < 0 { warn!( "`align` configuration must be a power of two of type u32, found {}", v ); None } else { Some(v as u32) } }); let generate_builder = toml_object .lookup("generate_builder") .and_then(Value::as_bool) .unwrap_or(generate_builder); let boxed_inline = toml_object .lookup("boxed_inline") .and_then(Value::as_bool) .unwrap_or(false); let builder_postprocess = toml_object .lookup("builder_postprocess") .and_then(Value::as_str) .map(String::from); let init_function_expression = toml_object .lookup("init_function_expression") .and_then(Value::as_str) .map(ToOwned::to_owned); let copy_into_function_expression = toml_object .lookup("copy_into_function_expression") .and_then(Value::as_str) .map(ToOwned::to_owned); let clear_function_expression = toml_object .lookup("clear_function_expression") .and_then(Value::as_str) .map(ToOwned::to_owned); let default_value = toml_object .lookup("default_value") .and_then(Value::as_str) .map(ToOwned::to_owned); let visibility = toml_object .lookup("visibility") .and_then(Value::as_str) .map(|v| v.parse()) .transpose(); if let Err(ref err) = visibility { error!("{}", err); } let visibility = visibility.ok().flatten().unwrap_or_default(); if boxed_inline && !((init_function_expression.is_none() && copy_into_function_expression.is_none() && clear_function_expression.is_none()) || (init_function_expression.is_some() && copy_into_function_expression.is_some() && clear_function_expression.is_some())) { panic!( "`init_function_expression`, `copy_into_function_expression` and `clear_function_expression` all have to be provided or neither" ); } if !boxed_inline && (init_function_expression.is_some() || copy_into_function_expression.is_some() || clear_function_expression.is_some()) { panic!( "`init_function_expression`, `copy_into_function_expression` and `clear_function_expression` can only be provided for BoxedInline types" ); } if status != GStatus::Manual && ref_mode.is_some() { warn!("ref_mode configuration used for non-manual object {}", name); } if status != GStatus::Manual && !conversion_type .as_ref() .map_or(true, ConversionType::can_use_to_generate) { warn!( "unexpected conversion_type {:?} configuration used for non-manual object {}", conversion_type, name ); } let generate_doc = toml_object .lookup("generate_doc") .and_then(Value::as_bool) .unwrap_or(true); if generate_trait.is_some() { warn!( "`trait` configuration is deprecated and replaced by `final_type` for object {}", name ); } GObject { name, functions, virtual_methods, constants, signals, members, properties, derives, status, module_name, version, cfg_condition, type_id: None, final_type, fundamental_type, exhaustive, trait_name, child_properties, concurrency, ref_mode, must_use, conversion_type, trust_return_value_nullability, manual_traits, align, generate_builder, builder_postprocess, boxed_inline, init_function_expression, copy_into_function_expression, clear_function_expression, visibility, default_value, generate_doc, } } pub fn parse_status_shorthands( objects: &mut GObjects, toml: &Value, concurrency: library::Concurrency, generate_builder: bool, trust_return_value_nullability: bool, ) { use self::GStatus::*; for &status in &[Manual, Generate, Ignore] { parse_status_shorthand( objects, status, toml, concurrency, generate_builder, trust_return_value_nullability, ); } } fn parse_status_shorthand( objects: &mut GObjects, status: GStatus, toml: &Value, concurrency: library::Concurrency, generate_builder: bool, trust_return_value_nullability: bool, ) { let option_name = format!("options.{status:?}").to_ascii_lowercase(); if let Some(a) = toml.lookup(&option_name).map(|a| a.as_array().unwrap()) { for name in a.iter().map(|s| s.as_str().unwrap()) { match objects.get(name) { None => { objects.insert( name.into(), GObject { name: name.into(), status, concurrency, trust_return_value_nullability, generate_builder, ..Default::default() }, ); } Some(_) => panic!("Bad name in {option_name}: {name} already defined"), } } } } pub fn resolve_type_ids(objects: &mut GObjects, library: &Library) { let ns = library.namespace(MAIN_NAMESPACE); let global_functions_name = format!("{}.*", ns.name); for (name, object) in objects.iter_mut() { let type_id = library.find_type(0, name); if type_id.is_none() && name != &global_functions_name && object.status != GStatus::Ignore { warn!("Configured object `{}` missing from the library", name); } else if object.generate_builder { if let Some(type_id) = type_id { if library.type_(type_id).is_abstract() { warn!( "Cannot generate builder for `{}` because it's a base class", name ); // We set this to `false` to avoid having the "not_bound" mode saying that this // builder should be generated. object.generate_builder = false; } } } object.type_id = type_id; } } #[cfg(test)] mod tests { use super::*; use crate::{analysis::conversion_type::ConversionType, library::Concurrency}; fn toml(input: &str) -> ::toml::Value { let value = ::toml::from_str(input); assert!(value.is_ok()); value.unwrap() } #[test] fn conversion_type_default() { let toml = &toml( r#" name = "Test" status = "generate" "#, ); let object = parse_object(toml, Concurrency::default(), false, false); assert_eq!(object.conversion_type, None); } #[test] fn conversion_type_option_str() { let toml = toml( r#" name = "Test" status = "generate" conversion_type = "Option" "#, ); let object = parse_object(&toml, Concurrency::default(), false, false); assert_eq!(object.conversion_type, Some(ConversionType::Option)); } #[test] fn conversion_type_option_table() { let toml = &toml( r#" name = "Test" status = "generate" [conversion_type] variant = "Option" "#, ); let object = parse_object(toml, Concurrency::default(), false, false); assert_eq!(object.conversion_type, Some(ConversionType::Option)); } #[test] fn conversion_type_result_table_missing_err() { let toml = &toml( r#" name = "Test" status = "generate" [conversion_type] variant = "Result" "#, ); let object = parse_object(toml, Concurrency::default(), false, false); assert_eq!( object.conversion_type, Some(ConversionType::Result { ok_type: Arc::from("Test"), err_type: Arc::from("MissingErrorType"), }), ); } #[test] fn conversion_type_result_table_with_err() { let toml = &toml( r#" name = "Test" status = "generate" [conversion_type] variant = "Result" err_type = "TryFromIntError" "#, ); let object = parse_object(toml, Concurrency::default(), false, false); assert_eq!( object.conversion_type, Some(ConversionType::Result { ok_type: Arc::from("Test"), err_type: Arc::from("TryFromIntError"), }), ); } #[test] fn conversion_type_result_table_with_ok_err() { let toml = &toml( r#" name = "Test" status = "generate" [conversion_type] variant = "Result" ok_type = "TestSuccess" err_type = "TryFromIntError" "#, ); let object = parse_object(toml, Concurrency::default(), false, false); assert_eq!( object.conversion_type, Some(ConversionType::Result { ok_type: Arc::from("TestSuccess"), err_type: Arc::from("TryFromIntError"), }), ); } #[test] fn conversion_type_fields() { let toml = &toml( r#" [[object]] name = "Test" status = "generate" [[object.constant]] name = "Const" [[object.function]] name = "Func" manual = true "#, ); let object = toml .lookup("object") .map(|t| parse_toml(t, Concurrency::default(), false, false)) .expect("parsing failed"); assert_eq!( object["Test"].constants, vec![crate::config::constants::Constant { ident: Ident::Name("Const".to_owned()), status: GStatus::Generate, version: None, cfg_condition: None, generate_doc: true, }], ); assert_eq!(object["Test"].functions.len(), 1); assert_eq!( object["Test"].functions[0].ident, Ident::Name("Func".to_owned()), ); } #[test] fn conversion_type_generate_doc() { let r = &toml( r#" name = "Test" status = "generate" generate_doc = false "#, ); let object = parse_object(r, Concurrency::default(), false, false); assert!(!object.generate_doc); // Ensure that the default value is "true". let r = &toml( r#" name = "Test" status = "generate" "#, ); let object = parse_object(r, Concurrency::default(), false, false); assert!(object.generate_doc); } } gir-0.20.5/src/config/ident.rs000066400000000000000000000037741475434152100161050ustar00rootroot00000000000000use std::fmt; use log::error; use regex::Regex; use toml::Value; use super::error::TomlHelper; #[derive(Clone, Debug)] pub enum Ident { Name(String), Pattern(Box), } impl fmt::Display for Ident { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Name(name) => f.write_str(name), Self::Pattern(regex) => write!(f, "Regex {regex}"), } } } impl PartialEq for Ident { fn eq(&self, other: &Ident) -> bool { match (self, other) { (Self::Name(s1), Self::Name(s2)) => s1 == s2, (Self::Pattern(r1), Self::Pattern(r2)) => r1.as_str() == r2.as_str(), _ => false, } } } impl Eq for Ident {} impl Ident { pub fn parse(toml: &Value, object_name: &str, what: &str) -> Option { match toml.lookup("pattern").and_then(Value::as_str) { Some(s) => Regex::new(&format!("^{s}$")) .map(Box::new) .map(Self::Pattern) .map_err(|e| { error!( "Bad pattern `{}` in {} for `{}`: {}", s, what, object_name, e ); e }) .ok(), None => match toml.lookup("name").and_then(Value::as_str) { Some(name) => { if name.contains(['.', '+', '*'].as_ref()) { error!( "Should be `pattern` instead of `name` in {} for `{}`", what, object_name ); None } else { Some(Self::Name(name.into())) } } None => None, }, } } pub fn is_match(&self, name: &str) -> bool { use self::Ident::*; match self { Name(n) => name == n, Pattern(regex) => regex.is_match(name), } } } gir-0.20.5/src/config/matchable.rs000066400000000000000000000005341475434152100167110ustar00rootroot00000000000000use super::ident::Ident; pub trait Matchable { type Item; fn matched(&self, name: &str) -> Vec<&Self::Item>; } impl> Matchable for [T] { type Item = T; fn matched(&self, name: &str) -> Vec<&Self::Item> { self.iter() .filter(|item| item.as_ref().is_match(name)) .collect() } } gir-0.20.5/src/config/members.rs000066400000000000000000000073511475434152100164270ustar00rootroot00000000000000use log::error; use toml::Value; use super::{error::TomlHelper, gobjects::GStatus, ident::Ident, parsable::Parse}; use crate::version::Version; #[derive(Clone, Debug)] pub struct Member { pub ident: Ident, pub version: Option, pub deprecated_version: Option, pub status: GStatus, pub cfg_condition: Option, pub generate_doc: bool, } impl Parse for Member { fn parse(toml: &Value, object_name: &str) -> Option { let Some(ident) = Ident::parse(toml, object_name, "member") else { error!( "No 'name' or 'pattern' given for member for object {}", object_name ); return None; }; toml.check_unwanted( &[ "version", "name", "pattern", "ignore", "manual", "cfg_condition", "generate_doc", ], &format!("member {object_name}"), ); let version = toml .lookup("version") .and_then(Value::as_str) .and_then(|s| s.parse().ok()); let deprecated_version = toml .lookup("deprecated_version") .and_then(Value::as_str) .and_then(|s| s.parse().ok()); let cfg_condition = toml .lookup("cfg_condition") .and_then(Value::as_str) .map(ToOwned::to_owned); let status = { if toml .lookup("ignore") .and_then(Value::as_bool) .unwrap_or(false) { GStatus::Ignore } else if toml .lookup("manual") .and_then(Value::as_bool) .unwrap_or(false) { GStatus::Manual } else { GStatus::Generate } }; let generate_doc = toml .lookup("generate_doc") .and_then(Value::as_bool) .unwrap_or(true); Some(Self { ident, version, deprecated_version, status, cfg_condition, generate_doc, }) } } impl AsRef for Member { fn as_ref(&self) -> &Ident { &self.ident } } pub type Members = Vec; #[cfg(test)] mod tests { use super::{ super::{ident::Ident, parsable::Parse}, *, }; use crate::version::Version; fn toml(input: &str) -> ::toml::Value { let value = ::toml::from_str(input); assert!(value.is_ok()); value.unwrap() } #[test] fn member_parse_alias() { let toml = toml( r#" name = "name1" "#, ); let f = Member::parse(&toml, "a").unwrap(); assert_eq!(f.ident, Ident::Name("name1".into())); } #[test] fn member_parse_version_default() { let toml = toml( r#" name = "name1" "#, ); let f = Member::parse(&toml, "a").unwrap(); assert_eq!(f.version, None); } #[test] fn member_parse_version() { let toml = toml( r#" name = "name1" version = "3.20" "#, ); let f = Member::parse(&toml, "a").unwrap(); assert_eq!(f.version, Some(Version::new(3, 20, 0))); } #[test] fn member_parse_generate_doc() { let r = toml( r#" name = "name1" generate_doc = false "#, ); let f = Member::parse(&r, "a").unwrap(); assert!(!f.generate_doc); // We now ensure that the default value is true. let r = toml( r#" name = "name1" "#, ); let f = Member::parse(&r, "a").unwrap(); assert!(f.generate_doc); } } gir-0.20.5/src/config/mod.rs000066400000000000000000000012031475434152100155420ustar00rootroot00000000000000mod child_properties; #[allow(clippy::module_inception)] pub mod config; pub mod constants; pub mod derives; pub mod error; mod external_libraries; pub mod functions; pub mod gobjects; pub mod ident; pub mod matchable; pub mod members; pub mod parameter_matchable; pub mod parsable; pub mod properties; pub mod property_generate_flags; pub mod signals; pub mod string_type; pub mod virtual_methods; pub mod work_mode; pub use self::{ child_properties::{ChildProperties, ChildProperty}, config::Config, gobjects::GObject, property_generate_flags::PropertyGenerateFlags, string_type::StringType, work_mode::WorkMode, }; gir-0.20.5/src/config/parameter_matchable.rs000066400000000000000000000012611475434152100207470ustar00rootroot00000000000000use super::{ident::Ident, matchable::Matchable}; pub trait Functionlike { type Parameter; fn parameters(&self) -> &[Self::Parameter]; // TODO: result } pub trait ParameterMatchable { type Parameter; fn matched_parameters(&self, parameter_name: &str) -> Vec<&Self::Parameter>; } impl, T: Functionlike> ParameterMatchable for [&T] { type Parameter = U; fn matched_parameters(&self, parameter_name: &str) -> Vec<&Self::Parameter> { let mut v = Vec::new(); for f in self.iter() { let pars = f.parameters().matched(parameter_name); v.extend_from_slice(&pars); } v } } gir-0.20.5/src/config/parsable.rs000066400000000000000000000012031475434152100165540ustar00rootroot00000000000000use toml::Value; pub trait Parse: Sized { fn parse(toml: &Value, name: &str) -> Option; } pub trait Parsable { type Item; fn parse(toml: Option<&Value>, object_name: &str) -> Vec; } impl Parsable for Vec { type Item = T; fn parse(toml: Option<&Value>, object_name: &str) -> Vec { let mut v = Vec::new(); if let Some(configs) = toml.and_then(Value::as_array) { for config in configs { if let Some(item) = T::parse(config, object_name) { v.push(item); } } } v } } gir-0.20.5/src/config/properties.rs000066400000000000000000000134561475434152100171740ustar00rootroot00000000000000use log::error; use toml::Value; use super::{ error::TomlHelper, gobjects::GStatus, ident::Ident, parsable::Parse, property_generate_flags::PropertyGenerateFlags, }; use crate::version::Version; #[derive(Clone, Debug)] pub struct Property { pub ident: Ident, pub status: GStatus, pub version: Option, pub generate: Option, pub bypass_auto_rename: bool, pub doc_trait_name: Option, pub generate_doc: bool, } impl Parse for Property { fn parse(toml: &Value, object_name: &str) -> Option { let Some(ident) = Ident::parse(toml, object_name, "property") else { error!( "No 'name' or 'pattern' given for property for object {}", object_name ); return None; }; toml.check_unwanted( &[ "ignore", "manual", "version", "name", "pattern", "generate", "bypass_auto_rename", "doc_trait_name", "generate_doc", ], &format!("property {object_name}"), ); let status = { if toml .lookup("ignore") .and_then(Value::as_bool) .unwrap_or(false) { GStatus::Ignore } else if toml .lookup("manual") .and_then(Value::as_bool) .unwrap_or(false) { GStatus::Manual } else { GStatus::Generate } }; let version = toml .lookup("version") .and_then(Value::as_str) .and_then(|s| s.parse().ok()); let generate = toml.lookup("generate").and_then(|v| { PropertyGenerateFlags::parse_flags(v, "generate") .map_err(|e| error!("{} for object {}", e, object_name)) .ok() }); let bypass_auto_rename = toml .lookup("bypass_auto_rename") .and_then(Value::as_bool) .unwrap_or(false); let doc_trait_name = toml .lookup("doc_trait_name") .and_then(Value::as_str) .map(ToOwned::to_owned); let generate_doc = toml .lookup("generate_doc") .and_then(Value::as_bool) .unwrap_or(true); Some(Self { ident, status, version, generate, bypass_auto_rename, doc_trait_name, generate_doc, }) } } impl AsRef for Property { fn as_ref(&self) -> &Ident { &self.ident } } pub type Properties = Vec; #[cfg(test)] mod tests { use super::{ super::{ ident::Ident, matchable::Matchable, parsable::{Parsable, Parse}, }, *, }; use crate::version::Version; fn properties_toml(input: &str) -> ::toml::Value { let mut value: ::toml::value::Table = ::toml::from_str(input).unwrap(); value.remove("f").unwrap() } fn toml(input: &str) -> ::toml::Value { let value = input.parse(); assert!(value.is_ok()); value.unwrap() } #[test] fn property_parse_ignore() { let toml = toml( r#" name = "prop1" ignore = true "#, ); let p = Property::parse(&toml, "a").unwrap(); assert_eq!(p.ident, Ident::Name("prop1".into())); assert!(p.status.ignored()); } #[test] fn property_parse_manual() { let toml = toml( r#" name = "prop1" manual = true "#, ); let p = Property::parse(&toml, "a").unwrap(); assert_eq!(p.ident, Ident::Name("prop1".into())); assert!(p.status.manual()); } #[test] fn property_bypass_auto_rename() { let toml = toml( r#" name = "prop1" bypass_auto_rename = true "#, ); let f = Property::parse(&toml, "a").unwrap(); assert_eq!(f.ident, Ident::Name("prop1".into())); assert!(f.bypass_auto_rename); } #[test] fn property_parse_version_default() { let toml = toml( r#" name = "prop1" "#, ); let p = Property::parse(&toml, "a").unwrap(); assert_eq!(p.version, None); assert!(p.status.need_generate()); } #[test] fn property_parse_version() { let toml = toml( r#" name = "prop1" version = "3.20" "#, ); let p = Property::parse(&toml, "a").unwrap(); assert_eq!(p.version, Some(Version::new(3, 20, 0))); } #[test] fn property_generate_doc() { let r = toml( r#" name = "prop" generate_doc = false "#, ); let p = Property::parse(&r, "a").unwrap(); assert!(!p.generate_doc); // Ensure that the default value is "true". let r = toml( r#" name = "prop" "#, ); let p = Property::parse(&r, "a").unwrap(); assert!(p.generate_doc); } #[test] fn properties_parse_empty_for_none() { let props = Properties::parse(None, "a"); assert!(props.is_empty()); } #[test] fn properties_parse_matches() { let toml = properties_toml( r#" [[f]] name = "prop1" [[f]] name = "p1.5" [[f]] name = "prop2" [[f]] pattern = 'prop\d+' "#, ); let props = Properties::parse(Some(&toml), "a"); assert_eq!(props.len(), 3); assert_eq!(props.matched("prop1").len(), 2); assert_eq!(props.matched("prop2").len(), 2); assert_eq!(props.matched("prop3").len(), 1); // "p1.5" is an invalid name assert_eq!(props.matched("p1.5").len(), 0); assert_eq!(props.matched("none").len(), 0); } } gir-0.20.5/src/config/property_generate_flags.rs000066400000000000000000000047731475434152100217140ustar00rootroot00000000000000use std::str::FromStr; use bitflags::bitflags; use super::error::TomlHelper; bitflags! { #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct PropertyGenerateFlags: u32 { const GET = 1; const SET = 2; const NOTIFY = 4; } } impl FromStr for PropertyGenerateFlags { type Err = String; fn from_str(s: &str) -> Result { match s { "get" => Ok(Self::GET), "set" => Ok(Self::SET), "notify" => Ok(Self::NOTIFY), _ => Err(format!("Wrong property generate flag \"{s}\"")), } } } impl PropertyGenerateFlags { pub fn parse_flags(toml: &toml::Value, option: &str) -> Result { let array = toml.as_result_vec(option)?; let mut val = Self::empty(); for v in array { let s = v.as_str().ok_or(format!( "Invalid `{}` value element, expected a string, found {}", option, v.type_str() ))?; match Self::from_str(s) { Ok(v) => val |= v, e @ Err(_) => return e, } } Ok(val) } } #[cfg(test)] mod tests { use super::*; fn parse(val: &str) -> Result { let input = format!("generate={val}"); let table: toml::Value = toml::from_str(&input).unwrap(); let value = table.lookup("generate").unwrap(); PropertyGenerateFlags::parse_flags(value, "generate") } #[test] fn parse_flags() { assert_eq!(parse(r#"["get"]"#).unwrap(), PropertyGenerateFlags::GET); assert_eq!(parse(r#"["set"]"#).unwrap(), PropertyGenerateFlags::SET); assert_eq!( parse(r#"["notify"]"#).unwrap(), PropertyGenerateFlags::NOTIFY ); assert_eq!( parse(r#"["set","get"]"#).unwrap(), PropertyGenerateFlags::GET | PropertyGenerateFlags::SET ); assert_eq!( parse(r#""get""#), Err("Invalid `generate` value, expected a array, found string".into()) ); assert_eq!( parse(r#"[true]"#), Err("Invalid `generate` value element, expected a string, found boolean".into()) ); assert_eq!( parse(r#"["bad"]"#), Err("Wrong property generate flag \"bad\"".into()) ); assert_eq!( parse(r#"["get", "bad"]"#), Err("Wrong property generate flag \"bad\"".into()) ); } } gir-0.20.5/src/config/signals.rs000066400000000000000000000157471475434152100164450ustar00rootroot00000000000000use std::str::FromStr; use log::error; use toml::Value; use super::{ error::TomlHelper, functions::Return, gobjects::GStatus, ident::Ident, parameter_matchable::Functionlike, parsable::{Parsable, Parse}, }; use crate::{ library::{self, Nullable}, version::Version, }; #[derive(Clone, Copy, Debug)] pub enum TransformationType { None, Borrow, // replace from_glib_none to from_glib_borrow // TODO: configure TreePath, // convert string to TreePath } impl FromStr for TransformationType { type Err = String; fn from_str(s: &str) -> Result { match s { "none" => Ok(Self::None), "borrow" => Ok(Self::Borrow), "treepath" => Ok(Self::TreePath), _ => Err(format!("Wrong transformation \"{s}\"")), } } } #[derive(Clone, Debug)] pub struct Parameter { pub ident: Ident, pub nullable: Option, pub transformation: Option, pub new_name: Option, } impl Parse for Parameter { fn parse(toml: &Value, object_name: &str) -> Option { let Some(ident) = Ident::parse(toml, object_name, "signal parameter") else { error!( "No 'name' or 'pattern' given for parameter for object {}", object_name ); return None; }; toml.check_unwanted( &["nullable", "transformation", "new_name", "name", "pattern"], &format!("parameter {object_name}"), ); let nullable = toml .lookup("nullable") .and_then(Value::as_bool) .map(Nullable); let transformation = toml .lookup("transformation") .and_then(Value::as_str) .and_then(|s| { TransformationType::from_str(s) .map_err(|err| { error!("{0}", err); err }) .ok() }); let new_name = toml .lookup("new_name") .and_then(Value::as_str) .map(ToOwned::to_owned); Some(Self { ident, nullable, transformation, new_name, }) } } impl AsRef for Parameter { fn as_ref(&self) -> &Ident { &self.ident } } pub type Parameters = Vec; #[derive(Clone, Debug)] pub struct Signal { pub ident: Ident, pub status: GStatus, pub inhibit: bool, pub version: Option, pub parameters: Parameters, pub ret: Return, pub concurrency: library::Concurrency, pub doc_hidden: bool, pub doc_trait_name: Option, pub generate_doc: bool, } impl Signal { pub fn parse( toml: &Value, object_name: &str, concurrency: library::Concurrency, ) -> Option { let Some(ident) = Ident::parse(toml, object_name, "signal") else { error!( "No 'name' or 'pattern' given for signal for object {}", object_name ); return None; }; toml.check_unwanted( &[ "ignore", "manual", "inhibit", "version", "parameter", "return", "doc_hidden", "name", "pattern", "concurrency", "doc_trait_name", "generate_doc", ], &format!("signal {object_name}"), ); let status = { if toml .lookup("ignore") .and_then(Value::as_bool) .unwrap_or(false) { GStatus::Ignore } else if toml .lookup("manual") .and_then(Value::as_bool) .unwrap_or(false) { GStatus::Manual } else { GStatus::Generate } }; let inhibit = toml .lookup("inhibit") .and_then(Value::as_bool) .unwrap_or(false); let version = toml .lookup("version") .and_then(Value::as_str) .and_then(|s| s.parse().ok()); let parameters = Parameters::parse(toml.lookup("parameter"), object_name); let ret = Return::parse(toml.lookup("return"), object_name); let concurrency = toml .lookup("concurrency") .and_then(Value::as_str) .and_then(|v| v.parse().ok()) .unwrap_or(concurrency); let doc_hidden = toml .lookup("doc_hidden") .and_then(Value::as_bool) .unwrap_or(false); let doc_trait_name = toml .lookup("doc_trait_name") .and_then(Value::as_str) .map(ToOwned::to_owned); let generate_doc = toml .lookup("generate_doc") .and_then(Value::as_bool) .unwrap_or(true); Some(Self { ident, status, inhibit, version, parameters, ret, concurrency, doc_hidden, doc_trait_name, generate_doc, }) } } impl Functionlike for Signal { type Parameter = self::Parameter; fn parameters(&self) -> &[Self::Parameter] { &self.parameters } } impl AsRef for Signal { fn as_ref(&self) -> &Ident { &self.ident } } pub type Signals = Vec; #[cfg(test)] mod tests { use super::{super::ident::Ident, *}; fn toml(input: &str) -> ::toml::Value { let value = input.parse::<::toml::Value>(); assert!(value.is_ok()); value.unwrap() } #[test] fn signal_parse_default() { let toml = toml( r#" name = "signal1" "#, ); let f = Signal::parse(&toml, "a", Default::default()).unwrap(); assert_eq!(f.ident, Ident::Name("signal1".into())); assert!(f.status.need_generate()); } #[test] fn signal_parse_ignore() { let toml = toml( r#" name = "signal1" ignore = true "#, ); let f = Signal::parse(&toml, "a", Default::default()).unwrap(); assert!(f.status.ignored()); } #[test] fn signal_parse_manual() { let toml = toml( r#" name = "signal1" manual = true "#, ); let f = Signal::parse(&toml, "a", Default::default()).unwrap(); assert!(f.status.manual()); } #[test] fn signal_parse_generate_doc() { let r = toml( r#" name = "signal1" generate_doc = false "#, ); let f = Signal::parse(&r, "a", Default::default()).unwrap(); assert!(!f.generate_doc); // Ensure that the default value is "true". let r = toml( r#" name = "prop" "#, ); let f = Signal::parse(&r, "a", Default::default()).unwrap(); assert!(f.generate_doc); } } gir-0.20.5/src/config/string_type.rs000066400000000000000000000011071475434152100173350ustar00rootroot00000000000000use std::str::FromStr; #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum StringType { Utf8, // &str for input, String for return Filename, // Path for input, PathBuf for return OsString, // OsStr for input, OsString for return } impl FromStr for StringType { type Err = String; fn from_str(s: &str) -> Result { match s { "utf8" => Ok(Self::Utf8), "filename" => Ok(Self::Filename), "os_string" => Ok(Self::OsString), _ => Err(format!("Wrong string type '{s}'")), } } } gir-0.20.5/src/config/virtual_methods.rs000066400000000000000000000117211475434152100202020ustar00rootroot00000000000000use std::collections::HashSet; use log::error; use toml::Value; use super::{ error::TomlHelper, functions::{check_rename, Parameters, Return}, gobjects::GStatus, ident::Ident, parsable::{Parsable, Parse}, }; use crate::version::Version; #[derive(Clone, Debug)] pub struct VirtualMethod { pub ident: Ident, pub status: GStatus, pub version: Option, pub cfg_condition: Option, pub parameters: Parameters, pub ret: Return, pub doc_hidden: bool, pub doc_ignore_parameters: HashSet, pub doc_trait_name: Option, pub unsafe_: bool, pub rename: Option, pub bypass_auto_rename: bool, pub generate_doc: bool, } impl Parse for VirtualMethod { fn parse(toml: &Value, object_name: &str) -> Option { let Some(ident) = Ident::parse(toml, object_name, "virtual_method") else { error!( "No 'name' or 'pattern' given for virtual_method for object {}", object_name ); return None; }; toml.check_unwanted( &[ "ignore", "manual", "version", "cfg_condition", "parameter", "return", "name", "doc_hidden", "doc_ignore_parameters", "pattern", "doc_trait_name", "unsafe", "rename", "bypass_auto_rename", "generate_doc", ], &format!("virtual_method {object_name}"), ); let status = { if toml .lookup("ignore") .and_then(Value::as_bool) .unwrap_or(false) { GStatus::Ignore } else if toml .lookup("manual") .and_then(Value::as_bool) .unwrap_or(false) { GStatus::Manual } else { GStatus::Generate } }; let version = toml .lookup("version") .and_then(Value::as_str) .and_then(|s| s.parse().ok()); let cfg_condition = toml .lookup("cfg_condition") .and_then(Value::as_str) .map(ToOwned::to_owned); let parameters = Parameters::parse(toml.lookup("parameter"), object_name); let ret = Return::parse(toml.lookup("return"), object_name); let doc_hidden = toml .lookup("doc_hidden") .and_then(Value::as_bool) .unwrap_or(false); let doc_ignore_parameters = toml .lookup_vec("doc_ignore_parameters", "Invalid doc_ignore_parameters") .map(|v| { v.iter() .filter_map(|v| v.as_str().map(String::from)) .collect() }) .unwrap_or_default(); let doc_trait_name = toml .lookup("doc_trait_name") .and_then(Value::as_str) .map(ToOwned::to_owned); let unsafe_ = toml .lookup("unsafe") .and_then(Value::as_bool) .unwrap_or(false); let rename = toml .lookup("rename") .and_then(Value::as_str) .map(ToOwned::to_owned); if !check_rename(&rename, object_name, &ident) { return None; } let bypass_auto_rename = toml .lookup("bypass_auto_rename") .and_then(Value::as_bool) .unwrap_or(false); let generate_doc = toml .lookup("generate_doc") .and_then(Value::as_bool) .unwrap_or(true); Some(Self { ident, status, version, cfg_condition, parameters, ret, doc_hidden, doc_ignore_parameters, doc_trait_name, unsafe_, rename, bypass_auto_rename, generate_doc, }) } } impl AsRef for VirtualMethod { fn as_ref(&self) -> &Ident { &self.ident } } pub type VirtualMethods = Vec; #[cfg(test)] mod tests { use super::{super::ident::Ident, *}; fn toml(input: &str) -> ::toml::Value { let value = ::toml::from_str(input); assert!(value.is_ok()); value.unwrap() } #[test] fn function_parse_ignore() { let toml = toml( r#" name = "func1" ignore = true "#, ); let f = VirtualMethod::parse(&toml, "a").unwrap(); assert_eq!(f.ident, Ident::Name("func1".into())); assert!(f.status.ignored()); } #[test] fn function_parse_manual() { let toml = toml( r#" name = "func1" manual = true "#, ); let f = VirtualMethod::parse(&toml, "a").unwrap(); assert_eq!(f.ident, Ident::Name("func1".into())); assert!(f.status.manual()); } } gir-0.20.5/src/config/work_mode.rs000066400000000000000000000015431475434152100167600ustar00rootroot00000000000000use std::str::FromStr; #[derive(Default, Clone, Copy, Debug, Eq, PartialEq)] pub enum WorkMode { #[default] Normal, // generate widgets etc. Sys, // generate -sys with FFI Doc, // generate documentation file DisplayNotBound, // Show not bound types } impl WorkMode { pub fn is_normal(self) -> bool { matches!(self, Self::Normal) } pub fn is_generate_rust_files(self) -> bool { matches!(self, Self::Normal | Self::Sys) } } impl FromStr for WorkMode { type Err = String; fn from_str(s: &str) -> Result { match s { "normal" => Ok(Self::Normal), "sys" => Ok(Self::Sys), "doc" => Ok(Self::Doc), "not_bound" => Ok(Self::DisplayNotBound), _ => Err(format!("Wrong work mode '{s}'")), } } } gir-0.20.5/src/consts.rs000066400000000000000000000000551475434152100150330ustar00rootroot00000000000000pub const TYPE_PARAMETERS_START: char = 'P'; gir-0.20.5/src/custom_type_glib_priority.rs000066400000000000000000000030331475434152100210320ustar00rootroot00000000000000//! Adds `glib::Priority` as custom type //! and attempts replace priority parameters with it in async functions use crate::{ analysis::conversion_type::ConversionType, config::WorkMode, library::*, visitors::FunctionsMutVisitor, }; impl Library { pub fn add_glib_priority(&mut self, work_mode: WorkMode) { if work_mode != WorkMode::Normal { return; } let tid_int = self.find_type(0, "*.gint").expect("No basic type *.gint"); let glib_ns_id = self .find_namespace("GLib") .expect("Missing `GLib` namespace in add_glib_priority!"); let tid_priority = self.add_type( glib_ns_id, "Priority", Type::Custom(Custom { name: "Priority".to_string(), conversion_type: ConversionType::Scalar, }), ); let mut replacer = ReplaceToPriority { tid_priority, tid_int, }; self.namespace_mut(MAIN_NAMESPACE) .visit_functions_mut(&mut replacer); } } struct ReplaceToPriority { pub tid_priority: TypeId, pub tid_int: TypeId, } impl FunctionsMutVisitor for ReplaceToPriority { fn visit_function_mut(&mut self, func: &mut Function) -> bool { if !func.name.ends_with("_async") { return true; } for par in &mut func.parameters { if par.typ == self.tid_int && par.name.ends_with("priority") { par.typ = self.tid_priority; } } true } } gir-0.20.5/src/env.rs000066400000000000000000000046521475434152100143210ustar00rootroot00000000000000use std::cell::RefCell; use crate::{ analysis::{self, namespaces::NsId}, config::{gobjects::GStatus, Config}, library::*, nameutil::use_glib_type, version::Version, }; #[derive(Debug)] pub struct Env { pub library: Library, pub config: Config, pub namespaces: analysis::namespaces::Info, pub symbols: RefCell, pub class_hierarchy: analysis::class_hierarchy::Info, pub analysis: analysis::Analysis, } impl Env { #[inline] pub fn type_(&self, tid: TypeId) -> &Type { self.library.type_(tid) } pub fn type_status(&self, name: &str) -> GStatus { self.config .objects .get(name) .map(|o| o.status) .unwrap_or_default() } pub fn type_status_sys(&self, name: &str) -> GStatus { self.config .objects .get(name) .map_or(GStatus::Generate, |o| o.status) } pub fn is_totally_deprecated( &self, ns_id: Option, deprecated_version: Option, ) -> bool { let to_compare_with = self.config.min_required_version(self, ns_id); match (deprecated_version, to_compare_with) { (Some(v), Some(to_compare_v)) => { if v <= to_compare_v { self.config.deprecate_by_min_version } else { false } } (Some(v), _) => { if v <= self.config.min_cfg_version { self.config.deprecate_by_min_version } else { false } } _ => false, } } pub fn is_too_low_version(&self, ns_id: Option, version: Option) -> bool { let to_compare_with = self.config.min_required_version(self, ns_id); if let (Some(v), Some(to_compare_v)) = (version, to_compare_with) { return v <= to_compare_v; } false } pub fn main_sys_crate_name(&self) -> &str { &self.namespaces[MAIN_NAMESPACE].sys_crate_name } /// Helper to get the ffi crate import pub fn sys_crate_import(&self, type_id: TypeId) -> String { let crate_name = &self.namespaces[type_id.ns_id].sys_crate_name; if crate_name == "gobject_ffi" { use_glib_type(self, crate_name) } else { crate_name.clone() } } } gir-0.20.5/src/file_saver.rs000066400000000000000000000021231475434152100156370ustar00rootroot00000000000000use std::{ fs::{self, File}, io::{BufWriter, Result, Write}, path::Path, }; use crate::writer::untabber::Untabber; pub fn save_to_file(path: P, make_backup: bool, mut closure: F) where P: AsRef, F: FnMut(&mut dyn Write) -> Result<()>, { let path = path.as_ref(); if let Some(parent) = path.parent() { let _ = fs::create_dir_all(parent); } if make_backup { let _backuped = create_backup(path) .unwrap_or_else(|why| panic!("couldn't create backup for {path:?}: {why:?}")); } let file = File::create(path).unwrap_or_else(|why| panic!("couldn't create {path:?}: {why}")); let writer = BufWriter::new(file); let mut untabber = Untabber::new(Box::new(writer)); closure(&mut untabber).unwrap_or_else(|why| panic!("couldn't write to {path:?}: {why:?}")); } /// Create .bak file pub fn create_backup>(path: P) -> Result { if fs::metadata(&path).is_err() { return Ok(false); } let new_path = path.as_ref().with_extension("bak"); fs::rename(path, new_path).map(|_| true) } gir-0.20.5/src/fmt.rs000066400000000000000000000015441475434152100143140ustar00rootroot00000000000000use std::{path::Path, process::Command}; use log::warn; /// Check if `cargo fmt` available pub fn check_fmt() -> bool { let output = Command::new("cargo").arg("fmt").arg("--version").output(); if let Ok(output) = output { output.status.success() } else { false } } /// Run `cargo fmt` on path pub fn format(path: &Path) { let output = Command::new("cargo").arg("fmt").current_dir(path).output(); match output { Ok(output) if output.status.success() => {} Ok(output) => { warn!( "Failed to format {}:\n{}\n{}", path.display(), String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stderr) ); } Err(_) => { /*We checked `cargo` fmt presence in check_fmt, so can ignore errors*/ } } } gir-0.20.5/src/git.rs000066400000000000000000000056001475434152100143060ustar00rootroot00000000000000use std::{ io::Result, path::{Path, PathBuf}, process::{Command, Output}, }; fn git_command(path: impl AsRef, subcommand: &[&str]) -> Result { let git_path = path .as_ref() .to_str() .expect("Repository path must be a valid UTF-8 string"); let mut args = vec!["-C", git_path]; args.extend(subcommand); Command::new("git").args(&args).output() } pub fn repo_hash(path: impl AsRef) -> Option { let output = git_command(path.as_ref(), &["rev-parse", "--short=12", "HEAD"]).ok()?; if !output.status.success() { return None; } let hash = String::from_utf8(output.stdout).ok()?; let hash = hash.trim_end_matches('\n'); if dirty(path) { Some(format!("{hash}+")) } else { Some(hash.into()) } } fn dirty(path: impl AsRef) -> bool { match git_command(path.as_ref(), &["ls-files", "-m"]) { Ok(modified_files) => !modified_files.stdout.is_empty(), Err(_) => false, } } fn gitmodules_config(subcommand: &[&str]) -> Option { let mut args = vec!["config", "-f", ".gitmodules", "-z"]; args.extend(subcommand); let output = git_command(Path::new("."), &args).ok()?; if !output.status.success() { return None; } let mut result = String::from_utf8(output.stdout).ok()?; assert_eq!(result.pop(), Some('\0')); Some(result) } fn path_command(path: impl AsRef, subcommand: &[&str]) -> Option { let mut output = git_command(path, subcommand).ok()?; if !output.status.success() { return None; } assert_eq!( output .stdout .pop() .map(u32::from) .and_then(std::char::from_u32), Some('\n') ); Some(path_from_output(output.stdout)) } #[cfg(unix)] fn path_from_output(output: Vec) -> PathBuf { use std::{ffi::OsString, os::unix::prelude::OsStringExt}; OsString::from_vec(output).into() } #[cfg(not(unix))] fn path_from_output(output: Vec) -> PathBuf { String::from_utf8(output).unwrap().into() } pub fn toplevel(path: impl AsRef) -> Option { path_command(path, &["rev-parse", "--show-toplevel"]) } // Only build.rs uses this #[allow(dead_code)] pub fn git_dir(path: impl AsRef) -> Option { path_command(path, &["rev-parse", "--git-dir"]) } pub(crate) fn repo_remote_url(path: impl AsRef) -> Option { // Find the subsection that defines the module for the given path: let key_for_path = gitmodules_config(&[ "--name-only", "--get-regexp", r"submodule\..+\.path", &format!("^{}$", path.as_ref().display()), ])?; let subsection = key_for_path .strip_suffix(".path") .expect("submodule..path should end with '.path'"); gitmodules_config(&["--get", &format!("{subsection}.url")]) } gir-0.20.5/src/lib.rs000066400000000000000000000017561475434152100143010ustar00rootroot00000000000000#![allow(clippy::too_many_arguments)] #![allow(clippy::assigning_clones)] /// Log warning only if type in generated library macro_rules! warn_main { ($tid: expr, $target:expr, $($arg:tt)*) => ( if $tid.ns_id == crate::library::MAIN_NAMESPACE { log::warn!($target, $($arg)*); } ); } // generated by build.rs mod gir_version; pub mod analysis; mod case; mod chunk; mod codegen; mod config; mod consts; mod custom_type_glib_priority; mod env; mod file_saver; pub mod fmt; mod git; pub mod library; mod library_postprocessing; mod library_preprocessing; mod nameutil; mod parser; mod traits; pub mod update_version; mod version; mod visitors; mod writer; mod xmlparser; pub use crate::{ analysis::{ class_hierarchy::run as class_hierarchy_run, namespaces::run as namespaces_run, run as analysis_run, symbols::run as symbols_run, }, codegen::generate as codegen_generate, config::{Config, WorkMode}, env::Env, library::Library, }; gir-0.20.5/src/library.rs000066400000000000000000001210221475434152100151640ustar00rootroot00000000000000use std::{ cmp::{Ord, Ordering, PartialOrd}, collections::{BTreeMap, BTreeSet, HashMap, HashSet}, fmt, iter::Iterator, ops::{Deref, DerefMut}, str::FromStr, }; use crate::{ analysis::conversion_type::ConversionType, config::gobjects::GStatus, env::Env, nameutil::split_namespace_name, traits::*, version::Version, }; #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Transfer { None, Container, Full, } impl FromStr for Transfer { type Err = String; fn from_str(name: &str) -> Result { match name { "none" => Ok(Self::None), "container" => Ok(Self::Container), "full" => Ok(Self::Full), _ => Err(format!("Unknown ownership transfer mode '{name}'")), } } } #[derive(Default, Clone, Copy, Debug, Eq, PartialEq)] pub enum ParameterDirection { None, #[default] In, Out, InOut, Return, } impl ParameterDirection { pub fn is_in(self) -> bool { matches!(self, Self::In | Self::InOut) } pub fn is_out(self) -> bool { matches!(self, Self::Out | Self::InOut) } } impl FromStr for ParameterDirection { type Err = String; fn from_str(name: &str) -> Result { match name { "in" => Ok(Self::In), "out" => Ok(Self::Out), "inout" => Ok(Self::InOut), _ => Err(format!("Unknown parameter direction '{name}'")), } } } /// Annotation describing lifetime requirements / guarantees of callback /// parameters, that is callback itself and associated user data. #[derive(Default, Clone, Copy, Debug, PartialEq, Eq)] pub enum ParameterScope { /// Parameter is not of callback type. #[default] None, /// Used only for the duration of the call. /// /// Can be invoked multiple times. Call, /// Used for the duration of the asynchronous call. /// /// Invoked exactly once when asynchronous call completes. Async, /// Used until notified with associated destroy notify parameter. /// /// Can be invoked multiple times. Notified, /// Forever scope Forever, } impl ParameterScope { pub fn is_forever(self) -> bool { matches!(self, Self::Forever) } pub fn is_call(self) -> bool { matches!(self, Self::Call) } pub fn is_async(self) -> bool { matches!(self, Self::Async) } pub fn is_none(self) -> bool { matches!(self, Self::None) } } impl FromStr for ParameterScope { type Err = String; fn from_str(name: &str) -> Result { match name { "call" => Ok(Self::Call), "async" => Ok(Self::Async), "notified" => Ok(Self::Notified), "forever" => Ok(Self::Forever), _ => Err(format!("Unknown parameter scope type: {name}")), } } } #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct Nullable(pub bool); impl Deref for Nullable { type Target = bool; fn deref(&self) -> &bool { &self.0 } } impl DerefMut for Nullable { fn deref_mut(&mut self) -> &mut bool { &mut self.0 } } #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct Mandatory(pub bool); impl Deref for Mandatory { type Target = bool; fn deref(&self) -> &bool { &self.0 } } #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct Infallible(pub bool); impl Deref for Infallible { type Target = bool; fn deref(&self) -> &bool { &self.0 } } #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum FunctionKind { Constructor, Function, Method, Global, ClassMethod, VirtualMethod, } impl FromStr for FunctionKind { type Err = String; fn from_str(name: &str) -> Result { match name { "constructor" => Ok(Self::Constructor), "function" => Ok(Self::Function), "method" => Ok(Self::Method), "callback" => Ok(Self::Function), "global" => Ok(Self::Global), _ => Err(format!("Unknown function kind '{name}'")), } } } #[derive(Default, Clone, Copy, Debug, Eq, PartialEq)] pub enum Concurrency { #[default] None, Send, SendSync, } impl FromStr for Concurrency { type Err = String; fn from_str(name: &str) -> Result { match name { "none" => Ok(Self::None), "send" => Ok(Self::Send), "send+sync" => Ok(Self::SendSync), _ => Err(format!("Unknown concurrency kind '{name}'")), } } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Basic { None, Boolean, Int8, UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64, Char, UChar, Short, UShort, Int, UInt, Long, ULong, Size, SSize, Float, Double, Pointer, VarArgs, UniChar, Utf8, Filename, Type, IntPtr, UIntPtr, TimeT, OffT, DevT, GidT, PidT, SockLenT, UidT, // Same encoding as Filename but can contains any string // Not defined in GLib directly OsString, Bool, Unsupported, } impl Basic { pub fn requires_conversion(&self) -> bool { !matches!( self, Self::Int8 | Self::UInt8 | Self::Int16 | Self::UInt16 | Self::Int32 | Self::UInt32 | Self::Int64 | Self::UInt64 | Self::Char | Self::UChar | Self::Short | Self::UShort | Self::Int | Self::UInt | Self::Long | Self::ULong | Self::Size | Self::SSize | Self::Float | Self::Double | Self::Bool ) } } const BASIC: &[(&str, Basic)] = &[ ("none", Basic::None), ("gboolean", Basic::Boolean), ("gint8", Basic::Int8), ("guint8", Basic::UInt8), ("gint16", Basic::Int16), ("guint16", Basic::UInt16), ("gint32", Basic::Int32), ("guint32", Basic::UInt32), ("gint64", Basic::Int64), ("guint64", Basic::UInt64), ("gchar", Basic::Char), ("guchar", Basic::UChar), ("gshort", Basic::Short), ("gushort", Basic::UShort), ("gint", Basic::Int), ("guint", Basic::UInt), ("glong", Basic::Long), ("gulong", Basic::ULong), ("gsize", Basic::Size), ("gssize", Basic::SSize), ("gfloat", Basic::Float), ("gdouble", Basic::Double), ("long double", Basic::Unsupported), ("gunichar", Basic::UniChar), ("gconstpointer", Basic::Pointer), ("gpointer", Basic::Pointer), ("va_list", Basic::Unsupported), ("varargs", Basic::VarArgs), ("utf8", Basic::Utf8), ("filename", Basic::Filename), ("GType", Basic::Type), ("gintptr", Basic::IntPtr), ("guintptr", Basic::UIntPtr), // TODO: this is temporary name, change it when type added to GLib ("os_string", Basic::OsString), ("bool", Basic::Bool), ("time_t", Basic::TimeT), ("off_t", Basic::OffT), ("dev_t", Basic::DevT), ("gid_t", Basic::GidT), ("pid_t", Basic::PidT), ("socklen_t", Basic::SockLenT), ("uid_t", Basic::UidT), ]; #[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct TypeId { pub ns_id: u16, pub id: u32, } impl TypeId { pub fn full_name(self, library: &Library) -> String { let ns_name = &library.namespace(self.ns_id).name; let type_ = &library.type_(self); format!("{}.{}", ns_name, &type_.get_name()) } pub fn tid_none() -> TypeId { Default::default() } pub fn tid_bool() -> TypeId { TypeId { ns_id: 0, id: 1 } } pub fn tid_uint32() -> TypeId { TypeId { ns_id: 0, id: 7 } } pub fn tid_utf8() -> TypeId { TypeId { ns_id: 0, id: 28 } } pub fn tid_filename() -> TypeId { TypeId { ns_id: 0, id: 29 } } pub fn tid_os_string() -> TypeId { TypeId { ns_id: 0, id: 33 } } pub fn tid_c_bool() -> TypeId { TypeId { ns_id: 0, id: 34 } } pub fn is_basic_type(self, env: &Env) -> bool { env.library.type_(self).is_basic_type(env) } } #[derive(Debug)] pub struct Alias { pub name: String, pub c_identifier: String, pub typ: TypeId, pub target_c_type: String, pub doc: Option, pub doc_deprecated: Option, } #[derive(Debug)] pub struct Constant { pub name: String, pub c_identifier: String, pub typ: TypeId, pub c_type: String, pub value: String, pub version: Option, pub deprecated_version: Option, pub doc: Option, pub doc_deprecated: Option, } #[derive(Debug)] pub struct Member { pub name: String, pub c_identifier: String, pub value: String, pub doc: Option, pub doc_deprecated: Option, pub status: GStatus, pub version: Option, pub deprecated_version: Option, } #[derive(Debug)] pub enum ErrorDomain { Quark(String), Function(String), } #[derive(Debug)] pub struct Enumeration { pub name: String, pub c_type: String, pub symbol_prefix: Option, pub members: Vec, pub functions: Vec, pub version: Option, pub deprecated_version: Option, pub doc: Option, pub doc_deprecated: Option, pub error_domain: Option, pub glib_get_type: Option, } #[derive(Debug)] pub struct Bitfield { pub name: String, pub c_type: String, pub symbol_prefix: Option, pub members: Vec, pub functions: Vec, pub version: Option, pub deprecated_version: Option, pub doc: Option, pub doc_deprecated: Option, pub glib_get_type: Option, } #[derive(Default, Debug)] pub struct Record { pub name: String, pub c_type: String, pub symbol_prefix: Option, pub glib_get_type: Option, pub gtype_struct_for: Option, pub fields: Vec, pub functions: Vec, pub version: Option, pub deprecated_version: Option, pub doc: Option, pub doc_deprecated: Option, /// A 'pointer' record is one where the c:type is a typedef that /// doesn't look like a pointer, but is internally: typedef struct _X *X; pub pointer: bool, /// A 'disguised' record is one where the c:type is a typedef to /// a struct whose content and size are unknown, it is :typedef struct _X X; pub disguised: bool, } impl Record { pub fn has_free(&self) -> bool { self.functions.iter().any(|f| f.name == "free") || (self.has_copy() && self.has_destroy()) } pub fn has_copy(&self) -> bool { self.functions .iter() .any(|f| f.name == "copy" || f.name == "copy_into") } pub fn has_destroy(&self) -> bool { self.functions.iter().any(|f| f.name == "destroy") } pub fn has_unref(&self) -> bool { self.functions.iter().any(|f| f.name == "unref") } pub fn has_ref(&self) -> bool { self.functions.iter().any(|f| f.name == "ref") } } #[derive(Default, Debug)] pub struct Field { pub name: String, pub typ: TypeId, pub c_type: Option, pub private: bool, pub bits: Option, pub array_length: Option, pub doc: Option, } #[derive(Default, Debug)] pub struct Union { pub name: String, pub c_type: Option, pub symbol_prefix: Option, pub glib_get_type: Option, pub fields: Vec, pub functions: Vec, pub doc: Option, } #[derive(Debug)] pub struct Property { pub name: String, pub readable: bool, pub writable: bool, pub construct: bool, pub construct_only: bool, pub typ: TypeId, pub c_type: Option, pub transfer: Transfer, pub version: Option, pub deprecated_version: Option, pub doc: Option, pub doc_deprecated: Option, pub getter: Option, pub setter: Option, } #[derive(Clone, Debug)] pub struct Parameter { pub name: String, pub typ: TypeId, pub c_type: String, pub instance_parameter: bool, pub direction: ParameterDirection, pub transfer: Transfer, pub caller_allocates: bool, pub nullable: Nullable, pub array_length: Option, pub is_error: bool, pub doc: Option, pub scope: ParameterScope, /// Index of the user data parameter associated with the callback. pub closure: Option, /// Index of the destroy notification parameter associated with the /// callback. pub destroy: Option, } #[derive(Debug)] pub struct Function { pub name: String, pub c_identifier: Option, pub kind: FunctionKind, pub parameters: Vec, pub ret: Parameter, pub throws: bool, pub version: Option, pub deprecated_version: Option, pub doc: Option, pub doc_deprecated: Option, pub get_property: Option, pub set_property: Option, pub finish_func: Option, pub async_func: Option, pub sync_func: Option, } #[derive(Debug)] pub struct Signal { pub name: String, pub parameters: Vec, pub ret: Parameter, pub is_action: bool, pub is_detailed: bool, pub version: Option, pub deprecated_version: Option, pub doc: Option, pub doc_deprecated: Option, } #[derive(Default, Debug)] pub struct Interface { pub name: String, pub c_type: String, pub symbol_prefix: String, pub type_struct: Option, pub c_class_type: Option, pub glib_get_type: String, pub functions: Vec, pub virtual_methods: Vec, pub signals: Vec, pub properties: Vec, pub prerequisites: Vec, pub version: Option, pub deprecated_version: Option, pub doc: Option, pub doc_deprecated: Option, } #[derive(Default, Debug)] pub struct Class { pub name: String, pub c_type: String, pub symbol_prefix: String, pub type_struct: Option, pub c_class_type: Option, pub glib_get_type: String, pub fields: Vec, pub functions: Vec, pub virtual_methods: Vec, pub signals: Vec, pub properties: Vec, pub parent: Option, pub implements: Vec, pub final_type: bool, pub version: Option, pub deprecated_version: Option, pub doc: Option, pub doc_deprecated: Option, pub is_abstract: bool, pub is_fundamental: bool, /// Specific to fundamental types pub ref_fn: Option, pub unref_fn: Option, } #[derive(Debug)] pub struct Custom { pub name: String, pub conversion_type: ConversionType, } macro_rules! impl_lexical_ord { () => (); ($name:ident => $field:ident, $($more_name:ident => $more_field:ident,)*) => ( impl_lexical_ord!($($more_name => $more_field,)*); impl PartialEq for $name { fn eq(&self, other: &$name) -> bool { self.$field.eq(&other.$field) } } impl Eq for $name { } impl PartialOrd for $name { fn partial_cmp(&self, other: &$name) -> Option { Some(self.cmp(other)) } } impl Ord for $name { fn cmp(&self, other: &$name) -> Ordering { self.$field.cmp(&other.$field) } } ); } impl_lexical_ord!( Alias => c_identifier, Bitfield => c_type, Class => c_type, Enumeration => c_type, Function => c_identifier, Interface => c_type, Record => c_type, Union => c_type, Custom => name, ); #[derive(Debug, Eq, PartialEq)] pub enum Type { Basic(Basic), Alias(Alias), Enumeration(Enumeration), Bitfield(Bitfield), Record(Record), Union(Union), Function(Function), Interface(Interface), Class(Class), Custom(Custom), Array(TypeId), CArray(TypeId), FixedArray(TypeId, u16, Option), PtrArray(TypeId), HashTable(TypeId, TypeId), List(TypeId), SList(TypeId), } impl fmt::Display for Type { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(match self { Self::Basic(_) => "Basic", Self::Alias(_) => "Alias", Self::Enumeration(_) => "Enumeration", Self::Bitfield(_) => "Bitfield", Self::Record(_) => "Record", Self::Union(_) => "Union", Self::Function(_) => "Function", Self::Interface(_) => "Interface", Self::Class(_) => "Class", Self::Custom(_) => "Custom", Self::Array(_) => "Array", Self::CArray(_) => "CArray", Self::FixedArray(_, _, _) => "FixedArray", Self::PtrArray(_) => "PtrArray", Self::HashTable(_, _) => "HashTable", Self::List(_) => "List", Self::SList(_) => "SList", }) } } impl Type { pub fn get_name(&self) -> String { match self { Self::Basic(basic) => format!("{basic:?}"), Self::Alias(alias) => alias.name.clone(), Self::Enumeration(enum_) => enum_.name.clone(), Self::Bitfield(bit_field) => bit_field.name.clone(), Self::Record(rec) => rec.name.clone(), Self::Union(u) => u.name.clone(), Self::Function(func) => func.name.clone(), Self::Interface(interface) => interface.name.clone(), Self::Array(type_id) => format!("Array {type_id:?}"), Self::Class(class) => class.name.clone(), Self::Custom(custom) => custom.name.clone(), Self::CArray(type_id) => format!("CArray {type_id:?}"), Self::FixedArray(type_id, size, _) => format!("FixedArray {type_id:?}; {size}"), Self::PtrArray(type_id) => format!("PtrArray {type_id:?}"), Self::HashTable(key_type_id, value_type_id) => { format!("HashTable {key_type_id:?}/{value_type_id:?}") } Self::List(type_id) => format!("List {type_id:?}"), Self::SList(type_id) => format!("SList {type_id:?}"), } } pub fn get_deprecated_version(&self) -> Option { match self { Self::Basic(_) => None, Self::Alias(_) => None, Self::Enumeration(enum_) => enum_.deprecated_version, Self::Bitfield(bit_field) => bit_field.deprecated_version, Self::Record(rec) => rec.deprecated_version, Self::Union(_) => None, Self::Function(func) => func.deprecated_version, Self::Interface(interface) => interface.deprecated_version, Self::Array(_) => None, Self::Class(class) => class.deprecated_version, Self::Custom(_) => None, Self::CArray(_) => None, Self::FixedArray(..) => None, Self::PtrArray(_) => None, Self::HashTable(_, _) => None, Self::List(_) => None, Self::SList(_) => None, } } pub fn get_glib_name(&self) -> Option<&str> { match self { Self::Alias(alias) => Some(&alias.c_identifier), Self::Enumeration(enum_) => Some(&enum_.c_type), Self::Bitfield(bit_field) => Some(&bit_field.c_type), Self::Record(rec) => Some(&rec.c_type), Self::Union(union) => union.c_type.as_deref(), Self::Function(func) => func.c_identifier.as_deref(), Self::Interface(interface) => Some(&interface.c_type), Self::Class(class) => Some(&class.c_type), _ => None, } } pub fn c_array( library: &mut Library, inner: TypeId, size: Option, c_type: Option, ) -> TypeId { let name = Self::c_array_internal_name(inner, size, &c_type); if let Some(size) = size { library.add_type( INTERNAL_NAMESPACE, &name, Self::FixedArray(inner, size, c_type), ) } else { library.add_type(INTERNAL_NAMESPACE, &name, Self::CArray(inner)) } } pub fn find_c_array(library: &Library, inner: TypeId, size: Option) -> TypeId { let name = Self::c_array_internal_name(inner, size, &None); library .find_type(INTERNAL_NAMESPACE, &name) .unwrap_or_else(|| panic!("No type for '*.{name}'")) } fn c_array_internal_name(inner: TypeId, size: Option, c_type: &Option) -> String { if let Some(size) = size { format!("[#{inner:?}; {size};{c_type:?}]") } else { format!("[#{inner:?}]") } } pub fn container(library: &mut Library, name: &str, mut inner: Vec) -> Option { match (name, inner.len()) { ("GLib.Array", 1) => { let tid = inner.remove(0); Some((format!("Array(#{tid:?})"), Self::Array(tid))) } ("GLib.PtrArray", 1) => { let tid = inner.remove(0); Some((format!("PtrArray(#{tid:?})"), Self::PtrArray(tid))) } ("GLib.HashTable", 2) => { let k_tid = inner.remove(0); let v_tid = inner.remove(0); Some(( format!("HashTable(#{k_tid:?}, #{v_tid:?})"), Self::HashTable(k_tid, v_tid), )) } ("GLib.List", 1) => { let tid = inner.remove(0); Some((format!("List(#{tid:?})"), Self::List(tid))) } ("GLib.SList", 1) => { let tid = inner.remove(0); Some((format!("SList(#{tid:?})"), Self::SList(tid))) } _ => None, } .map(|(name, typ)| library.add_type(INTERNAL_NAMESPACE, &name, typ)) } pub fn function(library: &mut Library, func: Function) -> TypeId { let mut param_tids: Vec = func.parameters.iter().map(|p| p.typ).collect(); param_tids.push(func.ret.typ); let typ = Self::Function(func); library.add_type(INTERNAL_NAMESPACE, &format!("fn<#{param_tids:?}>"), typ) } pub fn union(library: &mut Library, u: Union, ns_id: u16) -> TypeId { let field_tids: Vec = u.fields.iter().map(|f| f.typ).collect(); let typ = Self::Union(u); library.add_type(ns_id, &format!("#{field_tids:?}"), typ) } pub fn record(library: &mut Library, r: Record, ns_id: u16) -> TypeId { let field_tids: Vec = r.fields.iter().map(|f| f.typ).collect(); let typ = Self::Record(r); library.add_type(ns_id, &format!("#{field_tids:?}"), typ) } pub fn functions(&self) -> &[Function] { match self { Self::Enumeration(e) => &e.functions, Self::Bitfield(b) => &b.functions, Self::Record(r) => &r.functions, Self::Union(u) => &u.functions, Self::Interface(i) => &i.functions, Self::Class(c) => &c.functions, _ => &[], } } pub fn is_basic(&self) -> bool { matches!(*self, Self::Basic(_)) } /// If the type is an Alias containing a basic, it'll return true (whereas /// `is_basic` won't). pub fn is_basic_type(&self, env: &Env) -> bool { match self { Self::Alias(x) => env.library.type_(x.typ).is_basic_type(env), x => x.is_basic(), } } pub fn get_inner_type<'a>(&'a self, env: &'a Env) -> Option<(&'a Type, u16)> { match *self { Self::Array(t) | Self::CArray(t) | Self::FixedArray(t, ..) | Self::PtrArray(t) | Self::List(t) | Self::SList(t) => { let ty = env.type_(t); ty.get_inner_type(env).or(Some((ty, t.ns_id))) } _ => None, } } pub fn is_function(&self) -> bool { matches!(*self, Self::Function(_)) } pub fn is_class(&self) -> bool { matches!(*self, Self::Class(_)) } pub fn is_interface(&self) -> bool { matches!(*self, Self::Interface(_)) } pub fn is_final_type(&self) -> bool { match *self { Self::Class(Class { final_type, .. }) => final_type, Self::Interface(..) => false, _ => true, } } pub fn is_fundamental(&self) -> bool { match *self { Self::Class(Class { is_fundamental, .. }) => is_fundamental, _ => false, } } pub fn is_abstract(&self) -> bool { match *self { Self::Class(Class { is_abstract, .. }) => is_abstract, _ => false, } } pub fn is_enumeration(&self) -> bool { matches!(*self, Self::Enumeration(_)) } pub fn is_bitfield(&self) -> bool { matches!(*self, Self::Bitfield(_)) } } macro_rules! impl_maybe_ref { () => (); ($name:ident, $($more:ident,)*) => ( impl_maybe_ref!($($more,)*); impl MaybeRef<$name> for Type { fn maybe_ref(&self) -> Option<&$name> { if let Self::$name(x) = self { Some(x) } else { None } } fn to_ref(&self) -> &$name { self.maybe_ref().unwrap_or_else(|| { panic!("{} is not a {}", self.get_name(), stringify!($name)) }) } } ); } impl_maybe_ref!( Alias, Bitfield, Class, Enumeration, Function, Basic, Interface, Record, Union, ); impl MaybeRefAs for U { fn maybe_ref_as(&self) -> Option<&T> where Self: MaybeRef, { self.maybe_ref() } fn to_ref_as(&self) -> &T where Self: MaybeRef, { self.to_ref() } } #[derive(Debug, Default)] pub struct Namespace { pub name: String, pub types: Vec>, pub index: BTreeMap, pub glib_name_index: HashMap, pub constants: Vec, pub functions: Vec, pub package_names: Vec, pub versions: BTreeSet, pub doc: Option, pub doc_deprecated: Option, pub shared_library: Vec, pub identifier_prefixes: Vec, pub symbol_prefixes: Vec, /// C headers, relative to include directories provided by pkg-config /// --cflags. pub c_includes: Vec, } impl Namespace { fn new(name: &str) -> Self { Self { name: name.into(), ..Self::default() } } fn add_constant(&mut self, c: Constant) { self.constants.push(c); } fn add_function(&mut self, f: Function) { self.functions.push(f); } fn type_(&self, id: u32) -> &Type { self.types[id as usize].as_ref().unwrap() } fn type_mut(&mut self, id: u32) -> &mut Type { self.types[id as usize].as_mut().unwrap() } fn add_type(&mut self, name: &str, typ: Option) -> u32 { let glib_name = typ .as_ref() .and_then(Type::get_glib_name) .map(ToOwned::to_owned); let id = if let Some(id) = self.find_type(name) { self.types[id as usize] = typ; id } else { let id = self.types.len() as u32; self.types.push(typ); self.index.insert(name.into(), id); id }; if let Some(s) = glib_name { self.glib_name_index.insert(s, id); } id } fn find_type(&self, name: &str) -> Option { self.index.get(name).copied() } } pub const INTERNAL_NAMESPACE_NAME: &str = "*"; pub const INTERNAL_NAMESPACE: u16 = 0; pub const MAIN_NAMESPACE: u16 = 1; #[derive(Debug)] pub struct Library { pub namespaces: Vec, pub index: HashMap, } impl Library { pub fn new(main_namespace_name: &str) -> Self { let mut library = Self { namespaces: Vec::new(), index: HashMap::new(), }; assert_eq!( INTERNAL_NAMESPACE, library.add_namespace(INTERNAL_NAMESPACE_NAME) ); for &(name, t) in BASIC { library.add_type(INTERNAL_NAMESPACE, name, Type::Basic(t)); } assert_eq!(MAIN_NAMESPACE, library.add_namespace(main_namespace_name)); // For string_type override Type::c_array(&mut library, TypeId::tid_utf8(), None, None); Type::c_array(&mut library, TypeId::tid_filename(), None, None); Type::c_array(&mut library, TypeId::tid_os_string(), None, None); library } pub fn show_non_bound_types(&self, env: &Env) { let not_allowed_ending = [ "Class", "Private", "Func", "Callback", "Accessible", "Iface", "Type", "Interface", ]; let namespace_name = self.namespaces[MAIN_NAMESPACE as usize].name.clone(); let mut parents = HashSet::new(); for x in self.namespace(MAIN_NAMESPACE).types.iter().flatten() { let name = x.get_name(); let full_name = format!("{namespace_name}.{name}"); let mut check_methods = true; if !not_allowed_ending.iter().any(|s| name.ends_with(s)) || x.is_enumeration() || x.is_bitfield() { let version = x.get_deprecated_version(); let depr_version = version.unwrap_or(env.config.min_cfg_version); if !env.analysis.objects.contains_key(&full_name) && !env.analysis.records.contains_key(&full_name) && !env.config.objects.iter().any(|o| o.1.name == full_name) && depr_version >= env.config.min_cfg_version { check_methods = false; if let Some(version) = version { println!("[NOT GENERATED] {full_name} (deprecated in {version})"); } else { println!("[NOT GENERATED] {full_name}"); } } else if let Type::Class(Class { properties, .. }) = x { if !env .config .objects .get(&full_name) .is_some_and(|obj| obj.generate_builder) && properties .iter() .any(|prop| prop.construct_only || prop.construct || prop.writable) { println!("[NOT GENERATED BUILDER] {full_name}Builder"); } } } if let (Some(tid), Some(gobject_id)) = ( env.library.find_type(0, &full_name), env.library.find_type(0, "GObject.Object"), ) { for &super_tid in env.class_hierarchy.supertypes(tid) { let ty = env.library.type_(super_tid); let ns_id = super_tid.ns_id as usize; let full_parent_name = format!("{}.{}", self.namespaces[ns_id].name, ty.get_name()); if super_tid != gobject_id && env .type_status(&super_tid.full_name(&env.library)) .ignored() && parents.insert(full_parent_name.clone()) { if let Some(version) = ty.get_deprecated_version() { println!( "[NOT GENERATED PARENT] {full_parent_name} (deprecated in {version})" ); } else { println!("[NOT GENERATED PARENT] {full_parent_name}"); } } } if check_methods { self.not_bound_functions( env, &format!("{full_name}::"), x.functions(), "METHOD", ); } } } self.not_bound_functions( env, &format!("{namespace_name}."), &self.namespace(MAIN_NAMESPACE).functions, "FUNCTION", ); } fn not_bound_functions(&self, env: &Env, prefix: &str, functions: &[Function], kind: &str) { for func in functions { let version = func.deprecated_version; let depr_version = version.unwrap_or(env.config.min_cfg_version); if depr_version < env.config.min_cfg_version { continue; } let mut errors = func .parameters .iter() .filter_map(|p| { let mut ty = env.library.type_(p.typ); let mut ns_id = p.typ.ns_id as usize; if let Some((t, n)) = ty.get_inner_type(env) { ty = t; ns_id = n as usize; } if ty.is_basic() { return None; } let full_name = format!("{}.{}", self.namespaces[ns_id].name, ty.get_name()); if env.type_status(&p.typ.full_name(&env.library)).ignored() && !env.analysis.objects.contains_key(&full_name) && !env.analysis.records.contains_key(&full_name) && !env.config.objects.iter().any(|o| o.1.name == full_name) { Some(full_name) } else { None } }) .collect::>(); { let mut ty = env.library.type_(func.ret.typ); let mut ns_id = func.ret.typ.ns_id as usize; if let Some((t, n)) = ty.get_inner_type(env) { ty = t; ns_id = n as usize; } if !ty.is_basic() { let full_name = format!("{}.{}", self.namespaces[ns_id].name, ty.get_name()); if env .type_status(&func.ret.typ.full_name(&env.library)) .ignored() && !env.analysis.objects.contains_key(&full_name) && !env.analysis.records.contains_key(&full_name) && !env.config.objects.iter().any(|o| o.1.name == full_name) { errors.push(full_name); } } } if !errors.is_empty() { let full_name = format!("{}{}", prefix, func.name); let deprecated_version = match version { Some(dv) => format!(" (deprecated in {dv})"), None => String::new(), }; if errors.len() > 1 { let end = errors.pop().unwrap(); let begin = errors.join(", "); println!( "[NOT GENERATED {kind}] {full_name}{deprecated_version} because of {begin} and {end}" ); } else { println!( "[NOT GENERATED {}] {}{} because of {}", kind, full_name, deprecated_version, errors[0] ); } } } } pub fn namespace(&self, ns_id: u16) -> &Namespace { &self.namespaces[ns_id as usize] } pub fn namespace_mut(&mut self, ns_id: u16) -> &mut Namespace { &mut self.namespaces[ns_id as usize] } pub fn find_namespace(&self, name: &str) -> Option { self.index.get(name).copied() } pub fn add_namespace(&mut self, name: &str) -> u16 { if let Some(&id) = self.index.get(name) { id } else { let id = self.namespaces.len() as u16; self.namespaces.push(Namespace::new(name)); self.index.insert(name.into(), id); id } } pub fn add_constant(&mut self, ns_id: u16, c: Constant) { self.namespace_mut(ns_id).add_constant(c); } pub fn add_function(&mut self, ns_id: u16, f: Function) { self.namespace_mut(ns_id).add_function(f); } pub fn add_type(&mut self, ns_id: u16, name: &str, typ: Type) -> TypeId { TypeId { ns_id, id: self.namespace_mut(ns_id).add_type(name, Some(typ)), } } #[allow(clippy::manual_map)] pub fn find_type(&self, current_ns_id: u16, name: &str) -> Option { let (mut ns, name) = split_namespace_name(name); if name == "GType" { ns = None; } if let Some(ns) = ns { self.find_namespace(ns).and_then(|ns_id| { self.namespace(ns_id) .find_type(name) .map(|id| TypeId { ns_id, id }) }) } else if let Some(id) = self.namespace(current_ns_id).find_type(name) { Some(TypeId { ns_id: current_ns_id, id, }) } else if let Some(id) = self.namespace(INTERNAL_NAMESPACE).find_type(name) { Some(TypeId { ns_id: INTERNAL_NAMESPACE, id, }) } else { None } } pub fn find_or_stub_type(&mut self, current_ns_id: u16, name: &str) -> TypeId { if let Some(tid) = self.find_type(current_ns_id, name) { return tid; } let (ns, name) = split_namespace_name(name); if let Some(ns) = ns { let ns_id = self .find_namespace(ns) .unwrap_or_else(|| self.add_namespace(ns)); let ns = self.namespace_mut(ns_id); let id = ns .find_type(name) .unwrap_or_else(|| ns.add_type(name, None)); return TypeId { ns_id, id }; } let id = self.namespace_mut(current_ns_id).add_type(name, None); TypeId { ns_id: current_ns_id, id, } } pub fn type_(&self, tid: TypeId) -> &Type { self.namespace(tid.ns_id).type_(tid.id) } pub fn type_mut(&mut self, tid: TypeId) -> &mut Type { self.namespace_mut(tid.ns_id).type_mut(tid.id) } pub fn register_version(&mut self, ns_id: u16, version: Version) { self.namespace_mut(ns_id).versions.insert(version); } pub fn types<'a>(&'a self) -> Box + 'a> { Box::new(self.namespaces.iter().enumerate().flat_map(|(ns_id, ns)| { ns.types.iter().enumerate().filter_map(move |(id, type_)| { let tid = TypeId { ns_id: ns_id as u16, id: id as u32, }; type_.as_ref().map(|t| (tid, t)) }) })) } /// Types from a single namespace in alphabetical order. pub fn namespace_types<'a>( &'a self, ns_id: u16, ) -> Box + 'a> { let ns = self.namespace(ns_id); Box::new(ns.index.values().map(move |&id| { ( TypeId { ns_id, id }, ns.types[id as usize].as_ref().unwrap(), ) })) } pub fn is_crate(&self, crate_name: &str) -> bool { self.namespace(MAIN_NAMESPACE).name == crate_name } pub fn is_glib_crate(&self) -> bool { self.is_crate("GObject") || self.is_crate("GLib") } } #[cfg(test)] mod tests { use super::*; #[test] fn basic_tids() { let lib = Library::new("Gtk"); assert_eq!(TypeId::tid_none().full_name(&lib), "*.None"); assert_eq!(TypeId::tid_bool().full_name(&lib), "*.Boolean"); assert_eq!(TypeId::tid_uint32().full_name(&lib), "*.UInt32"); assert_eq!(TypeId::tid_c_bool().full_name(&lib), "*.Bool"); assert_eq!(TypeId::tid_utf8().full_name(&lib), "*.Utf8"); assert_eq!(TypeId::tid_filename().full_name(&lib), "*.Filename"); assert_eq!(TypeId::tid_os_string().full_name(&lib), "*.OsString"); } } gir-0.20.5/src/library_postprocessing.rs000066400000000000000000000646731475434152100203500ustar00rootroot00000000000000use std::collections::HashMap; use log::{error, info}; use crate::{ analysis::types::IsIncomplete, config::{ gobjects::{GObject, GStatus}, matchable::Matchable, Config, WorkMode, }, library::*, nameutil, parser::is_empty_c_type, traits::MaybeRefAs, }; impl Namespace { fn unresolved(&self) -> Vec<&str> { self.index .iter() .filter_map(|(name, &id)| { if self.types[id as usize].is_none() { Some(name.as_str()) } else { None } }) .collect() } } type DetectedCTypes = HashMap; impl Library { pub fn postprocessing(&mut self, config: &Config) { self.fix_gtype(); self.check_resolved(); self.fill_empty_signals_c_types(); self.resolve_class_structs(); self.correlate_class_structs(); self.fix_fields(); self.make_unrepresentable_types_opaque(); self.mark_final_types(config); self.update_error_domain_functions(config); self.mark_ignored_enum_members(config); } fn fix_gtype(&mut self) { if let Some(ns_id) = self.find_namespace("GObject") { // hide the `GType` type alias in `GObject` self.add_type(ns_id, "Type", Type::Basic(Basic::Unsupported)); } } fn check_resolved(&self) { let list: Vec<_> = self .index .iter() .flat_map(|(name, &id)| { let name = name.clone(); self.namespace(id) .unresolved() .into_iter() .map(move |s| format!("{name}.{s}")) }) .collect(); assert!(list.is_empty(), "Incomplete library, unresolved: {list:?}"); } fn fill_empty_signals_c_types(&mut self) { fn update_empty_signals_c_types(signals: &mut [Signal], c_types: &DetectedCTypes) { for signal in signals { update_empty_signal_c_types(signal, c_types); } } fn update_empty_signal_c_types(signal: &mut Signal, c_types: &DetectedCTypes) { for par in &mut signal.parameters { update_empty_c_type(&mut par.c_type, par.typ, c_types); } update_empty_c_type(&mut signal.ret.c_type, signal.ret.typ, c_types); } fn update_empty_c_type(c_type: &mut String, tid: TypeId, c_types: &DetectedCTypes) { if !is_empty_c_type(c_type) { return; } if let Some(s) = c_types.get(&tid) { *c_type = s.clone(); } } let mut tids = Vec::new(); let mut c_types = DetectedCTypes::new(); for (ns_id, ns) in self.namespaces.iter().enumerate() { for (id, type_) in ns.types.iter().enumerate() { let type_ = type_.as_ref().unwrap(); // Always contains something let tid = TypeId { ns_id: ns_id as u16, id: id as u32, }; match type_ { Type::Class(klass) => { if self.detect_empty_signals_c_types(&klass.signals, &mut c_types) { tids.push(tid); } } Type::Interface(iface) => { if self.detect_empty_signals_c_types(&iface.signals, &mut c_types) { tids.push(tid); } } _ => (), } } } for tid in tids { match self.type_mut(tid) { Type::Class(klass) => update_empty_signals_c_types(&mut klass.signals, &c_types), Type::Interface(iface) => { update_empty_signals_c_types(&mut iface.signals, &c_types); } _ => (), } } } fn detect_empty_signals_c_types( &self, signals: &[Signal], c_types: &mut DetectedCTypes, ) -> bool { let mut detected = false; for signal in signals { if self.detect_empty_signal_c_types(signal, c_types) { detected = true; } } detected } fn detect_empty_signal_c_types(&self, signal: &Signal, c_types: &mut DetectedCTypes) -> bool { let mut detected = false; for par in &signal.parameters { if self.detect_empty_c_type(&par.c_type, par.typ, c_types) { detected = true; } } if self.detect_empty_c_type(&signal.ret.c_type, signal.ret.typ, c_types) { detected = true; } detected } fn detect_empty_c_type(&self, c_type: &str, tid: TypeId, c_types: &mut DetectedCTypes) -> bool { if !is_empty_c_type(c_type) { return false; } if let std::collections::hash_map::Entry::Vacant(entry) = c_types.entry(tid) { if let Some(detected_c_type) = self.c_type_by_type_id(tid) { entry.insert(detected_c_type); } } true } fn c_type_by_type_id(&self, tid: TypeId) -> Option { let type_ = self.type_(tid); type_.get_glib_name().map(|glib_name| { if self.is_referenced_type(type_) { format!("{glib_name}*") } else { glib_name.to_string() } }) } fn is_referenced_type(&self, type_: &Type) -> bool { use crate::library::Type::*; match type_ { Alias(alias) => self.is_referenced_type(self.type_(alias.typ)), Record(..) | Union(..) | Class(..) | Interface(..) => true, _ => false, } } fn resolve_class_structs(&mut self) { // stores pairs of (gtype-struct-c-name, type-name) let mut structs_and_types = Vec::new(); for (ns_id, ns) in self.namespaces.iter().enumerate() { for type_ in &ns.types { let type_ = type_.as_ref().unwrap(); // Always contains something if let Type::Record(record) = type_ { if let Some(ref struct_for) = record.gtype_struct_for { if let Some(struct_for_tid) = self.find_type(ns_id as u16, struct_for) { structs_and_types.push((record.c_type.clone(), struct_for_tid)); } } } } } for (gtype_struct_c_type, struct_for_tid) in structs_and_types { match self.type_mut(struct_for_tid) { Type::Class(klass) => { klass.c_class_type = Some(gtype_struct_c_type); } Type::Interface(iface) => { iface.c_class_type = Some(gtype_struct_c_type); } x => unreachable!( "Something other than a class or interface has a class struct: {:?}", x ), } } } fn correlate_class_structs(&self) { for (ns_id, ns) in self.namespaces.iter().enumerate() { for type_ in &ns.types { let type_ = type_.as_ref().unwrap(); // Always contains something let name; let type_struct; let c_class_type; match type_ { Type::Class(klass) => { name = &klass.name; type_struct = &klass.type_struct; c_class_type = &klass.c_class_type; } Type::Interface(iface) => { name = &iface.name; type_struct = &iface.type_struct; c_class_type = &iface.c_class_type; } _ => { continue; } } if let Some(type_struct) = type_struct { let type_struct_tid = self.find_type(ns_id as u16, type_struct); assert!( type_struct_tid.is_some(), "\"{name}\" has glib:type-struct=\"{type_struct}\" but there is no such record" ); let type_struct_type = self.type_(type_struct_tid.unwrap()); if let Type::Record(r) = type_struct_type { if r.gtype_struct_for.as_ref() != Some(name) { if let Some(ref gtype_struct_for) = r.gtype_struct_for { panic!("\"{}\" has glib:type-struct=\"{}\" but the corresponding record \"{}\" has glib:is-gtype-struct-for={:?}", name, type_struct, r.name, gtype_struct_for); } else { panic!("\"{}\" has glib:type-struct=\"{}\" but the corresponding record \"{}\" has no glib:is-gtype-struct-for attribute", name, type_struct, r.name); } } } else { panic!( "Element with name=\"{type_struct}\" should be a record but it isn't" ); } } else if let Some(c) = c_class_type { panic!("\"{name}\" has no glib:type-struct but there is an element with glib:is-gtype-struct-for=\"{c}\""); } // else both type_struct and c_class_type are None, // and that's fine because they don't reference each // other. } } } fn fix_fields(&mut self) { enum Action { SetCType(String), SetName(String), } let mut actions: Vec<(TypeId, usize, Action)> = Vec::new(); for (ns_id, ns) in self.namespaces.iter().enumerate() { for (id, type_) in ns.types.iter().enumerate() { let type_ = type_.as_ref().unwrap(); // Always contains something let tid = TypeId { ns_id: ns_id as u16, id: id as u32, }; match type_ { Type::Class(Class { name, fields, .. }) | Type::Record(Record { name, fields, .. }) | Type::Union(Union { name, fields, .. }) => { for (fid, field) in fields.iter().enumerate() { if nameutil::needs_mangling(&field.name) { let new_name = nameutil::mangle_keywords(&*field.name).into_owned(); actions.push((tid, fid, Action::SetName(new_name))); } if field.c_type.is_some() { continue; } let field_type = self.type_(field.typ); if field_type.maybe_ref_as::().is_some() { // Function pointers generally don't have c_type. continue; } if let Some(c_type) = field_type.get_glib_name() { actions.push((tid, fid, Action::SetCType(c_type.to_owned()))); continue; } if let Type::Basic(Basic::Pointer) = field_type { // For example SoupBuffer is missing c:type for data field. actions.push((tid, fid, Action::SetCType("void*".to_owned()))); continue; } if let Type::FixedArray(..) = field_type { // fixed-size Arrays can only have inner c_type // HACK: field c_type used only in sys mode for pointer checking // so any string without * will work let array_c_type = "fixed_array".to_owned(); actions.push((tid, fid, Action::SetCType(array_c_type))); continue; } error!("Field `{}::{}` is missing c:type", name, &field.name); } } _ => {} } } } let ignore_missing_ctype = ["padding", "reserved", "_padding", "_reserved"]; for (tid, fid, action) in actions { match self.type_mut(tid) { Type::Class(Class { name, fields, .. }) | Type::Record(Record { name, fields, .. }) | Type::Union(Union { name, fields, .. }) => match action { Action::SetCType(c_type) => { // Don't be verbose when internal fields such as padding don't provide a // c-type if !ignore_missing_ctype.contains(&fields[fid].name.as_str()) { warn_main!( tid, "Field `{}::{}` missing c:type assumed to be `{}`", name, &fields[fid].name, c_type ); } fields[fid].c_type = Some(c_type); } Action::SetName(name) => fields[fid].name = name, }, _ => unreachable!("Expected class, record or union"), } } } fn make_unrepresentable_types_opaque(&mut self) { // Unions with non-`Copy` fields are unstable (see issue #32836). // It would seem that this shouldn't be cause for concern as one can // always make all types in the union copyable. // // Unfortunately, this is not that simple, as some types are currently // unrepresentable in Rust, and they do occur inside the unions. // Thus to avoid the problem, we mark all unions with such unrepresentable // types as opaque, and don't generate their definitions. let mut unrepresentable: Vec = Vec::new(); for (ns_id, ns) in self.namespaces.iter().enumerate() { for (id, type_) in ns.types.iter().enumerate() { let type_ = type_.as_ref().unwrap(); let tid = TypeId { ns_id: ns_id as u16, id: id as u32, }; match type_ { Type::Union(Union { fields, .. }) if fields.as_slice().is_incomplete(self) => { unrepresentable.push(tid); } _ => {} } } } for tid in unrepresentable { match self.type_mut(tid) { Type::Union(Union { name, fields, .. }) => { info!("Type `{}` is not representable.", name); fields.clear(); } _ => unreachable!("Expected a union"), } } } fn has_subtypes(&self, parent_tid: TypeId) -> bool { for (tid, _) in self.types() { if let Type::Class(class) = self.type_(tid) { if class.parent == Some(parent_tid) { return true; } } } false } fn mark_final_types(&mut self, config: &Config) { // Here we mark all class types as final types if configured so in the config or // otherwise if there is no public class struct for the type or the instance // struct has no fields (i.e. is not known!), and there are no known // subtypes. // // Final types can't have any subclasses and we handle them slightly different // for that reason. // FIXME: without class_hierarchy this function O(n2) due inner loop in // `has_subtypes` let mut overridden_final_types: Vec<(TypeId, bool)> = Vec::new(); for (ns_id, ns) in self.namespaces.iter().enumerate() { for (id, type_) in ns.types.iter().enumerate() { let type_ = type_.as_ref().unwrap(); // Always contains something if let Type::Class(klass) = type_ { let tid = TypeId { ns_id: ns_id as u16, id: id as u32, }; let full_name = tid.full_name(self); let obj = config.objects.get(&*full_name); if let Some(GObject { final_type: Some(final_type), .. }) = obj { // The config might also be used to override a type that is wrongly // detected as final type otherwise overridden_final_types.push((tid, *final_type)); } else if klass.final_type { continue; } else if klass.type_struct.is_none() { let is_final = !self.has_subtypes(tid); if is_final { overridden_final_types.push((tid, true)); } } else { let has_subtypes = self.has_subtypes(tid); let instance_struct_known = !klass.fields.is_empty(); let class_struct_known = if let Some(class_record_tid) = self.find_type(ns_id as u16, klass.type_struct.as_ref().unwrap()) { if let Type::Record(record) = self.type_(class_record_tid) { !record.disguised && !record.pointer } else { unreachable!("Type {} with non-record class", full_name); } } else { unreachable!("Can't find class for {}", full_name); }; let is_final = !has_subtypes && (!instance_struct_known || !class_struct_known); if is_final { overridden_final_types.push((tid, true)); } }; } } } for (tid, new_is_final) in overridden_final_types { if let Type::Class(Class { final_type, .. }) = self.type_mut(tid) { *final_type = new_is_final; } else { unreachable!(); } } } fn update_error_domain_functions(&mut self, config: &Config) { // Find find all error domains that have corresponding functions let mut error_domains = vec![]; for (ns_id, ns) in self.namespaces.iter().enumerate() { 'next_enum: for (id, type_) in ns.types.iter().enumerate() { let type_ = type_.as_ref().unwrap(); // Always contains something let enum_tid = TypeId { ns_id: ns_id as u16, id: id as u32, }; if let Type::Enumeration(enum_) = type_ { if let Some(ErrorDomain::Quark(ref domain)) = enum_.error_domain { let domain = domain.replace('-', "_"); let mut function_candidates = vec![domain.clone()]; if !domain.ends_with("_quark") { function_candidates.push(format!("{domain}_quark")); } if !domain.ends_with("_error_quark") { if domain.ends_with("_quark") { function_candidates .push(format!("{}_error_quark", &domain[..(domain.len() - 6)])); } else { function_candidates.push(format!("{domain}_error_quark")); } } if let Some(domain) = domain.strip_suffix("_error_quark") { function_candidates.push(domain.to_owned()); } if let Some(domain) = domain.strip_suffix("_quark") { function_candidates.push(domain.to_owned()); } if let Some(func) = ns.functions.iter().find(|f| { function_candidates .iter() .any(|c| f.c_identifier.as_ref() == Some(c)) }) { error_domains.push(( ns_id, enum_tid, None, func.c_identifier.as_ref().unwrap().clone(), )); continue 'next_enum; } // Quadratic in number of types... for (id, type_) in ns.types.iter().enumerate() { let type_ = type_.as_ref().unwrap(); // Always contains something let domain_tid = TypeId { ns_id: ns_id as u16, id: id as u32, }; let functions = match type_ { Type::Enumeration(Enumeration { functions, .. }) | Type::Class(Class { functions, .. }) | Type::Record(Record { functions, .. }) | Type::Interface(Interface { functions, .. }) => functions, _ => continue, }; if let Some(func) = functions.iter().find(|f| { function_candidates .iter() .any(|c| f.c_identifier.as_ref() == Some(c)) }) { error_domains.push(( ns_id, enum_tid, Some(domain_tid), func.c_identifier.as_ref().unwrap().clone(), )); continue 'next_enum; } } } } } } for (ns_id, enum_tid, domain_tid, function_name) in error_domains { if config.work_mode != WorkMode::Sys { if let Some(domain_tid) = domain_tid { match self.type_mut(domain_tid) { Type::Enumeration(Enumeration { functions, .. }) | Type::Class(Class { functions, .. }) | Type::Record(Record { functions, .. }) | Type::Interface(Interface { functions, .. }) => { let pos = functions .iter() .position(|f| f.c_identifier.as_ref() == Some(&function_name)) .unwrap(); functions.remove(pos); } _ => unreachable!(), } } else { let pos = self.namespaces[ns_id] .functions .iter() .position(|f| f.c_identifier.as_ref() == Some(&function_name)) .unwrap(); self.namespaces[ns_id].functions.remove(pos); } } if let Type::Enumeration(enum_) = self.type_mut(enum_tid) { assert!(enum_.error_domain.is_some()); enum_.error_domain = Some(ErrorDomain::Function(function_name)); } else { unreachable!(); } } } fn mark_ignored_enum_members(&mut self, config: &Config) { let mut members_to_change = vec![]; for (ns_id, ns) in self.namespaces.iter().enumerate() { for (id, _type_) in ns.types.iter().enumerate() { let type_id = TypeId { ns_id: ns_id as u16, id: id as u32, }; match self.type_(type_id) { Type::Bitfield(Bitfield { name, members, .. }) | Type::Enumeration(Enumeration { name, members, .. }) => { let full_name = format!("{}.{}", ns.name, name); let config = config.objects.get(&full_name); let mut type_members = HashMap::new(); for member in members.iter() { let status = config.and_then(|m| { m.members.matched(&member.name).first().map(|m| m.status) }); type_members.insert(member.c_identifier.clone(), status); } members_to_change.push((type_id, type_members)); } _ => (), }; } } for (type_id, item_members) in members_to_change { match self.type_mut(type_id) { Type::Bitfield(Bitfield { members, .. }) | Type::Enumeration(Enumeration { members, .. }) => { for member in members.iter_mut() { let status = item_members .get(&member.c_identifier) .copied() .flatten() .unwrap_or(GStatus::Generate); member.status = status; } } _ => (), }; } } } gir-0.20.5/src/library_preprocessing.rs000066400000000000000000000002511475434152100201270ustar00rootroot00000000000000use crate::{config::WorkMode, library::*}; impl Library { pub fn preprocessing(&mut self, work_mode: WorkMode) { self.add_glib_priority(work_mode); } } gir-0.20.5/src/main.rs000066400000000000000000000140121475434152100144440ustar00rootroot00000000000000use std::{cell::RefCell, env, path::PathBuf, process, str::FromStr}; use getopts::Options; use hprof::Profiler; use libgir::{self as gir, Config, Library, WorkMode}; fn print_usage(program: &str, opts: Options) { let brief = format!( "Usage: {program} [options] [ ] {program} (-h | --help)" ); print!("{}", opts.usage(&brief)); } trait OptionStr { fn as_str_ref(&self) -> Option<&str>; } impl> OptionStr for Option { fn as_str_ref(&self) -> Option<&str> { self.as_ref().map(|string| string.as_ref()) } } enum RunKind { Config(Config), CheckGirFile(String), } fn build_config() -> Result { let args: Vec<_> = env::args().collect(); let program = args[0].clone(); let mut options = Options::new(); options.optopt( "c", "config", "Config file path (default: Gir.toml)", "CONFIG", ); options.optflag("h", "help", "Show this message"); options.optmulti( "d", "girs-directories", "Directories for GIR files", "GIRSPATH", ); options.optopt( "m", "mode", "Work mode: doc, normal, sys or not_bound", "MODE", ); options.optopt("o", "target", "Target path", "PATH"); options.optopt("p", "doc-target-path", "Doc target path", "PATH"); options.optflag("b", "make-backup", "Make backup before generating"); options.optflag("s", "stats", "Show statistics"); options.optflag("", "disable-format", "Disable formatting generated code"); options.optopt( "", "check-gir-file", "Check if the given `.gir` file is valid", "PATH", ); let matches = options.parse(&args[1..]).map_err(|e| e.to_string())?; if let Some(check_gir_file) = matches.opt_str("check-gir-file") { return Ok(RunKind::CheckGirFile(check_gir_file)); } if matches.opt_present("h") { print_usage(&program, options); process::exit(0); } let work_mode = match matches.opt_str("m") { None => None, Some(s) => match WorkMode::from_str(&s) { Ok(w) => Some(w), Err(e) => { eprintln!("Error (switching to default work mode): {e}"); None } }, }; Config::new( matches.opt_str("c").as_str_ref(), work_mode, &matches.opt_strs("d"), matches.free.first().as_str_ref(), matches.free.get(1).as_str_ref(), matches.opt_str("o").as_str_ref(), matches.opt_str("doc-target-path").as_str_ref(), matches.opt_present("b"), matches.opt_present("s"), matches.opt_present("disable-format"), ) .map(RunKind::Config) } fn run_check(check_gir_file: &str) -> Result<(), String> { let path = PathBuf::from(check_gir_file); if !path.is_file() { return Err(format!("`{check_gir_file}`: file not found",)); } let lib_name = path .file_stem() .ok_or(format!("Failed to get file stem from `{check_gir_file}`",))?; let lib_name = lib_name .to_str() .ok_or_else(|| "failed to convert OsStr to str".to_owned())?; let mut library = Library::new(lib_name); let parent = path.parent().ok_or(format!( "Failed to get parent directory from `{check_gir_file}`", ))?; library.read_file(&[parent], &mut vec![lib_name.to_owned()]) } fn main() -> Result<(), String> { if std::env::var_os("RUST_LOG").is_none() { std::env::set_var("RUST_LOG", "gir=warn,libgir=warn"); } env_logger::init(); let mut cfg = match build_config() { Ok(RunKind::CheckGirFile(check_gir_file)) => return run_check(&check_gir_file), Ok(RunKind::Config(cfg)) => cfg, Err(err) => return Err(err), }; cfg.check_disable_format(); let statistics = Profiler::new("Gir"); statistics.start_frame(); let watcher_total = statistics.enter("Total"); let mut library = { let _watcher = statistics.enter("Loading"); let mut library = Library::new(&cfg.library_name); library.read_file(&cfg.girs_dirs, &mut vec![cfg.library_full_name()])?; library }; { let _watcher = statistics.enter("Preprocessing"); library.preprocessing(cfg.work_mode); } { let _watcher = statistics.enter("Update library by config"); gir::update_version::apply_config(&mut library, &cfg); } { let _watcher = statistics.enter("Postprocessing"); library.postprocessing(&cfg); } { let _watcher = statistics.enter("Resolving type ids"); cfg.resolve_type_ids(&library); } { let _watcher = statistics.enter("Checking versions"); gir::update_version::check_function_real_version(&mut library); } let mut env = { let _watcher = statistics.enter("Namespace/symbol/class analysis"); let namespaces = gir::namespaces_run(&library); let symbols = gir::symbols_run(&library, &namespaces); let class_hierarchy = gir::class_hierarchy_run(&library); gir::Env { library, config: cfg, namespaces, symbols: RefCell::new(symbols), class_hierarchy, analysis: Default::default(), } }; if env.config.work_mode != WorkMode::Sys { let _watcher = statistics.enter("Analyzing"); gir::analysis_run(&mut env); } if env.config.work_mode != WorkMode::DisplayNotBound { let _watcher = statistics.enter("Generating"); gir::codegen_generate(&env); } if !env.config.disable_format && env.config.work_mode.is_generate_rust_files() { let _watcher = statistics.enter("Formatting"); gir::fmt::format(&env.config.target_path); } drop(watcher_total); statistics.end_frame(); if env.config.show_statistics { statistics.print_timing(); } if env.config.work_mode == WorkMode::DisplayNotBound { env.library.show_non_bound_types(&env); } Ok(()) } gir-0.20.5/src/nameutil.rs000066400000000000000000000136601475434152100153460ustar00rootroot00000000000000use std::{borrow::Cow, collections::HashMap, path::*, sync::OnceLock}; use crate::case::*; static CRATE_NAME_OVERRIDES: OnceLock> = OnceLock::new(); pub(crate) fn set_crate_name_overrides(overrides: HashMap) { assert!( CRATE_NAME_OVERRIDES.set(overrides).is_ok(), "Crate name overrides already set" ); } fn get_crate_name_override(crate_name: &str) -> Option { CRATE_NAME_OVERRIDES .get() .and_then(|overrides| overrides.get(crate_name).cloned()) } pub fn split_namespace_name(name: &str) -> (Option<&str>, &str) { let mut parts = name.split('.'); let name = parts.next_back().unwrap(); let ns = parts.next_back(); assert!(ns.is_none() || parts.next().is_none()); (ns, name) } // unused :( // pub fn strip_suffix<'a>(name: &'a str, suffix: &str) -> Option<&'a str> { // if name.ends_with(suffix) { // Some(&name[..name.len() - suffix.len()]) // } // else { // None // } // } pub fn file_name_sys(name: &str) -> String { let mut path = PathBuf::from(name); let added = path.set_extension("rs"); assert!(added); path.to_str().unwrap().into() } /// Crate name with underscores for `use` statement pub fn crate_name(name: &str) -> String { let name = name.replace('-', "_").to_snake(); let crate_name = if let Some(name_without_prefix) = name.strip_prefix("g_") { name_without_prefix.to_owned() } else { name }; if let Some(crate_name) = get_crate_name_override(&crate_name) { crate_name } else { crate_name } } /// Crate name with '-' for Cargo.toml etc. pub fn exported_crate_name(crate_name: &str) -> String { crate_name.replace('_', "-") } pub fn module_name(name: &str) -> String { mangle_keywords(name.to_snake()).into_owned() } pub fn enum_member_name(name: &str) -> String { if name.starts_with(char::is_alphabetic) { name.to_camel() } else { format!("_{}", name.to_camel()) } } pub fn bitfield_member_name(name: &str) -> String { if name.starts_with(char::is_alphabetic) { name.to_uppercase() } else { format!("_{}", name.to_uppercase()) } } pub fn needs_mangling(name: &str) -> bool { keywords().contains_key(name) } // If the mangling happened, guaranteed to return Owned. pub fn mangle_keywords<'a, S: Into>>(name: S) -> Cow<'a, str> { let name = name.into(); if let Some(s) = keywords().get(&*name) { s.clone().into() } else { name } } fn keywords() -> &'static HashMap<&'static str, String> { static KEYWORDS: OnceLock> = OnceLock::new(); KEYWORDS.get_or_init(|| { [ "abstract", "alignof", "as", "async", "await", "become", "box", "break", "const", "continue", "crate", "do", "dyn", "else", "enum", "extern", "false", "final", "fn", "for", "if", "impl", "in", "let", "loop", "macro", "match", "mod", "move", "mut", "offsetof", "override", "priv", "proc", "pub", "pure", "ref", "return", "Self", "self", "sizeof", "static", "struct", "super", "trait", "true", "try", "type", "typeof", "unsafe", "unsized", "use", "virtual", "where", "while", "yield", ] .iter() .map(|k| (*k, format!("{k}_"))) .collect() }) } pub fn signal_to_snake(signal: &str) -> String { signal.replace("::", "_").replace('-', "_") } pub fn lib_name_to_toml(name: &str) -> String { name.to_string().replace(['-', '.'], "_") } pub fn use_glib_type(env: &crate::env::Env, import: &str) -> String { format!( "{}::{}", if env.library.is_glib_crate() { "crate" } else { "glib" }, import ) } pub fn use_glib_if_needed(env: &crate::env::Env, import: &str) -> String { format!( "{}{}", if env.library.is_glib_crate() { "" } else { "glib::" }, import ) } pub fn use_gio_type(env: &crate::env::Env, import: &str) -> String { format!( "{}::{}", if env.library.is_crate("Gio") { "crate" } else { "gio" }, import ) } pub fn use_gtk_type(env: &crate::env::Env, import: &str) -> String { format!( "{}::{}", if env.library.is_crate("Gtk") { "crate" } else { "gtk" }, import ) } pub fn is_gstring(name: &str) -> bool { name == "GString" || name.ends_with("::GString") } #[cfg(test)] mod tests { use super::*; #[test] fn split_no_namespace() { let (ns, name) = split_namespace_name("GObject"); assert_eq!(ns, None); assert_eq!(name, "GObject"); } #[test] fn split_full_name() { let (ns, name) = split_namespace_name("Gtk.StatusIcon"); assert_eq!(ns, Some("Gtk")); assert_eq!(name, "StatusIcon"); } // #[test] // fn strip_prefix_g() { // assert_eq!(strip_prefix("G", "GBusType"), "BusType"); // assert_eq!(strip_prefix("G", "G_BUS_TYPE_NONE"), "BUS_TYPE_NONE"); // } // // #[test] // fn strip_prefix_gtk() { // assert_eq!(strip_prefix("Gtk", "GtkAlign"), "Align"); // assert_eq!(strip_prefix("Gtk", "GTK_ALIGN_FILL"), "ALIGN_FILL"); // } #[test] fn crate_name_works() { assert_eq!(crate_name("GdkPixbuf"), "gdk_pixbuf"); assert_eq!(crate_name("GLib"), "glib"); assert_eq!(crate_name("GObject"), "gobject"); assert_eq!(crate_name("Gtk"), "gtk"); } #[test] fn file_name_sys_works() { assert_eq!(file_name_sys("funcs"), "funcs.rs"); } #[test] fn signal_to_snake_works() { assert_eq!(signal_to_snake("changed"), "changed"); assert_eq!(signal_to_snake("move-active"), "move_active"); } #[test] fn lib_name_to_toml_works() { assert_eq!(lib_name_to_toml("gstreamer-1.0"), "gstreamer_1_0"); } } gir-0.20.5/src/parser.rs000066400000000000000000001571221475434152100150260ustar00rootroot00000000000000use std::{ path::{Path, PathBuf}, str::FromStr, }; use log::{trace, warn}; use crate::{ library::*, version::Version, xmlparser::{Element, XmlParser}, }; const EMPTY_CTYPE: &str = "/*EMPTY*/"; pub fn is_empty_c_type(c_type: &str) -> bool { c_type == EMPTY_CTYPE } impl Library { pub fn read_file>( &mut self, dirs: &[P], libs: &mut Vec, ) -> Result<(), String> { for dir in dirs { let dir: &Path = dir.as_ref(); let file_name = make_file_name(dir, &libs[libs.len() - 1]); let Ok(mut parser) = XmlParser::from_path(&file_name) else { continue; }; return parser.document(|p, _| { p.element_with_name("repository", |sub_parser, _elem| { self.read_repository(dirs, sub_parser, libs) }) }); } Err(format!("Couldn't find `{}`...", &libs[libs.len() - 1])) } fn read_repository>( &mut self, dirs: &[P], parser: &mut XmlParser<'_>, libs: &mut Vec, ) -> Result<(), String> { let mut packages = Vec::new(); let mut includes = Vec::new(); parser.elements(|parser, elem| match elem.name() { "include" => { match (elem.attr("name"), elem.attr("version")) { (Some(name), Some(ver)) => { if self.find_namespace(name).is_none() { let lib = format!("{name}-{ver}"); if libs.iter().any(|x| *x == lib) { return Err(format!( "`{}` includes itself (full path:`{}`)!", lib, libs.join("::") )); } libs.push(lib); self.read_file(dirs, libs)?; libs.pop(); } } (Some(name), None) => includes.push(name.to_owned()), _ => {} } Ok(()) } "package" => { let name = elem.attr_required("name")?; packages.push(name.to_owned()); Ok(()) } "namespace" => self.read_namespace( parser, elem, std::mem::take(&mut packages), std::mem::take(&mut includes), ), "attribute" => parser.ignore_element(), _ => Err(parser.unexpected_element(elem)), })?; Ok(()) } fn read_namespace( &mut self, parser: &mut XmlParser<'_>, elem: &Element, packages: Vec, c_includes: Vec, ) -> Result<(), String> { let ns_name = elem.attr_required("name")?; let ns_id = self.add_namespace(ns_name); { let ns = self.namespace_mut(ns_id); ns.package_names = packages; ns.c_includes = c_includes; if let Some(s) = elem.attr("shared-library") { ns.shared_library = s .split(',') .filter_map(|x| { if !x.is_empty() { Some(String::from(x)) } else { None } }) .collect(); } if let Some(s) = elem.attr("identifier-prefixes") { ns.identifier_prefixes = s.split(',').map(String::from).collect(); } if let Some(s) = elem.attr("symbol-prefixes") { ns.symbol_prefixes = s.split(',').map(String::from).collect(); } } trace!( "Reading {}-{}", ns_name, elem.attr("version").unwrap_or("?") ); parser.elements(|parser, elem| { trace!("<{} name={:?}>", elem.name(), elem.attr("name")); match elem.name() { "class" => self.read_class(parser, ns_id, elem), "record" => self.read_record_start(parser, ns_id, elem), "union" => self.read_named_union(parser, ns_id, elem), "interface" => self.read_interface(parser, ns_id, elem), "callback" => self.read_named_callback(parser, ns_id, elem), "bitfield" => self.read_bitfield(parser, ns_id, elem), "enumeration" => self.read_enumeration(parser, ns_id, elem), "function" => self.read_global_function(parser, ns_id, elem), "constant" => self.read_constant(parser, ns_id, elem), "alias" => self.read_alias(parser, ns_id, elem), "boxed" | "function-macro" | "docsection" | "function-inline" => { parser.ignore_element() } _ => { warn!("<{} name={:?}>", elem.name(), elem.attr("name")); parser.ignore_element() } } })?; Ok(()) } fn read_class( &mut self, parser: &mut XmlParser<'_>, ns_id: u16, elem: &Element, ) -> Result<(), String> { let class_name = elem.attr_required("name")?; let c_type = self.read_object_c_type(parser, elem)?; let symbol_prefix = elem.attr_required("symbol-prefix").map(ToOwned::to_owned)?; let type_struct = elem.attr("type-struct").map(ToOwned::to_owned); let get_type = elem.attr_required("get-type")?; let version = self.read_version(parser, ns_id, elem)?; let deprecated_version = self.read_deprecated_version(parser, ns_id, elem)?; let is_fundamental = elem.attr("fundamental").is_some_and(|x| x == "1"); let (ref_fn, unref_fn) = if is_fundamental { ( elem.attr("ref-func").map(ToOwned::to_owned), elem.attr("unref-func").map(ToOwned::to_owned), ) } else { (None, None) }; let is_abstract = elem.attr("abstract").is_some_and(|x| x == "1"); let final_type = elem.attr("final").is_some_and(|x| x == "1"); let mut fns = Vec::new(); let mut signals = Vec::new(); let mut properties = Vec::new(); let mut impls = Vec::new(); let mut fields = Vec::new(); let mut vfns = Vec::new(); let mut doc = None; let mut doc_deprecated = None; let mut union_count = 1; parser.elements(|parser, elem| match elem.name() { "constructor" | "function" | "method" => { self.read_function_to_vec(parser, ns_id, elem, &mut fns) } "implements" => self.read_type(parser, ns_id, elem).map(|r| { impls.push(r.0); }), "signal" => self .read_signal(parser, ns_id, elem) .map(|s| signals.push(s)), "property" => self .read_property(parser, ns_id, elem, &symbol_prefix) .map(|p| { if let Some(p) = p { properties.push(p); } }), "field" => self.read_field(parser, ns_id, elem).map(|f| { fields.push(f); }), "virtual-method" => self .read_virtual_method(parser, ns_id, elem) .map(|v| vfns.push(v)), "doc" => parser.text().map(|t| doc = Some(t)), "doc-deprecated" => parser.text().map(|t| doc_deprecated = Some(t)), "source-position" => parser.ignore_element(), "union" => self .read_union(parser, ns_id, elem, Some(class_name), Some(c_type)) .map(|mut u| { let field_name = if let Some(field_name) = elem.attr("name") { field_name.into() } else { format!("u{union_count}") }; u = Union { name: format!("{class_name}_{field_name}"), c_type: Some(format!("{c_type}_{field_name}")), ..u }; let u_doc = u.doc.clone(); let ctype = u.c_type.clone(); fields.push(Field { name: field_name, typ: Type::union(self, u, ns_id), doc: u_doc, c_type: ctype, ..Field::default() }); union_count += 1; }), "attribute" => parser.ignore_element(), _ => Err(parser.unexpected_element(elem)), })?; let parent = elem .attr("parent") .map(|s| self.find_or_stub_type(ns_id, s)); let typ = Type::Class(Class { name: class_name.into(), c_type: c_type.into(), type_struct, c_class_type: None, // this will be resolved during postprocessing glib_get_type: get_type.into(), fields, functions: fns, virtual_methods: vfns, signals, properties, parent, implements: impls, final_type, doc, doc_deprecated, version, deprecated_version, symbol_prefix, is_abstract, is_fundamental, ref_fn, unref_fn, }); self.add_type(ns_id, class_name, typ); Ok(()) } fn read_record_start( &mut self, parser: &mut XmlParser<'_>, ns_id: u16, elem: &Element, ) -> Result<(), String> { if let Some(typ) = self.read_record(parser, ns_id, elem, None, None)? { let name = typ.get_name(); self.add_type(ns_id, &name, typ); } Ok(()) } fn read_record( &mut self, parser: &mut XmlParser<'_>, ns_id: u16, elem: &Element, parent_name_prefix: Option<&str>, parent_ctype_prefix: Option<&str>, ) -> Result, String> { let record_name = elem.attr("name").unwrap_or_default(); // Records starting with `_` are intended to be private and should not be bound if record_name.starts_with('_') { parser.ignore_element()?; return Ok(None); } let is_class_record = record_name.ends_with("Class"); let c_type = elem.attr("type").unwrap_or_default(); let symbol_prefix = elem.attr("symbol-prefix").map(ToOwned::to_owned); let get_type = elem.attr("get-type").map(ToOwned::to_owned); let gtype_struct_for = elem.attr("is-gtype-struct-for"); let version = self.read_version(parser, ns_id, elem)?; let deprecated_version = self.read_deprecated_version(parser, ns_id, elem)?; let pointer = elem.attr_bool("pointer", false); let disguised = elem.attr_bool("disguised", false); let mut fields = Vec::new(); let mut fns = Vec::new(); let mut doc = None; let mut doc_deprecated = None; let mut union_count = 1; parser.elements(|parser, elem| match elem.name() { "constructor" | "function" | "method" => { self.read_function_to_vec(parser, ns_id, elem, &mut fns) } "union" => self .read_union(parser, ns_id, elem, Some(record_name), Some(c_type)) .map(|mut u| { let field_name = if let Some(field_name) = elem.attr("name") { field_name.into() } else { format!("u{union_count}") }; u = Union { name: format!( "{}{}_{}", parent_name_prefix.map_or_else(String::new, |s| { format!("{s}_") }), record_name, field_name ), c_type: Some(format!( "{}{}_{}", parent_ctype_prefix.map_or_else(String::new, |s| { format!("{s}_") }), c_type, field_name )), ..u }; let u_doc = u.doc.clone(); let ctype = u.c_type.clone(); fields.push(Field { name: field_name, typ: Type::union(self, u, ns_id), doc: u_doc, c_type: ctype, ..Field::default() }); union_count += 1; }), "field" => { self.read_field(parser, ns_id, elem).map(|mut f| { // Workaround for bitfields if c_type == "GDate" { if f.name == "julian_days" { fields.push(f); } else if f.name == "julian" { f.name = "flags_dmy".into(); f.typ = TypeId::tid_uint32(); f.c_type = Some("guint".into()); f.bits = None; fields.push(f); } else { // Skip } return; } // Workaround for wrong GValue c:type if c_type == "GValue" && f.name == "data" { f.c_type = Some("GValue_data".into()); } fields.push(f); }) } "doc" => parser.text().map(|t| doc = Some(t)), "doc-deprecated" => parser.text().map(|t| doc_deprecated = Some(t)), "source-position" => parser.ignore_element(), "attribute" => parser.ignore_element(), "method-inline" => parser.ignore_element(), _ => Err(parser.unexpected_element(elem)), })?; let typ = Type::Record(Record { name: record_name.into(), c_type: c_type.into(), glib_get_type: get_type, functions: if is_class_record && gtype_struct_for.is_some() { fns.into_iter() .map(|mut f| { f.kind = FunctionKind::ClassMethod; f }) .collect::>() } else { fns }, gtype_struct_for: gtype_struct_for.map(|s| s.into()), fields, version, deprecated_version, doc, doc_deprecated, disguised, pointer, symbol_prefix, }); Ok(Some(typ)) } fn read_named_union( &mut self, parser: &mut XmlParser<'_>, ns_id: u16, elem: &Element, ) -> Result<(), String> { // Require a name here elem.attr_required("name")?; self.read_union(parser, ns_id, elem, None, None) .and_then(|mut u| { assert_ne!(u.name, ""); // Workaround for missing c:type if u.name == "_Value__data__union" { u.c_type = Some("GValue_data".into()); } else if u.c_type.is_none() { return Err(parser.fail("Missing union c:type")); } let union_name = u.name.clone(); self.add_type(ns_id, &union_name, Type::Union(u)); Ok(()) }) } fn read_union( &mut self, parser: &mut XmlParser<'_>, ns_id: u16, elem: &Element, parent_name_prefix: Option<&str>, parent_ctype_prefix: Option<&str>, ) -> Result { let union_name = elem.attr("name").unwrap_or(""); let c_type = self.read_object_c_type(parser, elem).unwrap_or(""); let get_type = elem.attr("get-type").map(|s| s.into()); let symbol_prefix = elem.attr("symbol-prefix").map(ToOwned::to_owned); let mut fields = Vec::new(); let mut fns = Vec::new(); let mut doc = None; let mut struct_count = 1; parser.elements(|parser, elem| match elem.name() { "source-position" => parser.ignore_element(), "field" => self.read_field(parser, ns_id, elem).map(|f| { fields.push(f); }), "constructor" | "function" | "method" => { self.read_function_to_vec(parser, ns_id, elem, &mut fns) } "record" => { let mut r = match self.read_record( parser, ns_id, elem, parent_name_prefix, parent_ctype_prefix, )? { Some(Type::Record(r)) => r, _ => return Ok(()), }; let field_name = if let Some(field_name) = elem.attr("name") { field_name.into() } else { format!("s{struct_count}") }; r = Record { name: format!( "{}{}_{}", parent_name_prefix.map_or_else(String::new, |s| { format!("{s}_") }), union_name, field_name ), c_type: format!( "{}{}_{}", parent_ctype_prefix.map_or_else(String::new, |s| { format!("{s}_") }), c_type, field_name ), ..r }; let r_doc = r.doc.clone(); let ctype = r.c_type.clone(); fields.push(Field { name: field_name, typ: Type::record(self, r, ns_id), doc: r_doc, c_type: Some(ctype), ..Field::default() }); struct_count += 1; Ok(()) } "doc" => parser.text().map(|t| doc = Some(t)), "attribute" => parser.ignore_element(), _ => Err(parser.unexpected_element(elem)), })?; Ok(Union { name: union_name.into(), c_type: Some(c_type.into()), glib_get_type: get_type, fields, functions: fns, doc, symbol_prefix, }) } fn read_virtual_method( &mut self, parser: &mut XmlParser, ns_id: u16, elem: &Element, ) -> Result { let method_name = elem.attr_required("name")?; let version = self.read_version(parser, ns_id, elem)?; let deprecated_version = self.read_deprecated_version(parser, ns_id, elem)?; let c_identifier = elem.attr("identifier").or_else(|| elem.attr("name")); let mut params = Vec::new(); let mut ret = None; let mut doc = None; let mut doc_deprecated = None; parser.elements(|parser, elem| match elem.name() { "parameters" => self .read_parameters(parser, ns_id, true, true) .map(|mut ps| params.append(&mut ps)), "return-value" => { if ret.is_some() { return Err(parser.fail("Too many elements")); } self.read_parameter(parser, ns_id, elem, true, false) .map(|p| ret = Some(p)) } "source-position" => parser.ignore_element(), "doc" => parser.text().map(|t| doc = Some(t)), "doc-deprecated" => parser.text().map(|t| doc_deprecated = Some(t)), "attribute" => parser.ignore_element(), _ => Err(parser.unexpected_element(elem)), })?; let throws = elem.attr_bool("throws", false); if throws { params.push(Parameter { name: "error".into(), typ: self.find_or_stub_type(ns_id, "GLib.Error"), c_type: "GError**".into(), instance_parameter: false, direction: ParameterDirection::Out, transfer: Transfer::Full, caller_allocates: false, nullable: Nullable(true), array_length: None, is_error: true, doc: None, scope: ParameterScope::None, closure: None, destroy: None, }); } if let Some(ret) = ret { Ok(Function { name: method_name.into(), c_identifier: c_identifier.map(|s| s.into()), kind: FunctionKind::VirtualMethod, parameters: params, ret, throws, version, deprecated_version, doc, doc_deprecated, get_property: None, set_property: None, finish_func: None, async_func: None, sync_func: None, }) } else { Err(parser.fail("Missing element")) } } fn read_field( &mut self, parser: &mut XmlParser<'_>, ns_id: u16, elem: &Element, ) -> Result { let field_name = elem.attr_required("name")?; let private = elem.attr_bool("private", false); let bits = elem.attr("bits").and_then(|s| s.parse().ok()); let mut typ = None; let mut doc = None; parser.elements(|parser, elem| match elem.name() { "type" | "array" => { if typ.is_some() { return Err(parser.fail("Too many elements")); } self.read_type(parser, ns_id, elem).map(|t| { typ = Some(t); }) } "callback" => { if typ.is_some() { return Err(parser.fail("Too many elements")); } self.read_function(parser, ns_id, elem.name(), elem, true) .map(|f| { typ = Some((Type::function(self, f), None, None)); }) } "doc" => parser.text().map(|t| doc = Some(t)), "attribute" => parser.ignore_element(), _ => Err(parser.unexpected_element(elem)), })?; if let Some((tid, c_type, array_length)) = typ { Ok(Field { name: field_name.into(), typ: tid, c_type, private, bits, array_length, doc, }) } else { Err(parser.fail("Missing element")) } } fn read_named_callback( &mut self, parser: &mut XmlParser<'_>, ns_id: u16, elem: &Element, ) -> Result<(), String> { self.read_function_if_not_moved(parser, ns_id, elem.name(), elem, true)? .map(|func| { let name = func.name.clone(); self.add_type(ns_id, &name, Type::Function(func)) }); Ok(()) } fn read_interface( &mut self, parser: &mut XmlParser<'_>, ns_id: u16, elem: &Element, ) -> Result<(), String> { let interface_name = elem.attr_required("name")?; let c_type = self.read_object_c_type(parser, elem)?; let symbol_prefix = elem.attr_required("symbol-prefix").map(ToOwned::to_owned)?; let type_struct = elem.attr("type-struct").map(ToOwned::to_owned); let get_type = elem.attr_required("get-type")?; let version = self.read_version(parser, ns_id, elem)?; let deprecated_version = self.read_deprecated_version(parser, ns_id, elem)?; let mut fns = Vec::new(); let mut vfns = Vec::new(); let mut signals = Vec::new(); let mut properties = Vec::new(); let mut prereqs = Vec::new(); let mut doc = None; let mut doc_deprecated = None; parser.elements(|parser, elem| match elem.name() { "constructor" | "function" | "method" => { self.read_function_to_vec(parser, ns_id, elem, &mut fns) } "prerequisite" => self.read_type(parser, ns_id, elem).map(|r| { prereqs.push(r.0); }), "signal" => self .read_signal(parser, ns_id, elem) .map(|s| signals.push(s)), "property" => self .read_property(parser, ns_id, elem, &symbol_prefix) .map(|p| { if let Some(p) = p { properties.push(p); } }), "doc" => parser.text().map(|t| doc = Some(t)), "doc-deprecated" => parser.text().map(|t| doc_deprecated = Some(t)), "virtual-method" => self .read_virtual_method(parser, ns_id, elem) .map(|v| vfns.push(v)), "source-position" => parser.ignore_element(), "attribute" => parser.ignore_element(), _ => Err(parser.unexpected_element(elem)), })?; let typ = Type::Interface(Interface { name: interface_name.into(), c_type: c_type.into(), type_struct, c_class_type: None, // this will be resolved during postprocessing glib_get_type: get_type.into(), functions: fns, virtual_methods: vfns, signals, properties, prerequisites: prereqs, doc, doc_deprecated, version, deprecated_version, symbol_prefix, }); self.add_type(ns_id, interface_name, typ); Ok(()) } fn read_bitfield( &mut self, parser: &mut XmlParser<'_>, ns_id: u16, elem: &Element, ) -> Result<(), String> { let bitfield_name = elem.attr_required("name")?; let c_type = self.read_object_c_type(parser, elem)?; let symbol_prefix = elem.attr("symbol-prefix").map(ToOwned::to_owned); let get_type = elem.attr("get-type").map(|s| s.into()); let version = self.read_version(parser, ns_id, elem)?; let deprecated_version = self.read_deprecated_version(parser, ns_id, elem)?; let mut members = Vec::new(); let mut fns = Vec::new(); let mut doc = None; let mut doc_deprecated = None; parser.elements(|parser, elem| match elem.name() { "member" => self .read_member(parser, ns_id, elem) .map(|m| members.push(m)), "constructor" | "function" | "method" => { self.read_function_to_vec(parser, ns_id, elem, &mut fns) } "doc" => parser.text().map(|t| doc = Some(t)), "doc-deprecated" => parser.text().map(|t| doc_deprecated = Some(t)), "source-position" => parser.ignore_element(), "attribute" => parser.ignore_element(), _ => Err(parser.unexpected_element(elem)), })?; let typ = Type::Bitfield(Bitfield { name: bitfield_name.into(), c_type: c_type.into(), members, functions: fns, version, deprecated_version, doc, doc_deprecated, glib_get_type: get_type, symbol_prefix, }); self.add_type(ns_id, bitfield_name, typ); Ok(()) } fn read_enumeration( &mut self, parser: &mut XmlParser<'_>, ns_id: u16, elem: &Element, ) -> Result<(), String> { let enum_name = elem.attr_required("name")?; let c_type = self.read_object_c_type(parser, elem)?; let symbol_prefix = elem.attr("symbol-prefix").map(ToOwned::to_owned); let get_type = elem.attr("get-type").map(|s| s.into()); let version = self.read_version(parser, ns_id, elem)?; let deprecated_version = self.read_deprecated_version(parser, ns_id, elem)?; let error_domain = elem .attr("error-domain") .map(|s| ErrorDomain::Quark(String::from(s))); let mut members = Vec::new(); let mut fns = Vec::new(); let mut doc = None; let mut doc_deprecated = None; parser.elements(|parser, elem| match elem.name() { "member" => self .read_member(parser, ns_id, elem) .map(|m| members.push(m)), "constructor" | "function" | "method" => { self.read_function_to_vec(parser, ns_id, elem, &mut fns) } "doc" => parser.text().map(|t| doc = Some(t)), "doc-deprecated" => parser.text().map(|t| doc_deprecated = Some(t)), "source-position" => parser.ignore_element(), "attribute" => parser.ignore_element(), _ => Err(parser.unexpected_element(elem)), })?; let typ = Type::Enumeration(Enumeration { name: enum_name.into(), c_type: c_type.into(), members, functions: fns, version, deprecated_version, doc, doc_deprecated, error_domain, glib_get_type: get_type, symbol_prefix, }); self.add_type(ns_id, enum_name, typ); Ok(()) } fn read_global_function( &mut self, parser: &mut XmlParser<'_>, ns_id: u16, elem: &Element, ) -> Result<(), String> { self.read_function_if_not_moved(parser, ns_id, "global", elem, false) .map(|func| { if let Some(func) = func { self.add_function(ns_id, func); } }) } fn read_constant( &mut self, parser: &mut XmlParser<'_>, ns_id: u16, elem: &Element, ) -> Result<(), String> { let const_name = elem.attr_required("name")?; let c_identifier = elem.attr_required("type")?; let value = elem.attr_required("value")?; let version = self.read_version(parser, ns_id, elem)?; let deprecated_version = self.read_deprecated_version(parser, ns_id, elem)?; let mut inner = None; let mut doc = None; let mut doc_deprecated = None; parser.elements(|parser, elem| match elem.name() { "type" | "array" => { if inner.is_some() { return Err(parser.fail_with_position( "Too many inner elements in element", elem.position(), )); } let (typ, c_type, array_length) = self.read_type(parser, ns_id, elem)?; if let Some(c_type) = c_type { inner = Some((typ, c_type, array_length)); } else { return Err(parser.fail_with_position( "Missing element's c:type", elem.position(), )); } Ok(()) } "doc" => parser.text().map(|t| doc = Some(t)), "doc-deprecated" => parser.text().map(|t| doc_deprecated = Some(t)), "source-position" => parser.ignore_element(), "attribute" => parser.ignore_element(), _ => Err(parser.unexpected_element(elem)), })?; if let Some((typ, c_type, _array_length)) = inner { self.add_constant( ns_id, Constant { name: const_name.into(), c_identifier: c_identifier.into(), typ, c_type, value: value.into(), version, deprecated_version, doc, doc_deprecated, }, ); Ok(()) } else { Err(parser.fail_with_position( "Missing element inside element", elem.position(), )) } } fn read_alias( &mut self, parser: &mut XmlParser<'_>, ns_id: u16, elem: &Element, ) -> Result<(), String> { let alias_name = elem.attr_required("name")?; let c_identifier = elem.attr_required("type")?; let mut inner = None; let mut doc = None; let mut doc_deprecated = None; parser.elements(|parser, elem| match elem.name() { "source-position" => parser.ignore_element(), "type" | "array" => { if inner.is_some() { return Err(parser.fail_with_position( "Too many inner elements in element", elem.position(), )); } let (typ, c_type, array_length) = self.read_type(parser, ns_id, elem)?; if let Some(c_type) = c_type { inner = Some((typ, c_type, array_length)); } else { return Err(parser.fail("Missing target's c:type")); } Ok(()) } "doc" => parser.text().map(|t| doc = Some(t)), "doc-deprecated" => parser.text().map(|t| doc_deprecated = Some(t)), "attribute" => parser.ignore_element(), _ => Err(parser.unexpected_element(elem)), })?; if let Some((typ, c_type, _array_length)) = inner { let typ = Type::Alias(Alias { name: alias_name.into(), c_identifier: c_identifier.into(), typ, target_c_type: c_type, doc, doc_deprecated, }); self.add_type(ns_id, alias_name, typ); Ok(()) } else { Err(parser.fail_with_position( "Missing element inside element", elem.position(), )) } } fn read_member( &mut self, parser: &mut XmlParser<'_>, ns_id: u16, elem: &Element, ) -> Result { let member_name = elem.attr_required("name")?; let value = elem.attr_required("value")?; let c_identifier = elem.attr("identifier").map(|x| x.into()); let version = self.read_version(parser, ns_id, elem)?; let deprecated_version = self.read_deprecated_version(parser, ns_id, elem)?; let mut doc = None; let mut doc_deprecated = None; parser.elements(|parser, elem| match elem.name() { "doc" => parser.text().map(|t| doc = Some(t)), "doc-deprecated" => parser.text().map(|t| doc_deprecated = Some(t)), "attribute" => parser.ignore_element(), _ => Err(parser.unexpected_element(elem)), })?; Ok(Member { name: member_name.into(), value: value.into(), doc, doc_deprecated, c_identifier: c_identifier.unwrap_or_else(|| member_name.into()), status: crate::config::gobjects::GStatus::Generate, version, deprecated_version, }) } fn read_function( &mut self, parser: &mut XmlParser<'_>, ns_id: u16, kind_str: &str, elem: &Element, is_callback: bool, ) -> Result { let fn_name = elem.attr_required("name")?; let c_identifier = elem.attr("identifier").or_else(|| elem.attr("type")); let kind = FunctionKind::from_str(kind_str).map_err(|why| parser.fail(&why))?; let is_method = kind == FunctionKind::Method; let version = self.read_version(parser, ns_id, elem)?; let deprecated_version = self.read_deprecated_version(parser, ns_id, elem)?; let mut gtk_get_property = None; let mut gtk_set_property = None; let finish_func = c_identifier.and_then(|c_identifier| { elem.attr("finish-func").map(|finish_func_name| { format!( "{}{finish_func_name}", c_identifier.strip_suffix(&fn_name).unwrap() ) }) }); let async_func = elem.attr("async-func").map(ToString::to_string); let sync_func = elem.attr("sync-func").map(ToString::to_string); let mut params = Vec::new(); let mut ret = None; let mut doc = None; let mut doc_deprecated = None; parser.elements(|parser, elem| match elem.name() { "parameters" => self .read_parameters(parser, ns_id, false, is_method) .map(|mut ps| params.append(&mut ps)), "return-value" => { if ret.is_some() { return Err(parser.fail_with_position( "Too many elements inside element", elem.position(), )); } ret = Some(self.read_parameter(parser, ns_id, elem, false, is_method)?); Ok(()) } "doc" => parser.text().map(|t| doc = Some(t)), "doc-deprecated" => parser.text().map(|t| doc_deprecated = Some(t)), "doc-version" => parser.ignore_element(), "source-position" => parser.ignore_element(), "attribute" => { if let (Some(name), Some(value)) = (elem.attr("name"), elem.attr("value")) { match name { "org.gtk.Method.get_property" => { gtk_get_property = Some(value.to_string()); Ok(()) } "org.gtk.Method.set_property" => { gtk_set_property = Some(value.to_string()); Ok(()) } _ => parser.ignore_element(), } } else { parser.ignore_element() } } _ => Err(parser.unexpected_element(elem)), })?; let get_property = gtk_get_property.or(elem.attr("get-property").map(ToString::to_string)); let set_property = gtk_set_property.or(elem.attr("set-property").map(ToString::to_string)); // The last argument of a callback is ALWAYS user data, so it has to be marked as such // in case it's missing. if is_callback && params.last().map(|x| x.closure.is_none()).unwrap_or(false) { params.last_mut().unwrap().closure = Some(2000); } let throws = elem.attr_bool("throws", false); if throws { params.push(Parameter { name: "error".into(), typ: self.find_or_stub_type(ns_id, "GLib.Error"), c_type: "GError**".into(), instance_parameter: false, direction: ParameterDirection::Out, transfer: Transfer::Full, caller_allocates: false, nullable: Nullable(true), array_length: None, is_error: true, doc: None, scope: ParameterScope::None, closure: None, destroy: None, }); } if let Some(ret) = ret { Ok(Function { name: fn_name.into(), c_identifier: c_identifier.map(|s| s.into()), kind, parameters: params, ret, throws, version, deprecated_version, doc, doc_deprecated, get_property, set_property, finish_func, async_func, sync_func, }) } else { Err(parser.fail_with_position( "Missing element in element", elem.position(), )) } } fn read_function_to_vec( &mut self, parser: &mut XmlParser<'_>, ns_id: u16, elem: &Element, fns: &mut Vec, ) -> Result<(), String> { if let Some(f) = self.read_function_if_not_moved(parser, ns_id, elem.name(), elem, false)? { fns.push(f); } Ok(()) } fn read_function_if_not_moved( &mut self, parser: &mut XmlParser<'_>, ns_id: u16, kind_str: &str, elem: &Element, is_callback: bool, ) -> Result, String> { if elem.attr("moved-to").is_some() { return parser.ignore_element().map(|_| None); } self.read_function(parser, ns_id, kind_str, elem, is_callback) .and_then(|f| { if f.c_identifier.is_none() { return Err(parser.fail_with_position( &format!( "Missing c:identifier attribute in <{}> element", elem.name() ), elem.position(), )); } Ok(Some(f)) }) } fn read_signal( &mut self, parser: &mut XmlParser<'_>, ns_id: u16, elem: &Element, ) -> Result { let signal_name = elem.attr_required("name")?; let is_action = elem.attr_bool("action", false); let is_detailed = elem.attr_bool("detailed", false); let version = self.read_version(parser, ns_id, elem)?; let deprecated_version = self.read_deprecated_version(parser, ns_id, elem)?; let mut params = Vec::new(); let mut ret = None; let mut doc = None; let mut doc_deprecated = None; parser.elements(|parser, elem| match elem.name() { "parameters" => self .read_parameters(parser, ns_id, true, false) .map(|mut ps| params.append(&mut ps)), "return-value" => { if ret.is_some() { return Err(parser.fail_with_position( "Too many elements in element", elem.position(), )); } self.read_parameter(parser, ns_id, elem, true, false) .map(|p| ret = Some(p)) } "doc" => parser.text().map(|t| doc = Some(t)), "doc-deprecated" => parser.text().map(|t| doc_deprecated = Some(t)), "attribute" => parser.ignore_element(), _ => Err(parser.unexpected_element(elem)), })?; if let Some(ret) = ret { Ok(Signal { name: signal_name.into(), parameters: params, ret, is_action, is_detailed, version, deprecated_version, doc, doc_deprecated, }) } else { Err(parser.fail_with_position( "Missing element in element", elem.position(), )) } } fn read_parameters( &mut self, parser: &mut XmlParser<'_>, ns_id: u16, allow_no_ctype: bool, for_method: bool, ) -> Result, String> { parser.elements(|parser, elem| match elem.name() { "parameter" | "instance-parameter" => { self.read_parameter(parser, ns_id, elem, allow_no_ctype, for_method) } _ => Err(parser.unexpected_element(elem)), }) } fn read_parameter( &mut self, parser: &mut XmlParser<'_>, ns_id: u16, elem: &Element, allow_no_ctype: bool, for_method: bool, ) -> Result { let param_name = elem.attr("name").unwrap_or(""); let instance_parameter = elem.name() == "instance-parameter"; let transfer = elem .attr_from_str("transfer-ownership")? .unwrap_or(Transfer::None); let nullable = elem.attr_bool("nullable", false); let scope = elem.attr_from_str("scope")?.unwrap_or(ParameterScope::None); let closure = elem.attr_from_str("closure")?; let destroy = elem.attr_from_str("destroy")?; let caller_allocates = elem.attr_bool("caller-allocates", false); let direction = if elem.name() == "return-value" { Ok(ParameterDirection::Return) } else { ParameterDirection::from_str(elem.attr("direction").unwrap_or("in")) .map_err(|why| parser.fail_with_position(&why, elem.position())) }?; let mut typ = None; let mut varargs = false; let mut doc = None; parser.elements(|parser, elem| match elem.name() { "type" | "array" => { if typ.is_some() { return Err(parser.fail_with_position( &format!("Too many elements in <{}> element", elem.name()), elem.position(), )); } typ = Some(self.read_type(parser, ns_id, elem)?); if let Some((tid, None, _)) = typ { if allow_no_ctype { typ = Some((tid, Some(EMPTY_CTYPE.to_owned()), None)); } else { return Err(parser.fail_with_position( &format!("Missing c:type attribute in <{}> element", elem.name()), elem.position(), )); } } Ok(()) } "varargs" => { varargs = true; parser.ignore_element() } "doc" => parser.text().map(|t| doc = Some(t)), "attribute" => parser.ignore_element(), _ => Err(parser.unexpected_element(elem)), })?; if let Some((tid, c_type, mut array_length)) = typ { if for_method { array_length = array_length.map(|l| l + 1); } Ok(Parameter { name: param_name.into(), typ: tid, c_type: c_type.unwrap(), instance_parameter, direction, transfer, caller_allocates, nullable: Nullable(nullable), array_length, is_error: false, doc, scope, closure, destroy, }) } else if varargs { Ok(Parameter { name: String::new(), typ: self.find_type(INTERNAL_NAMESPACE, "varargs").unwrap(), c_type: String::new(), instance_parameter, direction: Default::default(), transfer: Transfer::None, caller_allocates: false, nullable: Nullable(false), array_length: None, is_error: false, doc, scope, closure, destroy, }) } else { Err(parser.fail_with_position( &format!("Missing element in <{}> element", elem.name()), elem.position(), )) } } fn read_property( &mut self, parser: &mut XmlParser<'_>, ns_id: u16, elem: &Element, symbol_prefix: &str, ) -> Result, String> { let prop_name = elem.attr_required("name")?; let readable = elem.attr_bool("readable", true); let writable = elem.attr_bool("writable", false); let construct = elem.attr_bool("construct", false); let construct_only = elem.attr_bool("construct-only", false); let transfer = Transfer::from_str(elem.attr("transfer-ownership").unwrap_or("none")) .map_err(|why| parser.fail_with_position(&why, elem.position()))?; let version = self.read_version(parser, ns_id, elem)?; let deprecated_version = self.read_deprecated_version(parser, ns_id, elem)?; let mut has_empty_type_tag = false; let mut typ = None; let mut doc = None; let mut doc_deprecated = None; let mut gtk_getter = None; let mut gtk_setter = None; parser.elements(|parser, elem| match elem.name() { "type" | "array" => { if typ.is_some() { return Err(parser.fail_with_position( "Too many elements in element", elem.position(), )); } if !elem.has_attrs() && elem.name() == "type" { // defend from has_empty_type_tag = true; return parser.ignore_element(); } typ = Some(self.read_type(parser, ns_id, elem)?); if let Some((tid, None, _)) = typ { typ = Some((tid, Some(EMPTY_CTYPE.to_owned()), None)); } Ok(()) } "doc" => parser.text().map(|t| doc = Some(t)), "doc-deprecated" => parser.text().map(|t| doc_deprecated = Some(t)), "attribute" => { if let (Some(name), Some(value)) = (elem.attr("name"), elem.attr("value")) { match name { "org.gtk.Property.get" => { gtk_getter = value .split(symbol_prefix) .last() .and_then(|p| p.strip_prefix('_')) .map(|p| p.to_string()); Ok(()) } "org.gtk.Property.set" => { gtk_setter = value .split(symbol_prefix) .last() .and_then(|p| p.strip_prefix('_')) .map(|p| p.to_string()); Ok(()) } _ => parser.ignore_element(), } } else { parser.ignore_element() } } _ => Err(parser.unexpected_element(elem)), })?; let getter = gtk_getter.or(elem.attr("getter").map(ToString::to_string)); let setter = gtk_setter.or(elem.attr("setter").map(ToString::to_string)); if has_empty_type_tag { return Ok(None); } if let Some((tid, c_type, _array_length)) = typ { Ok(Some(Property { name: prop_name.into(), readable, writable, construct, construct_only, transfer, typ: tid, c_type, version, deprecated_version, doc, doc_deprecated, getter, setter, })) } else { Err(parser.fail_with_position( "Missing element in element", elem.position(), )) } } fn read_type( &mut self, parser: &mut XmlParser<'_>, ns_id: u16, elem: &Element, ) -> Result<(TypeId, Option, Option), String> { let type_name = elem .attr("name") .or_else(|| { if elem.name() == "array" { Some("array") } else { None } }) .ok_or_else(|| { parser.fail_with_position( " element is missing a name attribute", elem.position(), ) })?; let c_type = elem.attr("type").map(|s| s.into()); let array_length = elem.attr("length").and_then(|s| s.parse().ok()); let inner = parser.elements(|parser, elem| match elem.name() { "type" | "array" => self.read_type(parser, ns_id, elem), _ => Err(parser.unexpected_element(elem)), })?; if inner.is_empty() || type_name == "GLib.ByteArray" { if type_name == "array" { Err(parser.fail_with_position( " element is missing an inner element type", elem.position(), )) } else if type_name == "gboolean" && c_type.as_deref() == Some("_Bool") { Ok((self.find_or_stub_type(ns_id, "bool"), c_type, array_length)) } else { Ok(( self.find_or_stub_type(ns_id, type_name), c_type, array_length, )) } } else { let tid = if type_name == "array" { let inner_type = &inner[0]; Type::c_array( self, inner_type.0, elem.attr("fixed-size").and_then(|n| n.parse().ok()), inner_type.1.clone(), ) } else { let inner = inner.iter().map(|r| r.0).collect(); Type::container(self, type_name, inner).ok_or_else(|| { parser.fail_with_position("Unknown container type", elem.position()) })? }; Ok((tid, c_type, array_length)) } } fn read_version( &mut self, parser: &XmlParser<'_>, ns_id: u16, elem: &Element, ) -> Result, String> { self.read_version_attribute(parser, ns_id, elem, "version") } fn read_deprecated_version( &mut self, parser: &XmlParser<'_>, ns_id: u16, elem: &Element, ) -> Result, String> { self.read_version_attribute(parser, ns_id, elem, "deprecated-version") } fn read_version_attribute( &mut self, parser: &XmlParser<'_>, ns_id: u16, elem: &Element, attr: &str, ) -> Result, String> { if let Some(v) = elem.attr(attr) { match v.parse() { Ok(v) => { self.register_version(ns_id, v); Ok(Some(v)) } Err(e) => Err(parser.fail(&format!("Invalid `{attr}` attribute: {e}"))), } } else { Ok(None) } } fn read_object_c_type<'a>( &mut self, parser: &XmlParser<'_>, elem: &'a Element, ) -> Result<&'a str, String> { elem.attr("type") .or_else(|| elem.attr("type-name")) .ok_or_else(|| { parser.fail(&format!( "Missing `c:type`/`glib:type-name` attributes on element <{}>", elem.name() )) }) } } fn make_file_name(dir: &Path, name: &str) -> PathBuf { let mut path = dir.to_path_buf(); let name = format!("{name}.gir"); path.push(name); path } gir-0.20.5/src/traits.rs000066400000000000000000000012761475434152100150360ustar00rootroot00000000000000pub use crate::config::{matchable::Matchable, parameter_matchable::ParameterMatchable}; pub trait IntoString { fn into_string(self) -> String; } pub trait MapAny { fn map_any T>(self, op: F) -> Self; } impl MapAny for Result { fn map_any T>(self, op: F) -> Self { match self { Ok(x) => Ok(op(x)), Err(x) => Err(op(x)), } } } pub trait MaybeRef { fn maybe_ref(&self) -> Option<&T>; fn to_ref(&self) -> &T; } pub trait MaybeRefAs { fn maybe_ref_as(&self) -> Option<&T> where Self: MaybeRef; fn to_ref_as(&self) -> &T where Self: MaybeRef; } gir-0.20.5/src/update_version.rs000066400000000000000000000066571475434152100165670ustar00rootroot00000000000000use crate::{ config::Config, library::{self, Function, Parameter, Type, MAIN_NAMESPACE}, version::Version, Library, }; pub fn apply_config(library: &mut Library, cfg: &Config) { fix_versions_by_config(library, cfg); } pub fn check_function_real_version(library: &mut Library) { // In order to avoid the borrow checker to annoy us... let library2 = library as *const Library; for typ in &mut library.namespace_mut(MAIN_NAMESPACE).types { match typ { Some(Type::Class(c)) => update_function_version(&mut c.functions, library2), Some(Type::Interface(i)) => update_function_version(&mut i.functions, library2), Some(Type::Union(u)) => update_function_version(&mut u.functions, library2), Some(Type::Record(r)) => update_function_version(&mut r.functions, library2), Some(Type::Bitfield(b)) => update_function_version(&mut b.functions, library2), Some(Type::Enumeration(e)) => update_function_version(&mut e.functions, library2), _ => {} } } update_function_version( &mut library.namespace_mut(MAIN_NAMESPACE).functions, library2, ); } fn check_versions(param: &Parameter, current_version: &mut Option, lib: *const Library) { if param.typ.ns_id != MAIN_NAMESPACE { return; } let ty_version = match unsafe { (*lib).type_(param.typ) } { library::Type::Class(c) => c.version, library::Type::Enumeration(c) => c.version, library::Type::Bitfield(c) => c.version, library::Type::Record(c) => c.version, library::Type::Interface(c) => c.version, _ => None, }; let new_version = match (*current_version, ty_version) { (Some(current_version), Some(ty_version)) => { if current_version < ty_version { Some(ty_version) } else { None } } (None, Some(ty_version)) => Some(ty_version), _ => None, }; if let Some(new_version) = new_version { *current_version = Some(new_version); } } fn update_function_version(functions: &mut Vec, lib: *const Library) { for function in functions { let mut current_version = None; for parameter in &function.parameters { check_versions(parameter, &mut current_version, lib); } check_versions(&function.ret, &mut current_version, lib); if match (current_version, function.version) { (Some(cur_ver), Some(lib_ver)) => cur_ver > lib_ver, (Some(_), None) => true, _ => false, } { function.version = current_version; } } } fn fix_versions_by_config(library: &mut Library, cfg: &Config) { use crate::library::Type::*; for obj in cfg.objects.values() { if obj.status.ignored() { continue; } if obj.version.is_none() { continue; } let version = obj.version; let Some(tid) = library.find_type(0, &obj.name) else { continue; }; match library.type_mut(tid) { Class(class) => class.version = version, Interface(interface) => interface.version = version, Record(record) => record.version = version, Bitfield(flags) => flags.version = version, Enumeration(enum_) => enum_.version = version, _ => (), } } } gir-0.20.5/src/version.rs000066400000000000000000000076511475434152100152200ustar00rootroot00000000000000use std::{ fmt::{self, Display, Formatter}, str::FromStr, }; /// Major, minor and patch version #[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct Version(u16, u16, u16, bool); impl Version { pub fn new(major: u16, minor: u16, patch: u16) -> Self { Self(major, minor, patch, true) } /// Convert a version number to a config guard /// /// When generating a builder pattern, properties could be from a super-type /// class/interface and so the version used there must be prefixed with /// the crate name from where the super-type originates from in case it /// is different from the main crate. For those cases you can pass /// the crate name as the `prefix` parameter pub fn to_cfg(self, prefix: Option<&str>) -> String { if self.3 { if let Some(p) = prefix { format!("feature = \"{}_{}\"", p, self.to_feature()) } else { format!("feature = \"{}\"", self.to_feature()) } } else if let Some(p) = prefix { format!("not(feature = \"{}_{}\")", p, self.to_feature()) } else { format!("not(feature = \"{}\")", self.to_feature()) } } pub fn as_opposite(&mut self) { self.3 = !self.3; } pub fn to_feature(self) -> String { match self { Self(major, 0, 0, _) => format!("v{major}"), Self(major, minor, 0, _) => format!("v{major}_{minor}"), Self(major, minor, patch, _) => format!("v{major}_{minor}_{patch}"), } } /// Returns `inner_version` if it is stricter than `outer_version`, `None` /// otherwise pub fn if_stricter_than( inner_version: Option, outer_version: Option, ) -> Option { match (inner_version, outer_version) { (Some(inner_version), Some(outer_version)) if inner_version <= outer_version => None, (inner_version, _) => inner_version, } } } impl FromStr for Version { type Err = String; /// Parse a `Version` from a string. /// Currently always return Ok fn from_str(s: &str) -> Result { if s.contains('.') { let mut parts = s .splitn(4, '.') .map(str::parse) .take_while(Result::is_ok) .map(Result::unwrap); Ok(Self::new( parts.next().unwrap_or(0), parts.next().unwrap_or(0), parts.next().unwrap_or(0), )) } else { let val = s.parse::(); Ok(Self::new(val.unwrap_or(0), 0, 0)) } } } impl Display for Version { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { match *self { Self(major, 0, 0, _) => write!(f, "{major}"), Self(major, minor, 0, _) => write!(f, "{major}.{minor}"), Self(major, minor, patch, _) => write!(f, "{major}.{minor}.{patch}"), } } } #[cfg(test)] mod tests { use std::str::FromStr; use super::Version; #[test] fn from_str_works() { assert_eq!(FromStr::from_str("1"), Ok(Version::new(1, 0, 0))); assert_eq!(FromStr::from_str("2.1"), Ok(Version::new(2, 1, 0))); assert_eq!(FromStr::from_str("3.2.1"), Ok(Version::new(3, 2, 1))); assert_eq!(FromStr::from_str("3.ff.1"), Ok(Version::new(3, 0, 0))); } #[test] fn parse_works() { assert_eq!("1".parse(), Ok(Version::new(1, 0, 0))); } #[test] fn ord() { assert!(Version::new(0, 0, 0) < Version::new(1, 2, 3)); assert!(Version::new(1, 0, 0) < Version::new(1, 2, 3)); assert!(Version::new(1, 2, 0) < Version::new(1, 2, 3)); assert!(Version::new(1, 2, 3) == Version::new(1, 2, 3)); assert!(Version::new(1, 0, 0) < Version::new(2, 0, 0)); assert!(Version::new(3, 0, 0) == Version::new(3, 0, 0)); } } gir-0.20.5/src/visitors.rs000066400000000000000000000026551475434152100154140ustar00rootroot00000000000000use crate::library::*; pub trait FunctionsMutVisitor { // TODO: remove interrupt functionality if it is not used // visiting stops if returned false fn visit_function_mut(&mut self, func: &mut Function) -> bool; } impl Namespace { pub fn visit_functions_mut(&mut self, visitor: &mut V) -> bool { for type_ in self.types.iter_mut().flatten() { if !type_.visit_functions_mut(visitor) { return false; } } true } } impl Type { pub fn visit_functions_mut(&mut self, visitor: &mut V) -> bool { match self { Type::Class(class) => { for function in &mut class.functions { if !visitor.visit_function_mut(function) { return false; } } } Type::Interface(interface) => { for function in &mut interface.functions { if !visitor.visit_function_mut(function) { return false; } } } Type::Record(record) => { for function in &mut record.functions { if !visitor.visit_function_mut(function) { return false; } } } _ => (), } true } } gir-0.20.5/src/writer/000077500000000000000000000000001475434152100144705ustar00rootroot00000000000000gir-0.20.5/src/writer/defines.rs000066400000000000000000000003361475434152100164550ustar00rootroot00000000000000pub const TAB: &str = " "; pub const TAB_SIZE: usize = 4; pub const MAX_TEXT_WIDTH: usize = 100; #[cfg(test)] mod tests { use super::*; #[test] fn tabs() { assert_eq!(TAB_SIZE, TAB.len()); } } gir-0.20.5/src/writer/mod.rs000066400000000000000000000002071475434152100156140ustar00rootroot00000000000000mod defines; pub mod primitives; pub mod to_code; // TODO:remove pub pub mod untabber; pub use self::{defines::TAB, to_code::ToCode}; gir-0.20.5/src/writer/primitives.rs000066400000000000000000000070361475434152100172370ustar00rootroot00000000000000use super::defines::*; // TODO: convert to macro with usage // format!(indent!(5, "format:{}"), 6) pub fn tabs(num: usize) -> String { format!("{:1$}", "", TAB_SIZE * num) } pub fn format_block(prefix: &str, suffix: &str, body: &[String]) -> Vec { let mut v = Vec::new(); if !prefix.is_empty() { v.push(prefix.into()); } for s in body.iter() { let s = format!("{TAB}{s}"); v.push(s); } if !suffix.is_empty() { v.push(suffix.into()); } v } pub fn format_block_one_line( prefix: &str, suffix: &str, body: &[String], outer_separator: &str, inner_separator: &str, ) -> String { let mut s = format!("{prefix}{outer_separator}"); let mut first = true; for s_ in body { if first { first = false; s = s + s_; } else { s = s + inner_separator + s_; } } s + outer_separator + suffix } pub fn format_block_smart( prefix: &str, suffix: &str, body: &[String], outer_separator: &str, inner_separator: &str, ) -> Vec { format_block_smart_width( prefix, suffix, body, outer_separator, inner_separator, MAX_TEXT_WIDTH, ) } pub fn format_block_smart_width( prefix: &str, suffix: &str, body: &[String], outer_separator: &str, inner_separator: &str, max_width: usize, ) -> Vec { let outer_len = prefix.len() + suffix.len() + 2 * outer_separator.len(); let mut inner_len = inner_separator.len() * (body.len() - 1); // TODO: change to sum() for s in body { inner_len += s.len(); } if (outer_len + inner_len) > max_width { format_block(prefix, suffix, body) } else { let s = format_block_one_line(prefix, suffix, body, outer_separator, inner_separator); vec![s] } } pub fn comment_block(body: &[String]) -> Vec { body.iter().map(|s| format!("//{s}")).collect() } #[cfg(test)] mod tests { use super::*; #[test] fn test_tabs() { assert_eq!(tabs(0), ""); assert_eq!(tabs(1), TAB); assert_eq!(tabs(2), format!("{TAB}{TAB}")); } #[test] fn test_format_block() { let body = vec!["0 => 1,".into(), "1 => 0,".into()]; let actual = format_block("match a {", "}", &body); let expected = ["match a {", " 0 => 1,", " 1 => 0,", "}"]; assert_eq!(actual, expected); } #[test] fn test_format_block_smart_width_one_line_outer_separator() { let body = vec!["f()".into()]; let actual = format_block_smart_width("unsafe {", "}", &body, " ", "", 14); let expected = ["unsafe { f() }"]; assert_eq!(actual, expected); } #[test] fn test_format_block_smart_width_many_lines_outer_separator() { let body = vec!["f()".into()]; let actual = format_block_smart_width("unsafe {", "}", &body, " ", "", 13); let expected = ["unsafe {", " f()", "}"]; assert_eq!(actual, expected); } #[test] fn test_format_block_smart_one_line_inner_separator() { let body = vec!["a: &str".into(), "b: &str".into()]; let actual = format_block_smart("f(", ")", &body, "", ", "); let expected = ["f(a: &str, b: &str)"]; assert_eq!(actual, expected); } #[test] fn test_comment_block() { let body = vec!["f(a,".into(), " b)".into()]; let actual = comment_block(&body); let expected = ["//f(a,", "// b)"]; assert_eq!(actual, expected); } } gir-0.20.5/src/writer/to_code.rs000066400000000000000000000211521475434152100164530ustar00rootroot00000000000000use std::fmt::Write; use super::primitives::*; use crate::{ chunk::{Chunk, Param, TupleMode}, codegen::{translate_from_glib::TranslateFromGlib, translate_to_glib::TranslateToGlib}, env::Env, nameutil::use_glib_type, }; pub trait ToCode { fn to_code(&self, env: &Env) -> Vec; } impl ToCode for Chunk { fn to_code(&self, env: &Env) -> Vec { use crate::chunk::Chunk::*; match *self { Comment(ref chs) => comment_block(&chs.to_code(env)), Chunks(ref chs) => chs.to_code(env), BlockHalf(ref chs) => format_block("", "}", &chs.to_code(env)), UnsafeSmart(ref chs) => { format_block_smart("unsafe {", "}", &chs.to_code(env), " ", " ") } Unsafe(ref chs) => format_block("unsafe {", "}", &chs.to_code(env)), FfiCallTODO(ref name) => vec![format!("TODO: call {name}()")], FfiCall { ref name, ref params, } => { let prefix = format!("{name}("); // TODO: change to format_block or format_block_smart let s = format_block_one_line(&prefix, ")", ¶ms.to_code(env), "", ", "); vec![s] } FfiCallParameter { ref transformation_type, } => { let s = transformation_type.translate_to_glib(); vec![s] } FfiCallOutParameter { ref par } => { let s = if par.caller_allocates { format!("{}.to_glib_none_mut().0", par.name) } else if par.is_uninitialized && !par.is_error { format!("{}.as_mut_ptr()", par.name) } else { format!("&mut {}", par.name) }; vec![s] } FfiCallConversion { ref ret, ref array_length_name, ref call, } => { let call_strings = call.to_code(env); let (prefix, suffix) = ret.translate_from_glib_as_function(env, array_length_name.as_deref()); let s = format_block_one_line(&prefix, &suffix, &call_strings, "", ""); vec![s] } Let { ref name, is_mut, ref value, ref type_, } => { let modif = if is_mut { "mut " } else { "" }; let type_string = if let Some(type_) = type_ { let type_strings = type_.to_code(env); format_block_one_line(": ", "", &type_strings, "", "") } else { String::new() }; let value_strings = value.to_code(env); let prefix = format!("let {modif}{name}{type_string} = "); let s = format_block_one_line(&prefix, ";", &value_strings, "", ""); vec![s] } Uninitialized => vec!["std::mem::MaybeUninit::uninit()".into()], UninitializedNamed { ref name } => { let s = format!("{name}::uninitialized()"); vec![s] } NullPtr => vec!["std::ptr::null()".into()], NullMutPtr => vec!["std::ptr::null_mut()".into()], Custom(ref string) => vec![string.clone()], Tuple(ref chs, ref mode) => { #[allow(deprecated)] let with_bracket = match mode { TupleMode::Auto => chs.len() > 1, TupleMode::WithUnit => chs.len() != 1, TupleMode::Simple => true, }; let (prefix, suffix) = if with_bracket { ("(", ")") } else { ("", "") }; let s = format_block_one_line(prefix, suffix, &chs.to_code(env), "", ", "); vec![s] } FromGlibConversion { ref mode, ref array_length_name, ref value, } => { let value_strings = value.to_code(env); let (prefix, suffix) = mode.translate_from_glib_as_function(env, array_length_name.as_deref()); let s = format_block_one_line(&prefix, &suffix, &value_strings, "", ""); vec![s] } OptionalReturn { ref condition, ref value, } => { let value_strings = value.to_code(env); let prefix = format!("if {condition} {{ Some("); let suffix = ") } else { None }"; let s = format_block_one_line(&prefix, suffix, &value_strings, "", ""); vec![s] } AssertErrorSanity => { let assert = format!( "debug_assert_eq!(is_ok == {}, !error.is_null());", use_glib_type(env, "ffi::GFALSE") ); vec![assert] } ErrorResultReturn { ref ret, ref value } => { let mut lines = match ret { Some(r) => r.to_code(env), None => vec![], }; let value_strings = value.to_code(env); let prefix = "if error.is_null() { Ok("; let suffix = ") } else { Err(from_glib_full(error)) }"; let s = format_block_one_line(prefix, suffix, &value_strings, "", ""); lines.push(s); lines } AssertInit(x) => vec![x.to_string()], Connect { ref signal, ref trampoline, in_trait, is_detailed, } => { let mut v: Vec = Vec::with_capacity(6); if is_detailed { v.push(format!( r#"let detailed_signal_name = detail.map(|name| {{ format!("{signal}::{{name}}\0") }});"# )); v.push(format!( r#"let signal_name: &[u8] = detailed_signal_name.as_ref().map_or(&b"{signal}\0"[..], |n| n.as_bytes());"# )); v.push( "connect_raw(self.as_ptr() as *mut _, signal_name.as_ptr() as *const _," .to_string(), ); } else { v.push(format!( "connect_raw(self.as_ptr() as *mut _, b\"{signal}\\0\".as_ptr() as *const _," )); } let self_str = if in_trait { "Self, " } else { "" }; v.push(format!( "\tSome(std::mem::transmute::<*const (), unsafe extern \"C\" fn()>({trampoline}::<{self_str}F> as *const ())), Box_::into_raw(f))" )); v } Name(ref name) => vec![name.clone()], ExternCFunc { ref name, ref parameters, ref body, ref return_value, ref bounds, } => { let prefix = format!(r#"unsafe extern "C" fn {name}{bounds}("#); let suffix = ")".to_string(); let params: Vec<_> = parameters .iter() .flat_map(|param| param.to_code(env)) .collect(); let mut s = format_block_one_line(&prefix, &suffix, ¶ms, "", ", "); if let Some(return_value) = return_value { write!(s, " -> {return_value}").unwrap(); } s.push_str(" {"); let mut code = format_block("", "}", &body.to_code(env)); code.insert(0, s); code } Cast { ref name, ref type_, } => vec![format!("{name} as {type_}")], Call { ref func_name, ref arguments, } => { let args: Vec<_> = arguments.iter().flat_map(|arg| arg.to_code(env)).collect(); let s = format_block_one_line("(", ")", &args, "", ","); vec![format!("{func_name}{s};")] } } } } impl ToCode for Param { fn to_code(&self, _env: &Env) -> Vec { vec![format!("{}: {}", self.name, self.typ)] } } impl ToCode for [Chunk] { fn to_code(&self, env: &Env) -> Vec { let mut v = Vec::new(); for ch in self { let strs = ch.to_code(env); v.extend_from_slice(&strs); } v } } gir-0.20.5/src/writer/untabber.rs000066400000000000000000000012641475434152100166430ustar00rootroot00000000000000use std::io::{Result, Write}; use super::TAB; pub struct Untabber { orig: Box, } impl Untabber { pub fn new(orig: Box) -> Self { Self { orig } } } impl Write for Untabber { fn write(&mut self, buf: &[u8]) -> Result { let mut chunks = buf.split(|b| b == &b'\t').peekable(); while let Some(chunk) = chunks.next() { self.orig.write_all(chunk)?; if chunks.peek().is_some() { self.orig.write_all(TAB.as_bytes())?; } else { break; } } Ok(buf.len()) } fn flush(&mut self) -> Result<()> { self.orig.flush() } } gir-0.20.5/src/xmlparser.rs000066400000000000000000000335671475434152100155550ustar00rootroot00000000000000use std::{ fmt, fs::File, io::{BufReader, Read}, path::{Path, PathBuf}, rc::Rc, str, }; use xml::{ self, attribute::OwnedAttribute, common::{Position, TextPosition}, name::OwnedName, reader::{EventReader, XmlEvent}, }; /// NOTE: After parser returns an error its further behaviour is unspecified. pub struct XmlParser<'a> { /// Inner XML parser doing actual work. parser: EventReader>, /// Next event to be returned. /// /// Takes priority over events returned from inner parser. /// Used to support peaking one element ahead. peek_event: Option>, /// Position on peek event if any. peek_position: TextPosition, /// Used to emits errors. Rc so that it can be cheaply shared with Element /// type. error_emitter: Rc, } struct ErrorEmitter { /// Path to currently parsed document. path: Option, } impl ErrorEmitter { pub fn emit(&self, message: &str, position: TextPosition) -> String { let enriched = match self.path { Some(ref path) => format!("{} at line {}: {}", path.display(), position, message), None => format!("{position} {message}"), }; format!("GirXml: {enriched}") } pub fn emit_error(&self, error: &xml::reader::Error) -> String { // Error returned by EventReader already includes the position. // That is why we have a separate implementation that only // prepends the file path. let enriched = match self.path { Some(ref path) => format!("{}:{}", path.display(), error), None => format!("{error}"), }; format!("GirXml: {enriched}") } } /// A wrapper for `XmlEvent::StartDocument` which doesn't have its own type. pub struct Document; /// A wrapper for `XmlEvent::StartElement` which doesn't have its own type. pub struct Element { name: OwnedName, attributes: Vec, position: TextPosition, error_emitter: Rc, } impl Element { /// Returns the element local name. pub fn name(&self) -> &str { &self.name.local_name } /// Value of attribute with given name or None if it is not found. pub fn attr(&self, name: &str) -> Option<&str> { for attr in &self.attributes { if attr.name.local_name == name { return Some(&attr.value); } } None } /// Checks if elements has any attributes. pub fn has_attrs(&self) -> bool { !self.attributes.is_empty() } pub fn attr_bool(&self, name: &str, default: bool) -> bool { for attr in &self.attributes { if attr.name.local_name == name { return attr.value == "1"; } } default } pub fn attr_from_str(&self, name: &str) -> Result, String> where T: str::FromStr, T::Err: fmt::Display, { if let Some(value_str) = self.attr(name) { match T::from_str(value_str) { Ok(value) => Ok(Some(value)), Err(error) => { let message = format!( "Attribute `{}` on element <{}> has invalid value: {}", name, self.name(), error ); Err(self.error_emitter.emit(&message, self.position)) } } } else { Ok(None) } } /// Returns element position. pub fn position(&self) -> TextPosition { self.position } /// Value of attribute with given name or an error when absent. pub fn attr_required(&self, name: &str) -> Result<&str, String> { for attr in &self.attributes { if attr.name.local_name == name { return Ok(&attr.value); } } let message = format!( "Attribute `{}` on element <{}> is required.", name, self.name() ); Err(self.error_emitter.emit(&message, self.position)) } } impl XmlParser<'_> { pub fn from_path(path: &Path) -> Result, String> { match File::open(path) { Err(e) => Err(format!("Can't open file \"{}\": {}", path.display(), e)), Ok(file) => Ok(XmlParser { parser: EventReader::new(Box::new(BufReader::new(file))), peek_event: None, peek_position: TextPosition::new(), error_emitter: Rc::new(ErrorEmitter { path: Some(path.to_owned()), }), }), } } #[cfg(test)] pub fn new<'r, R: 'r + Read>(read: R) -> XmlParser<'r> { XmlParser { parser: EventReader::new(Box::new(read)), peek_event: None, peek_position: TextPosition::new(), error_emitter: Rc::new(ErrorEmitter { path: None }), } } /// Returns an error that combines current position and given error message. pub fn fail(&self, message: &str) -> String { self.error_emitter.emit(message, self.position()) } /// Returns an error that combines given error message and position. pub fn fail_with_position(&self, message: &str, position: TextPosition) -> String { self.error_emitter.emit(message, position) } pub fn unexpected_element(&self, elem: &Element) -> String { let message = format!("Unexpected element <{}>", elem.name()); self.error_emitter.emit(&message, elem.position()) } fn unexpected_event(&self, event: &XmlEvent) -> String { let message = format!("Unexpected event {event:?}"); self.error_emitter.emit(&message, self.position()) } pub fn position(&self) -> TextPosition { match self.peek_event { None => self.parser.position(), Some(_) => self.peek_position, } } /// Returns next XML event without consuming it. fn peek_event(&mut self) -> &Result { if self.peek_event.is_none() { self.peek_event = Some(self.next_event_impl()); self.peek_position = self.parser.position(); } self.peek_event.as_ref().unwrap() } /// Consumes and returns next XML event. fn next_event(&mut self) -> Result { match self.peek_event.take() { None => self.next_event_impl(), Some(e) => e, } } /// Returns next XML event directly from parser. fn next_event_impl(&mut self) -> Result { loop { match self.parser.next() { // Ignore whitespace and comments by default. Ok(XmlEvent::Whitespace(..) | XmlEvent::Comment(..)) => continue, Ok(event) => return Ok(event), Err(e) => return Err(self.error_emitter.emit_error(&e)), } } } pub fn document(&mut self, f: F) -> Result where F: FnOnce(&mut XmlParser<'_>, Document) -> Result, { let doc = self.start_document()?; let result = f(self, doc)?; self.end_document()?; Ok(result) } fn start_document(&mut self) -> Result { match self.next_event()? { XmlEvent::StartDocument { .. } => Ok(Document), e => Err(self.unexpected_event(&e)), } } fn end_document(&mut self) -> Result<(), String> { match self.next_event()? { XmlEvent::EndDocument { .. } => Ok(()), e => Err(self.unexpected_event(&e)), } } pub fn elements(&mut self, mut f: F) -> Result, String> where F: FnMut(&mut XmlParser<'_>, &Element) -> Result, { let mut results = Vec::new(); loop { match *self.peek_event() { Ok(XmlEvent::StartElement { .. }) => { let element = self.start_element()?; results.push(f(self, &element)?); self.end_element()?; } _ => return Ok(results), } } } pub fn element_with_name(&mut self, expected_name: &str, f: F) -> Result where F: FnOnce(&mut XmlParser<'_>, &Element) -> Result, { let elem = self.start_element()?; if expected_name != elem.name.local_name { return Err(self.unexpected_element(&elem)); } let result = f(self, &elem)?; self.end_element()?; Ok(result) } fn start_element(&mut self) -> Result { match self.next_event() { Ok(XmlEvent::StartElement { name, attributes, .. }) => Ok(Element { name, attributes, position: self.position(), error_emitter: self.error_emitter.clone(), }), Ok(e) => Err(self.unexpected_event(&e)), Err(e) => Err(e), } } fn end_element(&mut self) -> Result<(), String> { match self.next_event() { Ok(XmlEvent::EndElement { .. }) => Ok(()), Ok(e) => Err(self.unexpected_event(&e)), Err(e) => Err(e), } } pub fn text(&mut self) -> Result { let mut result = String::new(); loop { match *self.peek_event() { Ok(XmlEvent::Characters(..)) => { if let Ok(XmlEvent::Characters(s)) = self.next_event() { result.push_str(&s); } } Err(_) => { self.next_event()?; unreachable!(); } _ if result.is_empty() => { return Err(self.fail("Expected text content")); } _ => break, } } Ok(result) } /// Ignore everything within current element. pub fn ignore_element(&mut self) -> Result<(), String> { let mut depth = 1; loop { match *self.peek_event() { Ok(XmlEvent::StartElement { .. }) => { // Ignore warning about unused result, we know event is OK. drop(self.next_event()); depth += 1; } Ok(XmlEvent::EndElement { .. }) => { depth -= 1; if depth > 0 { drop(self.next_event()); } else { return Ok(()); } } Ok(_) => drop(self.next_event()), Err(_) => return self.next_event().map(|_| ()), } } } } #[cfg(test)] mod tests { use super::*; fn with_parser(xml: &[u8], f: F) -> Result where F: FnOnce(XmlParser<'_>) -> Result, { f(XmlParser::new(xml)) } #[test] fn test_element_with_name() { fn parse_with_root_name(xml: &[u8], root: &str) -> Result<(), String> { with_parser(xml, |mut p| { p.document(|p, _| p.element_with_name(root, |_, _elem| Ok(()))) }) } let xml = br#" "#; assert!(parse_with_root_name(xml, "a").is_ok()); assert!(parse_with_root_name(xml, "b").is_err()); } #[test] fn test_ignore_element() { let xml = br#" some text content "#; with_parser(xml, |mut p| { p.document(|p, _| p.element_with_name("a", |p, _| p.ignore_element())) }) .unwrap(); } #[test] fn test_elements() { let xml = br#" "#; let result: String = with_parser(xml, |mut p| { p.document(|p, _| { p.element_with_name("root", |p, _| { p.elements(|_, elem| elem.attr_required("name").map(|s| s.to_owned())) .map(|v| v.join(".")) }) }) }) .unwrap(); assert_eq!("a.b.c", result); } #[test] fn test_text() { let xml = br#" hello world!"#; let result: String = with_parser(xml, |mut p| { p.document(|p, _| p.element_with_name("x", |p, _| p.text())) }) .unwrap(); assert_eq!("hello world!", &result); } #[test] fn test_attr_required() { let xml = br#" "#; with_parser(xml, |mut p| { p.document(|p, _| { p.element_with_name("x", |_, elem| { assert!(elem.attr_required("a").is_ok()); assert!(elem.attr_required("b").is_ok()); assert!(elem.attr_required("c").is_err()); assert!(elem.attr_required("d").is_err()); Ok(()) }) }) }) .unwrap(); } #[test] fn test_attr_from_str() { let xml = br#" "#; with_parser(xml, |mut p| { p.document(|p, _| { p.element_with_name("x", |_, elem| { assert_eq!(elem.attr_from_str::("a").unwrap(), Some(123)); assert!(elem.attr_from_str::("b").is_err()); Ok(()) }) }) }) .unwrap(); } }