Skip to content

Commit 09693de

Browse files
bors[bot]xFrednetNiki4tap
authored
Merge #103
103: Add `QualifiedAstPath` and `PathExpr` representation r=Niki4tap a=xFrednet This is a draft PR to get some early feedback on the `QualifiedAstPath` representation. [Qualified paths](https://doc.rust-lang.org/reference/paths.html?search=QualifiedPathInType#qualified-paths) can be used in [expressions](https://doc.rust-lang.org/reference/expressions/path-expr.html) and [types](https://doc.rust-lang.org/reference/types.html?highlight=QualifiedPathInType#type-expressions). These are type relative and allow the definition of the `Self` type for traits (Example: `<Vec<u32> as Default>::default()`). This can be relevant, if a struct has several functions with the same name. An example is shown at the end of the PR description. ## Representation The current [`AstPath`](https://github.com/rust-marker/marker/blob/master/marker_api/src/ast/common/ast_path.rs#L13) representation is a simple slice of segments, which have an ident and potentially generic args. This representation is already a hybrid of [simple paths](https://doc.rust-lang.org/reference/paths.html#simple-paths) and [expression paths](https://doc.rust-lang.org/reference/paths.html#paths-in-expressions). The simple path, has the same structure as an expression path, with the difference, that it can't have generic arguments. In the current implementation, this simply means that all generic arguments are empty, for simple paths. [Qualified paths](https://doc.rust-lang.org/reference/paths.html?search=QualifiedPathInType#qualified-paths) are not as easily combinable with the existing `AstPath` as it can contain segments with `Self` type specification. Generally, all places, where a qualified path can be used, also a normal path is usable. It therefore makes sense to combine them in some shape or form. I thought of a two main ways to represent this: 1. Everything can be represented as one `AstPath`. The segments would then be an enum, with either an identifier or the `Self` type specification. The disadvantage is that the segment enum, would need to be handled everywhere, even in locations where the path can never be qualified. 2. Create a wrapper type, which optionally specifies a `Self` type. This is the way rustc does it with [`QPath`](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_hir/enum.QPath.html) I always found this confusing until now, when I was forces to fully understand how qualified paths work. I also think that just providing a `Path` directly, can cause users to use it directly, without considering, if the `Self` type is relevant. In most cases, this will be fine, but it might be possible to express the semantics better. This PR proposes a variant of the second option, with a lot of documentation. The `AstPath` representation can be accessed via the `TryInto` trait or the `to_path_lossy` function. This ensures that users, have simple access to the `AstPath` representation, but don't accadently ignore potentual `Self` type specifications. ## Naming Another question I have, is about naming. I find `QualifiedAstPath` very wordy. Can we maybe find a short version? Maybe, one of the following, if the meaning is clear 1. `QAstPath` 2. `AstQPath` 3. `QPath` 4. `QualifiedPath` --- Example when qualified paths are important: ```rs trait Foo { fn foo() { println!("foo() from Trait"); } } struct Item; impl Item { fn foo() { println!("foo() from Item") } } impl Foo for Item {} pub fn main() { Item::foo(); // "foo() from Item" <Item as Foo>::foo(); // "foo() from Trait" } ``` --- I plan to keep this as a draft PR, until I have made some modifications to the type representation and added the rustc backend, to ensure that the chosen representation can really express the semantics. The representation can still be changed later. I might create an issue in the design repo, to have a second look at the representation before making it stable. --- cc: #52 `@Niki4tap` Do you maybe have any thoughts on this? 🙃 Co-authored-by: xFrednet <[email protected]> Co-authored-by: Niki4tap <[email protected]>
2 parents e5e05ce + 71eec20 commit 09693de

26 files changed

+1992
-796
lines changed

marker_api/src/ast/common/ast_path.rs

Lines changed: 234 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,234 @@
55
// FIXME: It might be useful to not use a single path for everything, but instead
66
// split it up into an `ItemPath`, `GenericPath` etc. implementation.
77

8-
use super::SymbolId;
9-
use crate::{ast::generic::GenericArgs, context::with_cx, ffi::FfiSlice};
8+
use super::{GenericId, Ident, ItemId, VarId};
9+
use crate::{
10+
ast::{generic::GenericArgs, ty::TyKind},
11+
ffi::{FfiOption, FfiSlice},
12+
};
13+
14+
/// [`AstPath`]s are used to identify items. A qualified path (`QPath`) can be
15+
/// used in expressions and types to identify associated items on types. For
16+
/// traits it's additionally possible to specify the type that should be used
17+
/// as `Self`. This is sometimes needed to disambiguate an item, if it exists
18+
/// both as an associated item on a type, and as an associated item on traits,
19+
/// that this type implements.
20+
///
21+
/// In the following example, the `Item` has two implementations of the associated
22+
/// `foo()` function. One is provided by the `impl` block and the other one by the
23+
/// `Foo` trait. When calling `Item::foo()` the `impl` block implementation will
24+
/// targeted. To access the trait function, the path has to be specified, with `Item`
25+
/// declared as the `Self` type.
26+
///
27+
/// ```
28+
/// // Item
29+
/// struct Item;
30+
/// impl Item {
31+
/// fn foo() {
32+
/// println!("foo() from Item")
33+
/// }
34+
/// }
35+
///
36+
/// // trait
37+
/// trait Foo {
38+
/// fn foo();
39+
/// }
40+
/// impl Foo for Item {
41+
/// fn foo() {
42+
/// println!("foo() from Foo trait");
43+
/// }
44+
/// }
45+
///
46+
/// // Calls the `foo()` method of `impl Item`
47+
/// Item::foo(); // -> "foo() from Item"
48+
///
49+
/// // Calls the `foo()` method of `trait Foo` with `Item` as the `Self` type
50+
/// <Item as Foo>::foo(); // -> "foo() from Foo trait"
51+
/// ```
52+
///
53+
/// This representation can also be used to reference non-associated items, to
54+
/// make it more flexible. For these items, the path type will be [`None`]. The
55+
/// target can be resolved via the [`resolve()`](AstQPath::resolve) method.
56+
/// Alternatively, the [`AstPath`] representation can be accessed via
57+
/// [`as_path_lossy()`](AstQPath::as_path_lossy) or the [`TryInto`] implementation.
58+
#[repr(C)]
59+
#[derive(Debug, PartialEq, Eq, Hash)]
60+
pub struct AstQPath<'ast> {
61+
self_ty: FfiOption<TyKind<'ast>>,
62+
path_ty: FfiOption<TyKind<'ast>>,
63+
path: AstPath<'ast>,
64+
target: AstPathTarget,
65+
}
66+
67+
impl<'ast> AstQPath<'ast> {
68+
/// This method will return [`Some`], if the path has a specified `Self`
69+
/// type. The main type for type relative paths is provided by
70+
/// [`path_ty()`][`AstQPath::path_ty()`].
71+
///
72+
/// ```
73+
/// # let _: Vec<i32> =
74+
/// <Vec<_> as Default>::default();
75+
/// // ^^^^^^ The specified `Self` type `Vec<_>`
76+
/// ```
77+
///
78+
/// The [`AstQPath`] description contains more details, when this might
79+
/// be necessary.
80+
pub fn self_ty(&self) -> Option<TyKind<'ast>> {
81+
self.self_ty.copy()
82+
}
83+
84+
/// This method will return [`Some`], if the path is type relative.
85+
///
86+
/// ```
87+
/// # let _: Vec<i32> =
88+
/// <Vec<_> as Default>::default();
89+
/// // ^^^^^^^ The path is relative to the `Default` trait
90+
/// ```
91+
///
92+
/// The optional `Self` type can be accessed via [`self_ty`](AstQPath::self_ty()).
93+
pub fn path_ty(&self) -> Option<TyKind<'ast>> {
94+
self.path_ty.copy()
95+
}
96+
97+
/// This returns a [`AstPath`] of the referenced item. For type relative
98+
/// paths, this will include the type itself, if the type can be expressed
99+
/// as a [`AstPathSegment`]. For some types l,ike slices, this is not possible.
100+
/// The type has to be retrieved from [`path_ty()`][`AstQPath::path_ty()`].
101+
///
102+
/// ### Examples:
103+
///
104+
/// ```
105+
/// let _: Vec<i32> = Vec::default();
106+
/// // AstPath: `Vec::default`
107+
///
108+
/// let _: Vec<i32> = Default::default();
109+
/// // AstPath: `Default::default`
110+
///
111+
/// let _: Vec<i32> = <Vec<_> as Default>::default();
112+
/// // AstPath: `Default::default`
113+
///
114+
/// let _ = [0_u8].is_ascii();
115+
/// // AstPath: `is_ascii`
116+
/// ```
117+
///
118+
/// ### Warning
119+
///
120+
/// The method is lossy, as the optional `Self` type isn't included in this
121+
/// path. The path type might also be missing, if it can't be represented as
122+
/// a [`AstPathSegment`]. The conversion is lossless, if both types are none
123+
/// or if the path type can be represented. To resolve a qualified path
124+
/// [`resolve()`](Self::resolve()) should be used.
125+
///
126+
/// Omitting the `Self` type can be useful, in cases, where access to associated
127+
/// trait items should be analyzed, regardless of potential `Self` types.
128+
/// Alternatively, [`segments()`](AstQPath::segments()) can be used to access the
129+
/// segments directly.
130+
pub fn as_path_lossy(&self) -> &AstPath<'ast> {
131+
&self.path
132+
}
133+
134+
/// This returns the [`AstPathSegment`]s of the path. For type relative
135+
/// paths, this will include the type itself. The optional `Self` type
136+
/// isn't represented in these segments. These segments are identical with
137+
/// the segments provided by the path of
138+
/// [`as_path_lossy()`](AstQPath::as_path_lossy()). The documentation
139+
/// of that function contains more details.
140+
pub fn segments(&self) -> &[AstPathSegment<'ast>] {
141+
self.path.segments()
142+
}
143+
144+
/// This function resolves the target of this path.
145+
pub fn resolve(&self) -> AstPathTarget {
146+
// For rust-analyzer or future drivers, it might make sense to return
147+
// `Option<AstPathTarget>` instead, as the path might be dead,
148+
// when a lint crate calls this function. However, I have the feeling
149+
// that this would make the API less ergonomic. The `AstContext` will
150+
// already need to handle these cases explicitly. Currently, a user can
151+
// get a resolved id from the target, but the resolution of the ID, by
152+
// the `AstContext`, might fail. The outcome is the same, but all
153+
// "failable" resolution will be grouped in the `AstContext`
154+
self.target
155+
}
156+
157+
/// This returns the [`GenericArgs`] specified on the last segment of the path.
158+
/// This is especially useful, for paths pointing to types or functions. For
159+
/// example, the `u32` of the path `Vec<u32>`, is stored in the [`GenericArgs`]
160+
/// as a type parameter.
161+
pub fn generics(&self) -> &GenericArgs<'ast> {
162+
self.path.generics()
163+
}
164+
}
165+
166+
impl<'a, 'ast> TryFrom<&'a AstQPath<'ast>> for &'a AstPath<'ast> {
167+
type Error = ();
168+
169+
fn try_from(value: &'a AstQPath<'ast>) -> Result<Self, Self::Error> {
170+
fn is_segment_representable(ty: Option<TyKind<'_>>) -> bool {
171+
if let Some(ty) = ty {
172+
ty.is_primitive_ty()
173+
|| matches!(ty, TyKind::Path(path_ty) if is_segment_representable(path_ty.path().path_ty()))
174+
} else {
175+
true
176+
}
177+
}
178+
if value.self_ty.is_some() && is_segment_representable(value.path_ty.copy()) {
179+
Err(())
180+
} else {
181+
Ok(&value.path)
182+
}
183+
}
184+
}
185+
186+
#[cfg(feature = "driver-api")]
187+
impl<'ast> AstQPath<'ast> {
188+
pub fn new(
189+
self_ty: Option<TyKind<'ast>>,
190+
path_ty: Option<TyKind<'ast>>,
191+
path: AstPath<'ast>,
192+
target: AstPathTarget,
193+
) -> Self {
194+
Self {
195+
self_ty: self_ty.into(),
196+
path_ty: path_ty.into(),
197+
path,
198+
target,
199+
}
200+
}
201+
}
202+
203+
#[repr(C)]
204+
#[non_exhaustive]
205+
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
206+
pub enum AstPathTarget {
207+
/// The `Self` type, the [`ItemId`] points to the item,
208+
/// that the `Self` originates from. This will usually be an
209+
/// [`ImplItem`](crate::ast::item::ImplItem) or
210+
/// [`TraitItem`](crate::ast::item::TraitItem).
211+
SelfTy(ItemId),
212+
/// The path points to an item, identified by the [`ItemId`]. For example
213+
/// [`ConstItem`](crate::ast::item::ConstItem),
214+
/// [`StaticItem`](crate::ast::item::ImplItem),
215+
/// [`FnItem`](crate::ast::item::FnItem).
216+
Item(ItemId),
217+
/// The path target is a local variable, identified by the [`VarId`].
218+
Var(VarId),
219+
/// The path target is a generic type, identified by the [`GenericId`].
220+
Generic(GenericId),
221+
/// The target wasn't resolved, but should be available in the future.
222+
Wip,
223+
}
10224

