Skip to content

Commit f5e0312

Browse files
committed
typecheck EII
1 parent da12aaa commit f5e0312

File tree

5 files changed

+248
-0
lines changed

5 files changed

+248
-0
lines changed
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
use std::borrow::Cow;
2+
use std::iter;
3+
4+
use rustc_data_structures::fx::FxIndexSet;
5+
use rustc_errors::{Applicability, E0053, struct_span_code_err};
6+
use rustc_hir::def_id::{DefId, LocalDefId};
7+
use rustc_hir::{self as hir, HirId, ItemKind};
8+
use rustc_infer::infer::{self, InferCtxt, TyCtxtInferExt};
9+
use rustc_infer::traits::{ObligationCause, ObligationCauseCode};
10+
use rustc_middle::ty;
11+
use rustc_middle::ty::TyCtxt;
12+
use rustc_middle::ty::error::{ExpectedFound, TypeError};
13+
use rustc_span::{ErrorGuaranteed, Ident, Span};
14+
use rustc_trait_selection::error_reporting::InferCtxtErrorExt;
15+
use rustc_trait_selection::regions::InferCtxtRegionExt;
16+
use rustc_trait_selection::traits::ObligationCtxt;
17+
use rustc_type_ir::TypingMode;
18+
use tracing::{debug, instrument};
19+
20+
// checks whether the signature of some `external_impl`, matches
21+
// the signature of `declaration`, which it is supposed to be compatible
22+
// with in order to implement the item.
23+
pub(crate) fn compare_eii_predicate_entailment<'tcx>(
24+
tcx: TyCtxt<'tcx>,
25+
external_impl: LocalDefId,
26+
declaration: DefId,
27+
) -> Result<(), ErrorGuaranteed> {
28+
let external_impl_span = tcx.def_span(external_impl);
29+
let cause = ObligationCause::new(
30+
external_impl_span,
31+
external_impl,
32+
ObligationCauseCode::CompareEII { external_impl, declaration },
33+
);
34+
35+
// no trait bounds
36+
let param_env = ty::ParamEnv::empty();
37+
38+
let infcx = &tcx.infer_ctxt().build(TypingMode::non_body_analysis());
39+
let ocx = ObligationCtxt::new_with_diagnostics(infcx);
40+
41+
// We now need to check that the signature of the impl method is
42+
// compatible with that of the trait method. We do this by
43+
// checking that `impl_fty <: trait_fty`.
44+
//
45+
// FIXME. Unfortunately, this doesn't quite work right now because
46+
// associated type normalization is not integrated into subtype
47+
// checks. For the comparison to be valid, we need to
48+
// normalize the associated types in the impl/trait methods
49+
// first. However, because function types bind regions, just
50+
// calling `FnCtxt::normalize` would have no effect on
51+
// any associated types appearing in the fn arguments or return
52+
// type.
53+
54+
let wf_tys = FxIndexSet::default();
55+
56+
let external_impl_sig = infcx.instantiate_binder_with_fresh_vars(
57+
external_impl_span,
58+
infer::HigherRankedType,
59+
tcx.fn_sig(external_impl).instantiate_identity(),
60+
);
61+
62+
let norm_cause = ObligationCause::misc(external_impl_span, external_impl);
63+
let external_impl_sig = ocx.normalize(&norm_cause, param_env, external_impl_sig);
64+
debug!(?external_impl_sig);
65+
66+
let declaration_sig = tcx.fn_sig(declaration).no_bound_vars().expect("no bound vars");
67+
let declaration_sig =
68+
tcx.liberate_late_bound_regions(external_impl.to_def_id(), declaration_sig);
69+
let declaration_sig = ocx.normalize(&norm_cause, param_env, declaration_sig);
70+
71+
// FIXME: We'd want to keep more accurate spans than "the method signature" when
72+
// processing the comparison between the trait and impl fn, but we sadly lose them
73+
// and point at the whole signature when a trait bound or specific input or output
74+
// type would be more appropriate. In other places we have a `Vec<Span>`
75+
// corresponding to their `Vec<Predicate>`, but we don't have that here.
76+
// Fixing this would improve the output of test `issue-83765.rs`.
77+
let result = ocx.sup(&cause, param_env, declaration_sig, external_impl_sig);
78+
79+
if let Err(terr) = result {
80+
debug!(?external_impl_sig, ?declaration_sig, ?terr, "sub_types failed");
81+
82+
// TODO: nice error
83+
let emitted = report_eii_mismatch(
84+
infcx,
85+
cause,
86+
param_env,
87+
terr,
88+
(declaration, declaration_sig),
89+
(external_impl, external_impl_sig),
90+
);
91+
return Err(emitted);
92+
}
93+
94+
// Check that all obligations are satisfied by the implementation's
95+
// version.
96+
let errors = ocx.select_all_or_error();
97+
if !errors.is_empty() {
98+
let reported = infcx.err_ctxt().report_fulfillment_errors(errors);
99+
return Err(reported);
100+
}
101+
102+
// Finally, resolve all regions. This catches wily misuses of
103+
// lifetime parameters.
104+
let errors = infcx.resolve_regions(external_impl, param_env, wf_tys);
105+
if !errors.is_empty() {
106+
return Err(infcx
107+
.tainted_by_errors()
108+
.unwrap_or_else(|| infcx.err_ctxt().report_region_errors(external_impl, &errors)));
109+
}
110+
111+
Ok(())
112+
}
113+
114+
fn report_eii_mismatch<'tcx>(
115+
infcx: &InferCtxt<'tcx>,
116+
mut cause: ObligationCause<'tcx>,
117+
param_env: ty::ParamEnv<'tcx>,
118+
terr: TypeError<'tcx>,
119+
(declaration_did, declaration_sig): (DefId, ty::FnSig<'tcx>),
120+
(external_impl_did, external_impl_sig): (LocalDefId, ty::FnSig<'tcx>),
121+
) -> ErrorGuaranteed {
122+
let tcx = infcx.tcx;
123+
let (impl_err_span, trait_err_span, external_impl_name) =
124+
extract_spans_for_error_reporting(infcx, terr, &cause, declaration_did, external_impl_did);
125+
126+
let mut diag = struct_span_code_err!(
127+
tcx.dcx(),
128+
impl_err_span,
129+
E0053, // TODO: new error code
130+
"function `{}` has a type that is incompatible with the declaration",
131+
external_impl_name
132+
);
133+
match &terr {
134+
TypeError::ArgumentMutability(i) | TypeError::ArgumentSorts(_, i) => {
135+
if declaration_sig.inputs().len() == *i {
136+
// Suggestion to change output type. We do not suggest in `async` functions
137+
// to avoid complex logic or incorrect output.
138+
if let ItemKind::Fn { sig, .. } = &tcx.hir().expect_item(external_impl_did).kind
139+
&& !sig.header.asyncness.is_async()
140+
{
141+
let msg = "change the output type to match the declaration";
142+
let ap = Applicability::MachineApplicable;
143+
match sig.decl.output {
144+
hir::FnRetTy::DefaultReturn(sp) => {
145+
let sugg = format!(" -> {}", declaration_sig.output());
146+
diag.span_suggestion_verbose(sp, msg, sugg, ap);
147+
}
148+
hir::FnRetTy::Return(hir_ty) => {
149+
let sugg = declaration_sig.output();
150+
diag.span_suggestion_verbose(hir_ty.span, msg, sugg, ap);
151+
}
152+
};
153+
};
154+
} else if let Some(trait_ty) = declaration_sig.inputs().get(*i) {
155+
diag.span_suggestion_verbose(
156+
impl_err_span,
157+
"change the parameter type to match the declaration",
158+
trait_ty,
159+
Applicability::MachineApplicable,
160+
);
161+
}
162+
}
163+
_ => {}
164+
}
165+
166+
cause.span = impl_err_span;
167+
infcx.err_ctxt().note_type_err(
168+
&mut diag,
169+
&cause,
170+
trait_err_span.map(|sp| (sp, Cow::from("type in declaration"), false)),
171+
Some(param_env.and(infer::ValuePairs::PolySigs(ExpectedFound {
172+
expected: ty::Binder::dummy(declaration_sig),
173+
found: ty::Binder::dummy(external_impl_sig),
174+
}))),
175+
terr,
176+
false,
177+
None,
178+
);
179+
180+
diag.emit()
181+
}
182+
183+
#[instrument(level = "debug", skip(infcx))]
184+
fn extract_spans_for_error_reporting<'tcx>(
185+
infcx: &infer::InferCtxt<'tcx>,
186+
terr: TypeError<'_>,
187+
cause: &ObligationCause<'tcx>,
188+
declaration: DefId,
189+
external_impl: LocalDefId,
190+
) -> (Span, Option<Span>, Ident) {
191+
let tcx = infcx.tcx;
192+
let (mut external_impl_args, external_impl_name) = {
193+
let item = tcx.hir().expect_item(external_impl);
194+
let (sig, _, _) = item.expect_fn();
195+
(
196+
sig.decl.inputs.iter().map(|t| t.span).chain(iter::once(sig.decl.output.span())),
197+
item.ident,
198+
)
199+
};
200+
201+
let declaration_args = declaration.as_local().map(|def_id| {
202+
let hir_id: HirId = tcx.local_def_id_to_hir_id(def_id);
203+
if let Some(sig) = tcx.hir_fn_sig_by_hir_id(hir_id) {
204+
sig.decl.inputs.iter().map(|t| t.span).chain(iter::once(sig.decl.output.span()))
205+
} else {
206+
panic!("expected {def_id:?} to be a foreign function");
207+
}
208+
});
209+
210+
match terr {
211+
TypeError::ArgumentMutability(i) | TypeError::ArgumentSorts(ExpectedFound { .. }, i) => (
212+
external_impl_args.nth(i).unwrap(),
213+
declaration_args.and_then(|mut args| args.nth(i)),
214+
external_impl_name,
215+
),
216+
_ => (cause.span, tcx.hir().span_if_local(declaration), external_impl_name),
217+
}
218+
}

