Skip to content

Commit 0923d9a

Browse files
committed
type checking works (with some tests) for EII
1 parent d8ffcf3 commit 0923d9a

File tree

12 files changed

+302
-27
lines changed

12 files changed

+302
-27
lines changed

compiler/rustc_hir_analysis/messages.ftl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,13 @@ hir_analysis_lifetime_not_captured = `impl Trait` captures lifetime parameter, b
294294
.label = lifetime captured due to being mentioned in the bounds of the `impl Trait`
295295
.param_label = this lifetime parameter is captured
296296
297+
hir_analysis_lifetimes_or_bounds_mismatch_on_eii =
298+
lifetime parameters or bounds `{$ident}` do not match the declaration
299+
.label = lifetimes do not match
300+
.generics_label = lifetimes in impl do not match this signature
301+
.where_label = this `where` clause might not match the one in the trait
302+
.bounds_label = this bound might be missing in the implementation
303+
297304
hir_analysis_lifetimes_or_bounds_mismatch_on_trait =
298305
lifetime parameters or bounds on {$item_kind} `{$ident}` do not match the trait declaration
299306
.label = lifetimes do not match {$item_kind} in trait

compiler/rustc_hir_analysis/src/check/compare_eii.rs

Lines changed: 205 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,219 @@ use std::borrow::Cow;
22
use std::iter;
33

44
use rustc_data_structures::fx::FxIndexSet;
5-
use rustc_errors::{Applicability, E0053, E0053, struct_span_code_err, struct_span_code_err};
5+
use rustc_errors::{
6+
Applicability, Applicability, E0050, E0053, E0053, E0053, struct_span_code_err,
7+
struct_span_code_err, struct_span_code_err,
8+
};
69
use rustc_hir::def_id::{DefId, LocalDefId};
7-
use rustc_hir::{self as hir, self as hir, HirId, HirId, ItemKind, ItemKind};
10+
use rustc_hir::{
11+
self as hir, self as hir, self as hir, FnSig, HirId, HirId, HirId, ItemKind, ItemKind, ItemKind,
12+
};
813
use rustc_infer::infer::{self, InferCtxt, TyCtxtInferExt};
914
use rustc_infer::traits::{ObligationCause, ObligationCauseCode};
1015
use rustc_middle::ty;
1116
use rustc_middle::ty::TyCtxt;
12-
use rustc_middle::ty::error::{ExpectedFound, TypeError, TypeError};
17+
use rustc_middle::ty::error::{ExpectedFound, ExpectedFound, TypeError, TypeError, TypeError};
1318
use rustc_span::{ErrorGuaranteed, Ident, Span};
1419
use rustc_trait_selection::error_reporting::InferCtxtErrorExt;
1520
use rustc_trait_selection::regions::InferCtxtRegionExt;
1621
use rustc_trait_selection::traits::ObligationCtxt;
1722
use rustc_type_ir::TypingMode;
1823
use tracing::{debug, instrument};
1924