11225
#[repr(C)]
12226
#[derive(Debug, PartialEq, Eq, Hash)]
13227
pub struct AstPath<'ast> {
14228
// FIXME: Add optional target ID for values, lifetimes, etc that is faster to compare
15-
//
16-
// You were last trying to fix the compiler error related to lifetime identification and paths
17229
segments: FfiSlice<'ast, AstPathSegment<'ast>>,
18230
}
19231

20232
#[cfg(feature = "driver-api")]
21233
impl<'ast> AstPath<'ast> {
22234
pub fn new(segments: &'ast [AstPathSegment<'ast>]) -> Self {
235+
debug_assert!(!segments.is_empty());
23236
Self {
24237
segments: segments.into(),
25238
}
@@ -30,25 +243,38 @@ impl<'ast> AstPath<'ast> {
30243
pub fn segments(&self) -> &[AstPathSegment<'ast>] {
31244
self.segments.get()
32245
}
246+
247+
/// This returns the [`GenericArgs`] specified on the last segment of the path.
248+
/// This is especially useful, for paths pointing to types or functions. For
249+
/// example, the `u32` of the path `Vec<u32>`, is stored in the [`GenericArgs`]
250+
/// as a type parameter.
251+
pub fn generics(&self) -> &GenericArgs<'ast> {
252+
self.segments
253+
.get()
254+
.last()
255+
.expect("a path always has at least one segment")
256+
.generics()
257+
}
33258
}
34259

