Repository navigation
Conversation
dybucc
force-pushed
the
feature/ctest-specific-modules
branch
12 times, most recently
from
September 6, 2026 16:20
f845200 to
3f32191
Compare
dybucc
force-pushed
the
feature/ctest-specific-modules
branch
from
September 13, 2026 14:35
3f32191 to
e780462
Compare
This comment has been minimized.
This comment has been minimized.
dybucc
force-pushed
the
feature/ctest-specific-modules
branch
4 times, most recently
from
September 15, 2026 14:42
755c2ad to
9fc67fc
Compare
Add support for parsing modules in the input Rust crate to `ctest`. This should allow more easily implementing support for a number of recent feature additions that have been needed in `libc`.
Add support in `TranslationHelper` to filter out modules based off of a new type of skip accepted in `TestGenerator`'s public API. Filtering containing items is done by filtering on stringified paths (e.g. `foo::bar` to skip function `bar` inside top-level module `crate::foo`.)
Trim comment lines and strings to not surpass an 80-character mark from the very first character after a newline in source. This has been applied automatically through relevant unstable options in `rustfmt`.
- Rename `ident` function on all parsed items to `path`. After the changes in the last few patches, this function was returning the cached string we keep in each item representative of the stringified token stream from each item's `path` field. Thus, speaking of an identifier here is a bit misleading, and considering it a path to the item is more correct. - Add `ident` function to return the last segment of each parsed item's `path` field. This corresponds now with the previous semantics of `ident`, now `path`, but breaks the API because it returns a fully-owned `String` and not a `&str`. It also incurrs an additional allocation on each call, but that can be easily fixed by also "caching" this last segment of the item's path in one of the item's fields.
- Replace and tweak call sites where there were uses of the `ident` function exposed by parsed items into using a combination of both the new `ident` (which replicates the former's semantics,) `path` (which provides the full path to the item from the crate root,) and a new routine; `escape_item_path`. The latter is used to replace the default path separator `::` with `_`. Use of the these three is necessary to get the tests to both refer to the right Rust items in the parsed crate submodules, while keeping the same (single-segment) identifiers for C symbols. - Add `rust_ty` and `rust_val` fields to some of the types gathering data for the test templates. This is also necessary for keeping track of the actual paths to the types and symbols exposed by the crate, as modules introduce the possibility for items to be referred to by a path with more than one segment. - Tweak uses of Rust to C `MapInput` type variants for remapping into specifically only returning `CEnum`s in the current module. Before modules were supported, checking straight with the list of skips in the running `TestGenerator` was enough. This is not the case anymore, as those skips are provided by the user with respect to full paths (i.e. they apply globally across parsed modules.) The change in this patch ensures that when returning a `MapInput` variant that yields a type and not an item, the `CEnumType` variant is returned only when the current module being parsed contains an alias with the passed identifier, and further checks with the `TestGenerator` if the full path to that alias (if found) has a `CEnum` remapping set. - Tweak the Rust test template to reflect the changes made to the `template` module. This uses the new fields introduced in this same patch, and changes some uses of the `id` field to either one of the `rust_ty` or `rust_val` fields, as those keep the full path to the item type or item, respectively.
- Tweak one of the tests to adjust to the way module support has been implemented. Previously, items in nested modules would be expected to surface at the top-level. Now they are meant to be part of the parsed nested module within the initial `FfiItems` instance. - Tweak Rust test template to avoid `unused` lints against some of the utility functions. I am still looking through some stuff in the tests, but thus far these functions sometimes simply don't get used because certain askama loops never iterate when the tests that use them are wholly skipped.
Collaborator
|
☔ The latest upstream changes (possibly #5567) made this pull request unmergeable. Please resolve the merge conflicts. |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Add support for (currently) parsing and filtering Rust modules found in
the target crate to test. This is supposed to provide scaffolding for
multiple open feature requests that depend on ctest having support for
modules.
I decided to open this before finishing up work on generating proper
tests on both C and Rust sides, because I am not sure if I should just
let the user deal with including a cohesive set of modules that do not
cause item resolution conflicts, or if I should instead generate
separate tests for each parsed module.
Edit: the above paragraph really goes to say that I am not sure
whether I should generate multiple test templates while recursing
through the modules in
TestGenerator::generate_files, or if I shouldinstead recurse through them in
TestTemplate::new(with the passedFfiItems) and return a collection of test templates for each parsedmodule that has not been skipped.
Edit 2: I decided to go for generating multiple test templates for
each parsed module.
Edit 3: I completely forgot about
usestatements, so most of thisis currently useless.
Edit 4:
usestatements have now an initial solution. This stillhas limitations. Relevant changes are documented in Section Update 3. A
better algorithm is in the works.
cc @tgross35 @mbyx
TODO
Look into parsing
usestatements, as those are the one thingsmissing from getting modules to work.
Update the docs to the public API of
TestGeneratorto mentionthat the name remapping ought return the C identifiers without
worrying about potential item resolution conflicts.
Look through the
populate_roundtrip_testsfunction, as that oneI skipped to go first to
populate_field_ptr_testsbecause of theorder these tests appear in the test template files.
Look into properly resolving paths to the types of type aliases,
constants and statics. These are handled poorly now. An example is
the check for arrays that is made in
template::TestTemplate::populate_roundtrip_tests, which does notconsider whether the type of the alias is itself an alias to be
resolved recursively (and potentially be an array.)
Update 1
I have changed the way we parse item identifiers, and more specifically,
their absolute paths without
crateat the start (e.g.foo::bar::ctime()instead ofcrate::foo::bar::ctime().)This should make filtering continue working as-is right now for items,
but allow filtering on items in nested submodules by using an
appropriate string matching something like the above example.
Update 2
I think the initial set of changes is done. I have gone down the route
of potentially generating multiple templates for each parsed module,
such that more than a single test is generated. I have not yet
implemented the multi-template generation stuff, but that should be
fairly straightforward. What I believe to be done is the logic for
generating test to the right Rust paths while also keeping the single
segment paths on the C side of things.
I am currently working through the tests, to see what is missing and
broken from the prior
ctestinterface. At this point, I am only tryingto work through the prior tests to see if they continue working and
generating the same set of tests (barring nested modules.)
Update 3
This is an update on the last few weeks' worth of work. Time was spent
on a module resolution algorithm. This is necessary for correct item
paths. An example of this need is
libcitself.The current module tree shape has reexports at the crate root. This
means items should be "referrable" by that path (i.e. their identifiers
alone.) But the current strategy (in this patch) gets their source path.
This does not apply to upstream
ctest.ctestcurrently assumes itemsare reexported at the crate root. It only picks up on item identifiers
during parsing. But module support needs import resolution.
A summary of related changes in this patchset follows. This does not
currently include all changes in this PR.
Changes to
FfiItemsparsingThe
FfiItemstype needed to change its use ofsyn's visitor hooks.These are not completely useful with the addition of modules. This also
uncovered what is believed to be a bug in upstream
ctest.The main concern here are non-module scope items. The
synvisitorhooks will visit every single matching item. This means items that only
exist within a function are also parsed.
Such an item can not be used outside of the function. Other non-module
scopes also have this limitation (e.g. module-level
constblocks.)Upstream
ctestwill thus attempt to generate tsets for these as well.One could argue downstream users can set up skips for these. But that is
only a workaround. This also applies to
use-statement scanning. Ausestatement can exist within a function. But those should not beresolved.
Bottom line: The
synvisitor hooks had to be removed. Though it wouldbe more accurate to say that they were repurposed. Those trait methods
now live as free functions.
The solution went through use of the module hooks. These are the only
ones we can safely use. A module may not be declared within a function
scope. And
ctestrequires only the items that appear within modules.The current approach wraps the crate root in a module. This is because
the crate root is not parsed as a module. So the hook for modules is not
run in those cases. Then the actual parsing is done in the hook.
Each of the items
ctestneeds are "manually" parsed. This calls intothe previous hooks. These are now free functions. This is where they are
now used. This deserves further explanation.
The crate root is wrapped in a virtual module. This is to trigger the
module visitor hook. The virtual module needs to have a special
identifier. This is detected during parsing.
The crate root gets all parsed items added to it. Any other module gets
a new child module with all parsed items added to it. This builds up the
module tree hierarchy as one would expect.
But all of this needs to exist within a wrapper. Filling in an
FfiItemsis no more about calling intosyn's file hook. It needs tofirst wrap the items in the file in a module of their own.
This is what eventually gets parsed. The item container is then
unwrapped from the virtual module. Note the module is a
synmoduletype. This needs another transformation for the resolution algorithm.
There is one last thing to note about parsing.
usestatements may comeas groups (paths wrapped in curly brackets.) This is not convenient to
handle afterwards. These get "normalized" during parsing.
Normalization does one thing alone. It munches through a
usestatement. This may eventually stumble upon a group. That gets flattened
into a set of individual
usestatements. A singleusestatement thenyields a list.
One note about terminology must be made here.
usestatements may havearbitrary paths as their associated objects. They may also have a single
name. These are all referred to from here on as paths.
The resolution algorithm
The algorithm for resolution will now be explained. This follows a
fairly straightforward process. There is a single entrypoint routine. It
takes a module and yields another module.
This is meant to be called from the afore mentioned wrapper. It is
called with another virtual module for the root. Recall the crate root
is initially only an item container.
The new virtual module uses
ctest's ownModuletype. This is used tokeep track of the path to the module. This will be needed during
use-statement resolution.Resolution starts by bottomming out on the list of child modules. This
is to ensure it takes place in a depth-first manner. Then an adaptive
pass system is triggered over a single module.
The current approach requires resolution to be depth-first. This is
because of the current limitations the algorithm has. The current
resolution strategy can only resolve "forward"
usestatements.A "forward"
usestatement is defined as a one that brings into scopeitems from a descendant module. Its dual brings into scope items from
some ascendant module.
These observations are made with respect to the module in which the
usestatement appears.Note this does not exclude absolute
usestatements. Ausestatementis determined as one of the above based off of the target item. If the
target item is a descendant then that is a "forward"
usestatement.Current work is focused on support for bidirectional imports. The rest
of the explanation will focus on the initial revision of the algorithm.
This supports only
usestatements that refer to descendant items.This justifies the need for a depth-first approach. A leaf module in the
crate's module tree will have no resolvable
usestatements. This isalso a good time to define "resolvable" and "unresolvable"
usestatements.
A "resolvable"
usestatement is said to be resolvable in some finitenumber of resolution passes. Its dual is impossible to resolve in any
number of resolution passes.
One example of the latter are
usestatements from third-party crates.These will never resolve. The algorithm could attempt to inject the
crates into the module tree. But a simpler solution was found.
That shall be explained later.
The pass system explanation will now be resumed. A single resolution
pass is given by two steps. The first one is the production of a list of
resolution items. The second one merges these into the module at hand.
The former resolves all
usestatements within a given module. Thisyields a list of resolution items. Each resolution item corresponds with
one
usestatement. It can be "resolved" or "unresolved."A
usestatement is said to be "resolved" if the path it denotes hasyield a concrete item. Its dual refers to an item path that was not
found with respect to the module where the
usestatement appears at.Consider the following example.
Resolution of the first
usestatement depends on the second. The firstusestatement will yiled an unresolved item in the first pass. Onlyupon the second
usestatement resolving will the former also resolve.Though note both statements in the above example are said to be
resolvable. A definition for this is given further above. The third
usestatement is said to be unresolvable.A single pass maps the list of
usestatements. It uses theuse-statement resolution routine as map transform. This routinemunches through a given
usestatement's path.Each path of the segment consists of an identifier. This routine carries
with it a module. This serves as state. This state is initially seeded
by the module where the
usestatement appears at.Each identifier in the path gets looked up in this stateful module. A
match means the routine may continue munching. A mismatch means the
routine must yield an unresolved item.
A munch that reaches the basecase denotes a resolved item. This is
produced much like an unresolved item. This bubbles up to the single
module resolution routine. That one mapped the list of
usestatements.This eventually yields a potentially non-empty list of resolution items.
These now need to be merged into the top-level module. This is the
module where the
usestatements that sourced the resolutions lived at.This now enters the second stage of a given resolution pass. Merging
iterates through the list of resolution items. Resolved items pack the
item the
usestatement referred to. This deserves further explanation.A
usestatement can refer to one of a non-glob or a glob. Non-globsmay be further specified as non-modules or modules. A glob packs in the
resolved item an item container with all items from the parent module.
A non-glob packs in the resolved item an item container with itself only
as the contents. Notice how a non-module is small. A module carries with
it the subtree rooted at it in the crate's module tree.
A resolved glob has a very similar topology to a module. It also packs
the subtree rooted at it in the crate's module tree. But it contains no
information on the "subroot" where that subtree was sourced from.
Now back to the merging routine. Each resolved item also carries with it
the source
usestatement. This is looked up in the module into whichto merge. This is then is removed from the module into which to merge.
The last step is to extend the module into which to merge. This is the
actual "merging." The packed item container is deconstructed into its
component items. These then extend the lists of items in the target.
That finishes a single resolution pass. The next thing it does is go
back to the top-level resolution routine. That now determines whether
another pass is in order.
This is determined by diffing the number of
usestatements. Theoperands are the pre-pass module and the post-pass module. Passes over a
single module terminate if this comparison yields no differences.
This is also what allows unresolvable
use-statements not to break thealgorithm. These will not change once resolvable
use-statements arefully resolved. That is the sign that the pass system catches on.
This is then repeated depth-first across all modules. The root module is
processed last. This one has at that point the entire subtree resolved.
It may need more than one pass. But it will resolve resovable imports.
Extra notes
A few details were glossed over the above explanation. These are not
essential to understanding the algorithm. Chief among them is the path
manipulation routine and the rename case of
use-statement resolution.The latter will be explained first. A
usestatement in Rust can have atrailing renamed identifier. This identifier needs to also be reflected
in
ctest's view of the crate's module tree.An item's path is often straightforward to rename. Modification of the
last segment of a path is all it takes to rename a non-module. But a
module is also a subtree for other items and modules.
A module thus needs to recursively rename all items within the tree.
That is the purpose of the path manipulation routine. It modifies a
given module subtree with an externally-sourced closure.
Use of this routine in the rename case is straightforward. The case of
modules first modifies the root's path. Then each item builds its path
anew. The base path becomes the root's new path. Other segments remain.
The path manipulation routine is also used in the merge routine. Here
the logic is slightly more intricate. Path manipulation takes the module
in which a tree is rooted at. But a resolution item packs a container.
Recall item containers differ from modules. Items containers exist
within modules. So path manipulation during merging requires a virtual
module. This itself requires special dummy values.
A dummy path segment for the virtual module must be non-empty. This is
because the passed closure assumes so. This holds for all descendant
items. But there is another subtlety to this path manipulation call.
The passed subtree contains not the modules. These need further
consideration. A module to be merged should not have the entire path of
descendant items modified.
Only segments prior to the root's identifier should be removed. This
collides with the above path manipulation call. So modules are handled
separately. They do follow the same logic as the rename case above.
One more thing happens in a pass. A merged module's items are checked
for duplicates. This can happen from overlapping
use-statements. Thetype for an item container implements that operation.
Consider this example.
The crate root imports would bring two identical
Foos into scope.Cases like these are in need of deduplication.
Performance has not been taken into consideration. Certain data
structures could be improved. One example would be the use of
associative arrays for items. This would avoid deduplication.
Checklist
ctest/testsandctest/src/tests.rshave beenupdated
LIBC_BLESS=1 cargo test -p ctest);@rustbot label +stable-nominated