25+
use super::potentially_plural_count;
26+
use crate::errors::LifetimesOrBoundsMismatchOnEII;
27+
28+
/// Checks a bunch of different properties of the impl/trait methods for
29+
/// compatibility, such as asyncness, number of argument, self receiver kind,
30+
/// and number of early- and late-bound generics.
31+
fn check_is_structurally_compatible<'tcx>(
32+
tcx: TyCtxt<'tcx>,
33+
external_impl: LocalDefId,
34+
declaration: DefId,
35+
eii_name: Symbol,
36+
eii_attr_span: Span,
37+
) -> Result<(), ErrorGuaranteed> {
38+
// FIXME(jdonszelmann): check no generics
39+
compare_number_of_method_arguments(tcx, external_impl, declaration, eii_name, eii_attr_span)?;
40+
check_region_bounds_on_impl_item(tcx, external_impl, declaration, eii_attr_span)?;
41+
Ok(())
42+
}
43+
44+
fn check_region_bounds_on_impl_item<'tcx>(
45+
tcx: TyCtxt<'tcx>,
46+
external_impl: LocalDefId,
47+
declaration: DefId,
48+
eii_attr_span: Span,
49+
) -> Result<(), ErrorGuaranteed> {
50+
let external_impl_generics = tcx.generics_of(external_impl.to_def_id());
51+
let external_impl_params = external_impl_generics.own_counts().lifetimes;
52+
53+
let declaration_generics = tcx.generics_of(declaration);
54+
let declaration_params = declaration_generics.own_counts().lifetimes;
55+
56+
debug!(?declaration_generics, ?external_impl_generics);
57+
58+
// Must have same number of early-bound lifetime parameters.
59+
// Unfortunately, if the user screws up the bounds, then this
60+
// will change classification between early and late. E.g.,
61+
// if in trait we have `<'a,'b:'a>`, and in impl we just have
62+
// `<'a,'b>`, then we have 2 early-bound lifetime parameters
63+
// in trait but 0 in the impl. But if we report "expected 2
64+
// but found 0" it's confusing, because it looks like there
65+
// are zero. Since I don't quite know how to phrase things at
66+
// the moment, give a kind of vague error message.
67+
if declaration_params != external_impl_params {
68+
let span = tcx
69+
.hir_get_generics(external_impl)
70+
.expect("expected impl item to have generics or else we can't compare them")
71+
.span;
72+
73+
let mut generics_span = None;
74+
let mut bounds_span = vec![];
75+
let mut where_span = None;
76+
if let Some(declaration_node) = tcx.hir_get_if_local(declaration)
77+
&& let Some(declaration_generics) = declaration_node.generics()
78+
{
79+
generics_span = Some(declaration_generics.span);
80+
// FIXME: we could potentially look at the impl's bounds to not point at bounds that
81+
// *are* present in the impl.
82+
for p in declaration_generics.predicates {
83+
if let hir::WherePredicateKind::BoundPredicate(pred) = p.kind {
84+
for b in pred.bounds {
85+
if let hir::GenericBound::Outlives(lt) = b {
86+
bounds_span.push(lt.ident.span);
87+
}
88+
}
89+
}
90+
}
91+
if let Some(declaration_node) = tcx.hir_get_if_local(declaration)
92+
&& let Some(declaration_generics) = declaration_node.generics()
93+
{
94+
let mut impl_bounds = 0;
95+
for p in declaration_generics.predicates {
96+
if let hir::WherePredicateKind::BoundPredicate(pred) = p.kind {
97+
for b in pred.bounds {
98+
if let hir::GenericBound::Outlives(_) = b {
99+
impl_bounds += 1;
100+
}
101+
}
102+
}
103+
}
104+
if impl_bounds == bounds_span.len() {
105+
bounds_span = vec![];
106+
} else if declaration_generics.has_where_clause_predicates {
107+
where_span = Some(declaration_generics.where_clause_span);
108+
}
109+
}
110+
}
111+
let mut diag = tcx.dcx().create_err(LifetimesOrBoundsMismatchOnEII {
112+
span,
113+
ident: tcx.item_name(external_impl.to_def_id()),
114+
generics_span,
115+
bounds_span,
116+
where_span,
117+
});
118+
119+
diag.span_label(eii_attr_span, format!("required because of this attribute"));
120+
return Err(diag.emit());
121+
}
122+
123+
Ok(())
124+
}
125+
126+
fn compare_number_of_method_arguments<'tcx>(
127+
tcx: TyCtxt<'tcx>,
128+
external_impl: LocalDefId,
129+
declaration: DefId,
130+
eii_name: Symbol,
131+
eii_attr_span: Span,
132+
) -> Result<(), ErrorGuaranteed> {
133+
let external_impl_fty = tcx.fn_sig(external_impl);
134+
let declaration_fty = tcx.fn_sig(declaration);
135+
let declaration_number_args = declaration_fty.skip_binder().inputs().skip_binder().len();
136+
let external_impl_number_args = external_impl_fty.skip_binder().inputs().skip_binder().len();
137+
let external_impl_name = tcx.item_name(external_impl.to_def_id());
138+
139+
if declaration_number_args != external_impl_number_args {
140+
let declaration_span = declaration
141+
.as_local()
142+
.and_then(|def_id| {
143+
let declaration_sig = get_declaration_sig(tcx, def_id).expect("foreign item sig");
144+
let pos = declaration_number_args.saturating_sub(1);
145+
declaration_sig.decl.inputs.get(pos).map(|arg| {
146+
if pos == 0 {
147+
arg.span
148+
} else {
149+
arg.span.with_lo(declaration_sig.decl.inputs[0].span.lo())
150+
}
151+
})
152+
})
153+
.or_else(|| tcx.hir().span_if_local(declaration));
154+
155+
let (external_impl_sig, _, _) = &tcx.hir().expect_item(external_impl).expect_fn();
156+
let pos = external_impl_number_args.saturating_sub(1);
157+
let impl_span = external_impl_sig
158+
.decl
159+
.inputs
160+
.get(pos)
161+
.map(|arg| {
162+
if pos == 0 {
163+
arg.span
164+
} else {
165+
arg.span.with_lo(external_impl_sig.decl.inputs[0].span.lo())
166+
}
167+
})
168+
.unwrap_or_else(|| tcx.def_span(external_impl));
169+
170+
let mut err = struct_span_code_err!(
171+
tcx.dcx(),
172+
impl_span,
173+
E0050, // FIXME(jdonszelmann): new error code
174+
"`{external_impl_name}` has {} but #[{eii_name}] requires it to have {}",
175+
potentially_plural_count(external_impl_number_args, "parameter"),
176+
declaration_number_args
177+
);
178+
179+
if let Some(declaration_span) = declaration_span {
180+
err.span_label(
181+
declaration_span,
182+
format!(
183+
"requires {}",
184+
potentially_plural_count(declaration_number_args, "parameter")
185+
),
186+
);
187+
}
188+
189+
err.span_label(
190+
impl_span,
191+
format!(
192+
"expected {}, found {}",
193+
potentially_plural_count(declaration_number_args, "parameter"),
194+
external_impl_number_args
195+
),
196+
);
197+
198+
err.span_label(eii_attr_span, format!("required because of this attribute"));
199+
200+
return Err(err.emit());
201+
}
202+
203+
Ok(())
204+
}
205+
20206
// checks whether the signature of some `external_impl`, matches
21207
// the signature of `declaration`, which it is supposed to be compatible
22208
// with in order to implement the item.
23209
pub(crate) fn compare_eii_function_types<'tcx>(
24210
tcx: TyCtxt<'tcx>,
25211
external_impl: LocalDefId,
26212
declaration: DefId,
213+
eii_name: Symbol,
214+
eii_attr_span: Span,
27215
) -> Result<(), ErrorGuaranteed> {
216+
check_is_structurally_compatible(tcx, external_impl, declaration, eii_name, eii_attr_span)?;
217+
28218
let external_impl_span = tcx.def_span(external_impl);
29219
let cause = ObligationCause::new(
30220
external_impl_span,
@@ -52,6 +242,12 @@ pub(crate) fn compare_eii_function_types<'tcx>(
52242
// type.
53243

54244
let wf_tys = FxIndexSet::default();
245+
let norm_cause = ObligationCause::misc(external_impl_span, external_impl);
246+
247+
let declaration_sig = tcx.fn_sig(declaration).instantiate_identity();
248+
let declaration_sig = infcx.enter_forall_and_leak_universe(declaration_sig);
249+
let declaration_sig = ocx.normalize(&norm_cause, param_env, declaration_sig);
250+
55251
let external_impl_sig = infcx.instantiate_binder_with_fresh_vars(
56252
external_impl_span,
57253
infer::HigherRankedType,
@@ -60,16 +256,9 @@ pub(crate) fn compare_eii_function_types<'tcx>(
60256
infcx.fresh_args_for_item(external_impl_span, external_impl.to_def_id()),
61257
),
62258
);
63-
64-
let norm_cause = ObligationCause::misc(external_impl_span, external_impl);
65259
let external_impl_sig = ocx.normalize(&norm_cause, param_env, external_impl_sig);
66260
debug!(?external_impl_sig);
67261

68-
let declaration_sig = tcx.fn_sig(declaration).instantiate_identity();
69-
let declaration_sig =
70-
tcx.liberate_late_bound_regions(external_impl.to_def_id(), declaration_sig);
71-
let declaration_sig = ocx.normalize(&norm_cause, param_env, declaration_sig);
72-
73262
// FIXME: We'd want to keep more accurate spans than "the method signature" when
74263
// processing the comparison between the trait and impl fn, but we sadly lose them
75264
// and point at the whole signature when a trait bound or specific input or output
@@ -201,8 +390,7 @@ fn extract_spans_for_error_reporting<'tcx>(
201390
};
202391