compiler/rustc_hir_analysis/src/check/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ a type parameter).
6464

6565
pub mod always_applicable;
6666
mod check;
67+
mod compare_eii;
6768
mod compare_impl_item;
6869
mod entry;
6970
pub mod intrinsic;

compiler/rustc_hir_analysis/src/check/wfcheck.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use std::ops::{ControlFlow, Deref};
33

44
use hir::intravisit::{self, Visitor};
55
use rustc_abi::ExternAbi;
6+
use rustc_attr_parsing::{AttributeKind, find_attr};
67
use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet};
78
use rustc_errors::codes::*;
89
use rustc_errors::{Applicability, ErrorGuaranteed, pluralize, struct_span_code_err};
@@ -39,6 +40,7 @@ use rustc_trait_selection::traits::{
3940
use tracing::{debug, instrument};
4041
use {rustc_ast as ast, rustc_hir as hir};
4142

43+
use super::compare_eii::compare_eii_predicate_entailment;
4244
use crate::autoderef::Autoderef;
4345
use crate::collect::CollectItemTypesVisitor;
4446
use crate::constrained_generic_params::{Parameter, identify_constrained_generic_params};
@@ -1294,6 +1296,23 @@ fn check_item_fn(
12941296
decl: &hir::FnDecl<'_>,
12951297
) -> Result<(), ErrorGuaranteed> {
12961298
enter_wf_checking_ctxt(tcx, span, def_id, |wfcx| {
1299+
// does the function have an EiiImpl attribute? that contains the defid of a *macro*
1300+
// that was used to mark the implementation. This is a two step process.
1301+
if let Some(eii_macro) =
1302+
find_attr!(tcx.get_all_attrs(def_id), AttributeKind::EiiImpl {eii_macro} => *eii_macro)
1303+
{
1304+
// we expect this macro to have the `EiiMacroFor` attribute, that points to a function
1305+
// signature that we'd like to compare the function we're currently checking with
1306+
if let Some(eii_extern_item) = find_attr!(tcx.get_all_attrs(eii_macro), AttributeKind::EiiMacroFor {eii_extern_item} => *eii_extern_item)
1307+
{
1308+
let _ = compare_eii_predicate_entailment(tcx, def_id, eii_extern_item);
1309+
} else {
1310+
panic!(
1311+
"EII impl macro {eii_macro:?} did not have an eii macro for attribute pointing to a function"
1312+
)
1313+
}
1314+
}
1315+
12971316
let sig = tcx.fn_sig(def_id).instantiate_identity();
12981317
check_fn_or_method(wfcx, ident.span, sig, decl, def_id);
12991318
Ok(())

compiler/rustc_middle/src/traits/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,13 @@ pub enum ObligationCauseCode<'tcx> {
309309
kind: ty::AssocKind,
310310
},
311311

312+
/// Error derived when checking an impl item is compatible with
313+
/// its corresponding trait item's definition
314+
CompareEII {
315+
external_impl: LocalDefId,
316+
declaration: DefId,
317+
},
318+
312319
/// Checking that the bounds of a trait's associated type hold for a given impl
313320
CheckAssociatedTypeBounds {
314321
impl_item_def_id: LocalDefId,

compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3565,6 +3565,9 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
35653565
}
35663566
err.span_note(assoc_span, msg);
35673567
}
3568+
ObligationCauseCode::CompareEII { .. } => {
3569+
panic!("trait bounds on EII not yet supported ")
3570+
}
35683571
ObligationCauseCode::TrivialBound => {
35693572
err.help("see issue #48214");
35703573
tcx.disabled_nightly_features(

0 commit comments

Comments
 (0)