35260
#[repr(C)]
36261
#[derive(Debug, PartialEq, Eq, Hash)]
262+
#[cfg_attr(feature = "driver-api", derive(Clone))]
37263
pub struct AstPathSegment<'ast> {
38-
ident: SymbolId,
264+
ident: Ident<'ast>,
39265
generics: GenericArgs<'ast>,
40266
}
41267

42268
#[cfg(feature = "driver-api")]
43269
impl<'ast> AstPathSegment<'ast> {
44-
pub fn new(ident: SymbolId, generics: GenericArgs<'ast>) -> Self {
270+
pub fn new(ident: Ident<'ast>, generics: GenericArgs<'ast>) -> Self {
45271
Self { ident, generics }
46272
}
47273
}
48274

49275
impl<'ast> AstPathSegment<'ast> {
50-
pub fn ident(&self) -> &str {
51-
with_cx(self, |cx| cx.symbol_str(self.ident))
276+
pub fn ident(&self) -> &Ident<'ast> {
277+
&self.ident
52278
}
53279

54280
pub fn generics(&self) -> &GenericArgs<'ast> {

marker_api/src/ast/common/span.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,8 @@ impl From<SpanId> for SpanOwner {
148148
}
149149
}
150150

151+
#[derive(PartialEq, Eq, Hash)]
152+
#[cfg_attr(feature = "driver-api", derive(Clone))]
151153
pub struct Ident<'ast> {
152154
_lifetime: PhantomData<&'ast ()>,
153155
sym: SymbolId,

marker_api/src/ast/expr.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,14 @@ pub use int_lit_expr::*;
1515
pub use str_lit_expr::*;
1616
// other expressions
1717
mod block_expr;
18+
mod call_exprs;
1819
mod op_exprs;
20+
mod path_expr;
1921
mod unstable_expr;
2022
pub use block_expr::*;
23+
pub use call_exprs::*;
2124
pub use op_exprs::*;
25+
pub use path_expr::*;
2226
pub use unstable_expr::*;
2327

2428
pub trait ExprData<'ast>: Debug {
@@ -50,6 +54,8 @@ pub enum ExprKind<'ast> {
5054
BinaryOp(&'ast BinaryOpExpr<'ast>),
5155
QuestionMark(&'ast QuestionMarkExpr<'ast>),
5256
As(&'ast AsExpr<'ast>),
57+
Path(&'ast PathExpr<'ast>),
58+
Call(&'ast CallExpr<'ast>),
5359
Unstable(&'ast UnstableExpr<'ast>),
5460
}
5561