203392
let declaration_args = declaration.as_local().map(|def_id| {
204-
let hir_id: HirId = tcx.local_def_id_to_hir_id(def_id);
205-
if let Some(sig) = tcx.hir_fn_sig_by_hir_id(hir_id) {
393+
if let Some(sig) = get_declaration_sig(tcx, def_id) {
206394
sig.decl.inputs.iter().map(|t| t.span).chain(iter::once(sig.decl.output.span()))
207395
} else {
208396
panic!("expected {def_id:?} to be a foreign function");
@@ -218,3 +406,8 @@ fn extract_spans_for_error_reporting<'tcx>(
218406
_ => (cause.span, tcx.hir().span_if_local(declaration), external_impl_name),
219407
}
220408
}
409+
410+
fn get_declaration_sig<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> Option<&'tcx FnSig<'tcx>> {
411+
let hir_id: HirId = tcx.local_def_id_to_hir_id(def_id);
412+
tcx.hir_fn_sig_by_hir_id(hir_id)
413+
}

compiler/rustc_hir_analysis/src/check/wfcheck.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1298,7 +1298,7 @@ fn check_item_fn(
12981298
enter_wf_checking_ctxt(tcx, span, def_id, |wfcx| {
12991299
// does the function have an EiiImpl attribute? that contains the defid of a *macro*
13001300
// that was used to mark the implementation. This is a two step process.
1301-
for EIIImpl { eii_macro, .. } in
1301+
for EIIImpl { eii_macro, span, .. } in
13021302
find_attr!(tcx.get_all_attrs(def_id), AttributeKind::EiiImpl(impls) => impls)
13031303
.into_iter()
13041304
.flatten()
@@ -1307,7 +1307,13 @@ fn check_item_fn(
13071307
// signature that we'd like to compare the function we're currently checking with
13081308
if let Some(eii_extern_item) = find_attr!(tcx.get_all_attrs(*eii_macro), AttributeKind::EiiMacroFor {eii_extern_item, ..} => *eii_extern_item)
13091309
{
1310-
let _ = compare_eii_function_types(tcx, def_id, eii_extern_item);
1310+
let _ = compare_eii_function_types(
1311+
tcx,
1312+
def_id,
1313+
eii_extern_item,
1314+
tcx.item_name(*eii_macro),
1315+
*span,
1316+
);
13111317
} else {
13121318
panic!(
13131319
"EII impl macro {eii_macro:?} did not have an eii macro for attribute pointing to a function"

compiler/rustc_hir_analysis/src/errors.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,21 @@ pub(crate) struct LifetimesOrBoundsMismatchOnTrait {
207207
pub ident: Ident,
208208
}
209209

210+
#[derive(Diagnostic)]
211+
#[diag(hir_analysis_lifetimes_or_bounds_mismatch_on_eii)]
212+
pub(crate) struct LifetimesOrBoundsMismatchOnEII {
213+
#[primary_span]
214+
#[label]
215+
pub span: Span,
216+
#[label(hir_analysis_generics_label)]
217+
pub generics_span: Option<Span>,
218+
#[label(hir_analysis_where_label)]
219+
pub where_span: Option<Span>,
220+
#[label(hir_analysis_bounds_label)]
221+
pub bounds_span: Vec<Span>,
222+
pub ident: Symbol,
223+
}
224+
210225
#[derive(Diagnostic)]
211226
#[diag(hir_analysis_drop_impl_on_wrong_item, code = E0120)]
212227
pub(crate) struct DropImplOnWrongItem {

tests/ui/eii/cross_crate_wrong_ty.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//@ compile-flags: --crate-type rlib
2+
//@ aux-build: cross_crate_eii_declaration.rs
3+
#![feature(eii)]
4+
#![feature(decl_macro)]
5+
#![feature(rustc_attrs)]
6+
#![feature(eii_internals)]
7+
8+
extern crate cross_crate_eii_declaration;
9+
10+
#[unsafe(cross_crate_eii_declaration::foo)]
11+
fn other() -> u64 {
12+
//~^ ERROR `other` has 0 parameters but #[foo] requires it to have 1
13+
0
14+
}
15+
16+
fn main() {
17+
cross_crate_eii_declaration::bar(0);
18+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
error[E0050]: `other` has 0 parameters but #[foo] requires it to have 1
2+
--> $DIR/cross_crate_wrong_ty.rs:11:1
3+
|
4+
LL | #[unsafe(cross_crate_eii_declaration::foo)]
5+
| ------------------------------------------- required because of this attribute
6+
LL | fn other() -> u64 {
7+
| ^^^^^^^^^^^^^^^^^ expected 1 parameter, found 0
8+
9+
error: aborting due to 1 previous error
10+
11+
For more information about this error, try `rustc --explain E0050`.

tests/ui/eii/subtype_1.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
//@ compile-flags: --crate-type rlib
2+
//FIXME: known ICE
23
#![feature(eii)]
34
#![feature(decl_macro)]
45
#![feature(rustc_attrs)]
@@ -16,6 +17,7 @@ unsafe extern "Rust" {
1617

1718
#[foo]
1819
fn other<'a, 'b>(x: &'b u64) -> &'b u64 {
20+
//~^ ERROR lifetime parameters or bounds `other` do not match the declaration
1921
&0
2022
}
2123

tests/ui/eii/subtype_1.stderr

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
error: lifetime parameters or bounds `other` do not match the declaration
2+
--> $DIR/subtype_1.rs:19:9
3+
|
4+
LL | safe fn bar<'a, 'b>(x: &'b u64) -> &'a u64;
5+
| -------- lifetimes in impl do not match this signature
6+
...
7+
LL | #[foo]
8+
| ------ required because of this attribute
9+
LL | fn other<'a, 'b>(x: &'b u64) -> &'b u64 {
10+
| ^^^^^^^^ lifetimes do not match
11+
12+
error: aborting due to 1 previous error
13+

0 commit comments

Comments
 (0)