-
Notifications
You must be signed in to change notification settings - Fork 261
Introduce a path!
macro for creating items from simple paths
#485
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
/// Produces an item corresponding to the provided path. | ||
/// | ||
/// This macro can output any type `T` which has `impl Make<T> for Path`. | ||
/// In particular, it can produce types and expressions which correspond | ||
/// to the given path. The path may be both relative and absolute. The | ||
/// `crate`, `self` and `super` path specifiers are also supported. | ||
/// | ||
/// The macro cannot produce qualified paths, e.g. any paths with generic | ||
/// parameters, or paths which start with a `<Type as Trait>` construct. | ||
/// | ||
/// At the moment, the path must be written literally. Variable interpolation | ||
/// is not supported. | ||
/// | ||
/// # Example | ||
/// | ||
/// ```rust | ||
/// # use c2rust_ast_builder::{syn, path}; | ||
/// let p: syn::Path = path![::core::ptr::null_mut]; | ||
/// let e: Box<syn::Expr> = path![Vec::new]; | ||
/// let t: syn::Type = path![::libc::c_char]; | ||
/// ``` | ||
#[macro_export] | ||
macro_rules! path { | ||
(:: $($seg: ident)::+) => {{ | ||
let path = $crate::syn::Path { | ||
leading_colon: Some(Default::default()), | ||
..path!( $($seg)::+ ) | ||
}; | ||
$crate::Make::make(path, &$crate::mk()) | ||
}}; | ||
|
||
($($seg: ident)::+) => { | ||
$crate::Make::make([ $( stringify!($seg) ),+ ], &$crate::mk()) | ||
}; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just commenting so it's easier to find.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! This looks really great and really simplifies things.
A couple of questions from a quick overview, though I'll look more closely tomorrow:
- Why
path![]
instead ofpath!()
? - In
path![Vec::new]
, how and what doesVec
resolve to? - Can this create
PathSegment
s that can then be joined to generics so we can at least usepath!
for the stem of those paths? - Are the paths in
path![]
IDE clickable? I think that's somewhat important. - When you saying qualified self, do you mean
self
orSelf
? - In the common case, can it default to absolute paths and check that that path is correct at compile time?
- Does this fix Qualified paths like
libc::c_int
should be globally qualified like::libc::c_int
to avoid name clashes #447? I saw it does at least most of it I think.
I agree that removing the unnecessary generics is good. Better compile speed and no ugly casts due to no inference, as you said. Removing lots of unnecessary vec![]
s is also great, as most of those should've been arrays, slices, or iterators.
Also, run |
path!
macro for creating items from simple paths
Because I refactored a lot of expressions of the form I don't think there is any strong argument for any of the bracket types, so that reason is as good as any. If you want, you can conceptualize it as an array of path segments, separated by Braces would look a bit heavyweight, I associate them with complex code blocks and not simple paths. Parentheses are possible, but that would be a ton of work for little gain. Personally, I feel that parentheses kinda merge with the contained path, and not stand out enough. They will blend even more if the macro is ever modified to accept optional function call arguments. Brackets are unlikely to be used inside of that macro.
Nothing, it's just a token. It is essentially the same as with sequences of strings that we had before, but it is easier to type, easier to read, and more greppable (i.e. I can easily search the code for all uses of I doubt that it's possible to make
Maybe, but I would like to think more on the good API for that case. There are a lot of cases like There are some more complex cases, but I currently have no opinion on the best way to support them (or even whether they should be supported by the macro in the first place).
That would be great, but I have absolutely no idea how it would be possible to implement. Quite likely that it's just impossible. It is also somewhat of dubious value: it would mean that working on the transpiler would require having as direct dependencies all crates which may be used by the transpiled code. Ok, adding On the bright side, an explicit full path is pretty easy to search in the IDE. I don't know about VsCode, but CLion understands Rust paths in symbol search window.
I mean that you cannot write paths like
Absolute paths are no different from relative paths at the type level, they are both
No, although I patched some obvious issues. In particular, I didn't fix anything in the paths which I didn't move to the new API, and I didn't fully qualify imported macros. |
Works on my machine 🤷♂️ Actually not really, but I get an entirely different and even more confusing set of errors: "asm.aarch64/asm-test" fails to link because each of its object files "is incompatible with elf_x86_64". Shouldn't it be doing a cross-compilation? I also get an "unused argument" error in one of the inline assembly snippets. This is certainly not related to my PR. In fact, I have also been getting many weird errors when I ran the test script in the past, even on the 2020 version of c2rust. I just thought that it's some misconfiguration of my environment, or some known failure which is somewhy not ignored by my test runner. I didn't change anything in the FYI, I'm running tests on WSL 2.0 Ubuntu 20, with CLang 11. |
That's reasonable. I think there are possible extensions to this initial API that could work well with braces, so it may be fine.
Ah, that makes sense, though we should avoid unqualified paths like pub trait TypeName {
fn type_name() -> &'static str {
std::any::type_name::<Self>()
}
fn type_name_of_val(&self) -> &'static str {
Self::type_name()
}
}
impl<T> TypeName for T {}
fn main() {
let x = Vec::<()>::new.type_name_of_val();
println!("{x}");
} It works for types and functions, but as you can see it doesn't work well with generics, and doesn't work for macros and modules, so I'm not sure how useful it is, but it does have some benefits in giving the full, canonical path, seeing through re-exports like
Re possible future interpolation, I think something like this would be preferable and easier to implement: path![::core::ptr::null_mut].join(generic_args(&[ty]));
path![::core::ptr].join(access);
With something like this, macro_rules! path {
($path:path) => {
{
#[allow(unused_imports)]
use $path;
stringify!($path).split("::")
}
};
} the path (a I still think something like this is quite useful, but it could be optional. Maybe a happy-path
Ah. I don't think we ever use those, and hopefully, we don't need to. They're quite ugly.
I think we want to make relative paths harder to use, as we should be using absolute paths in almost all of the cases. So I thought
Got it. We'll finish that up later then. |
Re the CI failures and testing, the local errors you are getting are likely because you've I've also tested things time-to-time on WSL 2, Ubuntu 20.04, but with clang 14 (though I've tested versions 6-14 in the past). I've seen those same errors in CI locally before when I messed around with the derive macros' imports, so I'm guessing it's there. |
Also, just checked, |
Ah, the reason the |
The error from c2rust/tests/statics/src/test_sections.rs Lines 32 to 42 in e42598e
|
I think there should be some limits on that policy. For example, all of There is also the case of identifiers, identifier expressions and identifier patterns which can also be produced as Imho it is more reasonable to prevent specific crates (e.g. libc and memoffset) to be used via relative paths, but that can be easily achieved just by grepping the codebase for |
I think primitive types like
Sorry, I'm not sure what you mean by this?
You're right one can always get around things using In fact, there's a good chance some C libraries have |
There are a number of mostly separable things going on here:
|
I agree with this. Inferred generics here makes things over-complicated. For example, there's no way to specify the generic with a turbofish
True, this is probably not worth it, and with reflection, we could run into problems if cross-compiling/transpiling (as I found out with
I disagree with this. The reason we currently have a hodgepodge of some absolute paths and some relative paths, when almost all of the paths should be absolute, is precisely because of reasons like this. I think the builder methods should also be renamed to prioritize relative paths, so |
Correct me if I'm wrong, but the only reason to prefer absolute paths is to avoid unintended shadowing of our required crates by some names in the transpiled C code. There is an imho easier and more systematic way to deal with this: add all required crate names to the list of reserved identifiers, and generate fresh renames for all collisions. There is just a handful of reserved crate names, and they aren't that likely to be shadowed in the first place. Absolute paths are a bit of an odd feature of the language. They are very unidiomatic. I struggle to remember any other use case for them outside of So absolute path is almost never used, most users will probably be confused when they see it, the reflexes will always lead one to use relative paths, and it isn't even clear which items should or shouldn't use absolute paths (c.f. the
But that is a distinction which doesn't matter in practice. The API is strongly typed, and there is no possibility of using the macro in a place which can accept different node types (and if one was to change the API and introduce such places, the code wouldn't typecheck since the intermediate node type couldn't be resolved). What matters is that the object is given by the specific path, and it is obvious from the path whether it's an expression, or type, or macro (both because of naming conventions and because the paths that c2rust can hardcode are all well-known objects from stdlib and a few crates). We could make separate
That is a more practical issue, indeed. This means that the macro cannot be used in a position of generic arguments. In particular, one cannot call trait methods on One possibility is to re-introduce generics into the API, but in a more strategic way which would actually allow to reduce boilerplate. |
I have found the cause of errors. It was actually caused by my fix of relative to absolute item paths. The relative paths were hardcoded in test fixtures, and were also incorrectly handled by the bitfield derive macro. Perhaps the test fixtures are too brittle, although one can also say that a change between relative and absolute item paths is something that should be detected by tests. |
… on literal paths
This impl just wraps a Clone call. Moving this call to the use site results in more clear code with more clear performance characteristics (i.e. no hidden allocations). Removing this impl also simplifies the trait resolution, potentially avoiding impl conflicts.
The translation is similar to `.path(vec![..])` -> `.path([..])`. This uses the `impl Make<Path> for [T; N]` instead of `impl Make<Path> for Vec<T>`. This change simplifies the use sites and avoids redundant allocation of vectors which are immediately destroyed.
This replaces `path_ty`, `path_expr`, `path` and similar calls with the `c2rust_ast_builder::path!` macro invocation where the resulting type can be unambiguously inferred.
Remove generic parameters which are actually used only with a single concrete type, substituting that type instead. E.g. a function `fn foo<E: Make<Box<Expr>>, T: Make<Box<Type>>(e: E, t: T)` is turned into a function `fn foo(e: Box<Expr>, t: Box<Type>)`. This doesn't restrict the usages of the function in any way, since the only type which is `Make<Box<Expr>>` is `Box<Expr>` itself. These generic parameters are strictly redundant. They produce generic bloat, harm compile times, complicate the API, and prevent type inference for function arguments (i.e. if `foo(f1(), f2())` and either `f1` or `f2` is generic in its return type, then type inference will fail). Using the concrete types is strictly better, and opens the possibility of more ergonomic API with better type inference.
This trait is actually implemented for a single type: `&str`. The generic function bounded by LitStringable actually accept only `&str`. Removing the trait and specializing the function results in simplified, possibly more efficient API.
…onstruction This relies on the specialized constructors of ast nodes, which allows to use the same macro call to create items of different types (which are all unambiguously represented by a simple path). Also includes fixes to some paths which should have been absolute, but were written as relative.
* change `Vec::<Box<Expr>>::new()` into `vec![]` in builder method argument position; * fix translation of `CTypeKind::UInt128` from `bf16` into `u128`; * remove redundant `Make::make` calls; * suppress clippy::boxed_local which fires incorrectly;
Note that this change applies only to paths used via the `path!` macro.
Removed unused dependencies for c2rust-transpile. Disabled default features for `syn` and `proc-macro2` crates (this is unlikely to significantly affect compile times since those features are still used in the proc macro crates).
Also use `punct_box` where appropriate.
* remove redundant clones; * simplify pattern (remove redundant `ref` and `&` patterns); * convert `opt.map(f).unwrap_or(v)` into `opt.map_or(v, f)`; * remove explicit `into_iter` and `iter` calls where redundant; * eliminate gouble-step initialization of mutable variable via combinators;
Please make unrelated changes (8307e28...f2778f6) as one or more PRs separate from this one. I think the absolute-vs-relative paths question should be addressed separately as well. I like the idea of avoiding accidentally stepping on shadowed identifiers while using idiomatic relative paths where possible, but that doesn't depend on whether we construct paths with a macro or in longhand. For this PR, I would ask that relative/absolute paths are left as-is so that it's easier to review that the macro change is semantics-preserving. re: implicit conversions from paths to syntax nodes that contain them, I still don't think that this is good for readability. The confusion is not about what kind of (object-language) entity is indicated by the path, but rather the type of the (meta-language) value returned by the For example, the change: - let fn_path = mk().path_expr(vec!["f128", "f128", "from"]);
+ let fn_path = path![::f128::f128::from]; is confusing as what was once a Something like: let fn_path = mk().path_expr(path![::f128::f128::from]); would leave it clear that It's possible to deduce what ends up happening with the additional |
This PR introduces the
path!
macro which can be used likeIt can output any type
T
which hasimpl Make<T> for Path
. Currently this meansPath
,Type
andExpr
. Currently the macro supports only simple raw paths, i.e. no variable interpolation (only literal identifiers), no qualified self and no generic parameters. The restriction on simple interpolation of variables may be lifted in a later PR. The restriction on qualified self and generics will likely no be lifted, since they seem far beyond the capabilities of reasonable declarative macros.This PR also adds
impl Make<Path>
for slices and arrays. This allows to avoid unnecessary allocations when creating paths (almost all paths created by the transpiler have a statically known size). As an extra benefit, switching fromvec![..]
to[..]
construction looks more lightweight, and allows processing of the array by rustfmt (vec! calls are not formatted properly in general).This PR also specializes the methods of
Builder
with respect to most generic parameters. Those generics were redundant since each position could be occupied only by a single specific type. E.g.Make<Box<Expr>>
is implemented only byBox<Expr>
, so that is always the type ofimpl Make<Box<Expr>>
arguments.Specializing those generic parameters has no restriction on the practical usability of the API, and it in fact makes it more flexible: generic parameters break type inference. If I have
fn foo<T: Trait>(t: T)
andfn bar<R>() -> R
, thenfoo(bar())
will result in a compile error since the type ofR
is generally impossible to infer. The type inference is heavily used by thepath!
macro to overload on the return type.As a bonus, specializing the parameters allows to remove a significant number of redundant explicit type casts. It also results in better compile speed (generics aren't fully compiled in the defining crate, and are instantiated separately in each codegen unit). The speed benefit is minor, but a nice touch.