Skip to content

Commit 9cc40b1

Browse files
bors[bot]xFrednet
andauthored
Merge #46
46: Rustc syntactic types, global context and some docs r=xFrednet a=xFrednet Follow-up of #45. This adds the rustc backend. I've also adapted the type representation a bit. As always, happy about reviews, but I'll merge it without, if there has been no interaction by the next step 🙃 closes #44 closes #40 CC: #35 Co-authored-by: xFrednet <[email protected]>
2 parents ce1eb57 + 507d2a9 commit 9cc40b1

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+1982
-833
lines changed

.github/workflows/rust.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ jobs:
3838
- uses: actions/checkout@v3
3939
- uses: actions-rs/toolchain@v1
4040
with:
41-
toolchain: nightly-2022-09-22
41+
toolchain: nightly-2022-11-03
4242
components: cargo, clippy, rustfmt
4343
- run: rustc -vV
4444
- run: cargo build

.github/workflows/rust_bors.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ jobs:
3333
- uses: actions/checkout@v3
3434
- uses: actions-rs/toolchain@v1
3535
with:
36-
toolchain: nightly-2022-09-22
36+
toolchain: nightly-2022-11-03
3737
components: cargo, clippy, rustfmt
3838
- run: rustc -vV
3939
- run: cargo build

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cargo-linter/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ fn main() {
4343
let matches = get_clap_config().get_matches_from(std::env::args().take_while(|s| s != CARGO_ARGS_SEPARATOR));
4444
if matches.is_present("version") {
4545
let version_info = env!("CARGO_PKG_VERSION");
46-
println!("{}", version_info);
46+
println!("{version_info}");
4747
exit(0);
4848
}
4949

docs/internal/driver-info.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Driver information
2+
3+
This document contains information required to implement linter drivers.
4+
5+
## Lifetimes
6+
7+
### Models
8+
9+
Lint-crates officially only know the `'ast` lifetime. All non-copy items given to lint-crates have this lifetime. For a lint crate, it feels like the `'ast` lifetime start with the call of a `check_` function and also ends with it. Drivers layer their implementation on top of this view.
10+
11+
Rustc's linter driver uses roughly the following lifetime model:
12+
13+
```
14+
'tcx by Rustc: |------------------------------------------|
15+
'ast by Driver: |------------------------------------|
16+
Lint-crates: |------------------------------|
17+
LintPass functions: |--------| |----| |----|
18+
```
19+
20+
The lint-crates are loaded after the entire AST has been mapped and will only be dropped after they're unloaded. This makes `'ast` basically equivalent to `'static` in this model. However, this API is not only intended for compilers like rustc, but also potentially language servers. These have different requirements as the AST has to change. This means nodes have to be dropped and rechecked again. The following showcases a model that could be used by rust-analyzer:
21+
22+
```
23+
rust-analyzer: |------------------------------------|
24+
Lint-crates: |------------------------------|
25+
'ast for AST nodes: |----------| |----------|
26+
LintPass functions: |-----| |-----|
27+
```
28+
29+
Notice that here the lint-crates life longer than the AST they're analyzing.
30+
31+
### `'static AstContext` hackery
32+
33+
AST nodes can provide a lot of information about themselves and their context. Most of this information is not stored in the node itself, but dynamically loaded using an `AstContext` instance. In the initial prototype, the `AstContext` was stored in each node. However, this added an unnecessary reference to every node, increasing their size and made the driver code more convoluted. For these reason, the implementation was changed to use a global instance that is used by all nodes.
34+
35+
This global instance is set by the adapter before any `check_*` function is called inside the lint crate. The context will be updated once for every tree given to the adapter. The current implementation imposes the following requirements:
36+
37+
1. The `AstContext` instance has to be valid for the entire time, that it takes to process the AST passed to the adapter. This means that the instance as well as the AST has to remain in memory. This behavior can implement all models as described above.
38+
2. Lint crates should always be called by the same thread and not be access concurrently. (The implementation might be able to handle it, but this is not actively tested.)
39+
3. The driver should never call functions on AST nodes that depend on the current `AstContext` instance. (In general, there should be no reason to do so.)

docs/internal/type-guidelines.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Type guidelines
2+
3+
This file contains guidelines for API types and the reasoning behind these:
4+
5+
* API types should all have `#[repr(C)]`
6+
* API enums should all have a `#[non_exhaustive]` attribute
7+
* Slices provided and stored by the API should contain objects and not references to objects:
8+
* Prefer `&[Parameter]` over `&[&Parameter]`
9+
* This reduces the number of lifetimes and helps the driver creation a bit. The borrow checker will enforce, that the items in the slice are never moved unless they're `Copy`. This also means that we should be careful which items we give `Copy`
10+

linter_adapter/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ impl<'ast> Adapter<'ast> {
2929
}
3030

3131
pub fn process_krate(&mut self, cx: &'ast AstContext<'ast>, krate: &Crate<'ast>) {
32+
self.external_lint_crates.set_ast_context(cx);
33+
3234
for item in krate.items() {
3335
self.external_lint_crates.check_item(cx, *item);
3436
}

linter_adapter/src/loader.rs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use libloading::Library;
22

3+
use linter_api::context::AstContext;
34
use linter_api::lint::Lint;
45
use linter_api::LintPass;
56

@@ -10,7 +11,7 @@ pub struct LintCrateRegistry<'ast> {
1011
passes: Vec<LoadedLintCrate<'ast>>,
1112
}
1213

13-
impl<'a> LintCrateRegistry<'a> {
14+
impl<'ast> LintCrateRegistry<'ast> {
1415
/// # Errors
1516
/// This can return errors if the library couldn't be found or if the
1617
/// required symbols weren't provided.
@@ -43,6 +44,12 @@ impl<'a> LintCrateRegistry<'a> {
4344

4445
new_self
4546
}
47+
48+
pub(super) fn set_ast_context(&self, cx: &'ast AstContext<'ast>) {
49+
for lint_pass in &self.passes {
50+
lint_pass.set_ast_context(cx);
51+
}
52+
}
4653
}
4754

4855
impl<'ast> LintPass<'ast> for LintCrateRegistry<'ast> {
@@ -82,6 +89,7 @@ macro_rules! gen_LoadedLintCrate {
8289
/// reference due to lifetime restrictions.
8390
struct LoadedLintCrate<'ast> {
8491
_lib: &'static Library,
92+
set_ast_context: libloading::Symbol<'ast, unsafe extern "C" fn(&'ast AstContext<'ast>) -> ()>,
8593
$(
8694
$fn_name: libloading::Symbol<'ast, unsafe extern "C" fn($($arg_ty,)*) -> $ret_ty>,
8795
)*
@@ -101,6 +109,11 @@ macro_rules! gen_LoadedLintCrate {
101109
return Err(LoadingError::IncompatibleVersion);
102110
}
103111

112+
let set_ast_context = unsafe {
113+
lib.get::<unsafe extern "C" fn(&'ast AstContext<'ast>) -> ()>(b"set_ast_context\0")
114+
.map_err(|_| LoadingError::MissingLintDeclaration)?
115+
};
116+
104117
$(
105118
let $fn_name = {
106119
let name: Vec<u8> = stringify!($fn_name).bytes().chain(std::iter::once(b'\0')).collect();
@@ -113,12 +126,19 @@ macro_rules! gen_LoadedLintCrate {
113126
// create Self
114127
Ok(Self {
115128
_lib: lib,
129+
set_ast_context,
116130
$(
117131
$fn_name,
118132
)*
119133
})
120134
}
121135

136+
fn set_ast_context(&self, cx: &'ast AstContext<'ast>) -> () {
137+
unsafe {
138+
(self.set_ast_context)(cx)
139+
}
140+
}
141+
122142
// safe wrapper to external functions
123143
$(
124144
fn $fn_name(& $($mut_)* self $(, $arg_name: $arg_ty)*) -> $ret_ty {

linter_api/src/ast/common.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ pub use ast_path::*;
99

1010
use std::fmt::Debug;
1111

12+
use super::generic::GenericArgs;
13+
1214
#[non_exhaustive]
1315
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
1416
pub enum Edition {
@@ -70,6 +72,7 @@ pub enum Abi {
7072
/// implementations. In general this means that the user has not selected a
7173
/// specific ABI.
7274
Default,
75+
C,
7376
/// FIXME: Remove this variant. See
7477
/// <https://doc.rust-lang.org/nightly/nightly-rustc/rustc_target/spec/abi/enum.Abi.html>
7578
Other,
@@ -117,3 +120,27 @@ pub trait Attribute<'ast>: Debug {
117120
}
118121

119122
pub trait Pattern<'ast> {}
123+
124+
#[repr(C)]
125+
#[derive(Debug, PartialEq, Eq, Hash)]
126+
pub struct TraitRef<'ast> {
127+
item_id: ItemId,
128+
generics: GenericArgs<'ast>,
129+
}
130+
131+
#[cfg(feature = "driver-api")]
132+
impl<'ast> TraitRef<'ast> {
133+
pub fn new(item_id: ItemId, generics: GenericArgs<'ast>) -> Self {
134+
Self { item_id, generics }
135+
}
136+
}
137+
138+
impl<'ast> TraitRef<'ast> {
139+
pub fn trait_id(&self) -> ItemId {
140+
self.item_id
141+
}
142+
143+
pub fn generics(&self) -> &GenericArgs<'ast> {
144+
&self.generics
145+
}
146+
}

linter_api/src/ast/common/ast_path.rs

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,48 +8,55 @@
88
use super::SymbolId;
99
use crate::{
1010
ast::generic::GenericArgs,
11-
context::AstContext,
11+
context::with_cx,
1212
ffi::{FfiOption, FfiSlice},
1313
};
1414

1515
#[repr(C)]
1616
#[derive(Debug, PartialEq, Eq, Hash)]
1717
pub struct AstPath<'ast> {
18-
_cx: &'ast AstContext<'ast>,
19-
segments: FfiSlice<'ast, &'ast AstPathSegment<'ast>>,
18+
// FIXME: Add optional target ID for values lifetimes etc that is faster to compare
19+
//
20+
// You were last trying to fix the compiler error related to lifetime identification and paths
21+
segments: FfiSlice<'ast, AstPathSegment<'ast>>,
22+
}
23+
24+
#[cfg(feature = "driver-api")]
25+
impl<'ast> AstPath<'ast> {
26+
pub fn new(segments: FfiSlice<'ast, AstPathSegment<'ast>>) -> Self {
27+
Self { segments }
28+
}
2029
}
2130

2231
impl<'ast> AstPath<'ast> {
23-
pub fn segments(&self) -> &[&AstPathSegment<'ast>] {
32+
pub fn segments(&self) -> &[AstPathSegment<'ast>] {
2433
self.segments.get()
2534
}
2635
}
2736

2837
#[repr(C)]
2938
#[derive(Debug, PartialEq, Eq, Hash)]
3039
pub struct AstPathSegment<'ast> {
31-
cx: &'ast AstContext<'ast>,
3240
ident: SymbolId,
33-
generic_args: FfiOption<&'ast GenericArgs<'ast>>,
41+
generics: FfiOption<GenericArgs<'ast>>,
3442
}
3543

3644
#[cfg(feature = "driver-api")]
3745
impl<'ast> AstPathSegment<'ast> {
38-
pub fn new(cx: &'ast AstContext<'ast>, ident: SymbolId, generic_args: Option<&'ast GenericArgs<'ast>>) -> Self {
46+
pub fn new(ident: SymbolId, generics: Option<GenericArgs<'ast>>) -> Self {
3947
Self {
40-
cx,
4148
ident,
42-
generic_args: generic_args.into(),
49+
generics: generics.into(),
4350
}
4451
}
4552
}
4653

4754
impl<'ast> AstPathSegment<'ast> {
4855
pub fn ident(&self) -> String {
49-
self.cx.symbol_str(self.ident)
56+
with_cx(self, |cx| cx.symbol_str(self.ident))
5057
}
5158

52-
pub fn generic_args(&self) -> Option<&GenericArgs<'ast>> {
53-
self.generic_args.get().copied()
59+
pub fn generics(&self) -> Option<&GenericArgs<'ast>> {
60+
self.generics.get()
5461
}
5562
}

0 commit comments

Comments
 (0)