@@ -70,6 +76,7 @@ pub enum ExprPrecedence {
7076
Path = 0x1300_0000,
7177

7278
Method = 0x1200_0000,
79+
Call = 0x1200_0001,
7380

7481
Field = 0x1100_0000,
7582

@@ -143,7 +150,7 @@ macro_rules! impl_expr_kind_fn {
143150
($method:ident () -> $return_ty:ty) => {
144151
impl_expr_kind_fn!($method() -> $return_ty,
145152
IntLit, FloatLit, StrLit, CharLit, BoolLit, Block, UnaryOp, Borrow,
146-
BinaryOp, QuestionMark, As, Unstable
153+
BinaryOp, QuestionMark, As, Path, Call, Unstable
147154
);
148155
};
149156
($method:ident () -> $return_ty:ty $(, $kind:ident)+) => {

marker_api/src/ast/expr/call_exprs.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
use crate::ffi::FfiSlice;
2+
3+
use super::{CommonExprData, ExprKind};
4+
5+
/// A [call expression](https://doc.rust-lang.org/reference/expressions/call-expr.html#call-expressions)
6+
/// calling a function. The called function is identified using an expression,
7+
/// called *operand*. The following shows a few examples of call expressions:
8+
/// ```
9+
/// # pub fn foo(_: u32) {}
10+
/// // vvv The operand pointing to a function called `foo`
11+
/// foo(1);
12+
/// // ^ A number literal as an argument
13+
///
14+
/// # let _: Vec<u32> =
15+
/// Vec::new();
16+
/// // ^^^^^^^^ The operand pointing to the associated function `new()` for
17+
/// // the type `Vec<_>`. This is not a method call, as the function
18+
/// // doesn't take `self` as an argument.
19+
///
20+
/// (|| "Closures are cool")();
21+
/// // ^^^^^^^^^^^^^^^^^^^^^^^^ A closure expression as an operand
22+
/// ```
23+
#[repr(C)]
24+
#[derive(Debug)]
25+
pub struct CallExpr<'ast> {
26+
data: CommonExprData<'ast>,
27+
operand: ExprKind<'ast>,
28+
args: FfiSlice<'ast, ExprKind<'ast>>,
29+
}
30+
31+
impl<'ast> CallExpr<'ast> {
32+
/// The expression identifying what will be called. This will often be a
33+
/// [`PathExpr`](super::PathExpr).
34+
pub fn operand(&self) -> ExprKind<'ast> {
35+
self.operand
36+
}
37+
38+
/// The arguments given to the operand.
39+
pub fn args(&self) -> &[ExprKind<'ast>] {
40+
self.args.get()
41+
}
42+
}
43+
44+
super::impl_expr_data!(CallExpr<'ast>, Call);
45+
46+
#[cfg(feature = "driver-api")]
47+
impl<'ast> CallExpr<'ast> {
48+
pub fn new(data: CommonExprData<'ast>, operand: ExprKind<'ast>, args: &'ast [ExprKind<'ast>]) -> Self {
49+
Self {
50+
data,
51+
operand,
52+
args: args.into(),
53+
}
54+
}
55+
}

0 commit comments

Comments
 (0)