Skip to content

Commit

Permalink
Rollup merge of #109136 - compiler-errors:simplify-proc-macro-checkin…
Browse files Browse the repository at this point in the history
…g, r=oli-obk

Simplify proc macro signature validity check

Use an `ObligationCtxt` instead of `normalize_erasing_regions` + `DeepRejectCtxt`. This should both give us a more accurate error message, and also avoid issues like not-well-formed proc macro signatures. Also, let's fall back on the regular type mismatch error reporting for making these diagnostic notes, instead of hard-coding a bunch of specific diagnostics.

Fixes #109129
  • Loading branch information
Dylan-DPC authored Mar 15, 2023
2 parents d133c36 + 00dc3b2 commit 8c5ea61
Show file tree
Hide file tree
Showing 17 changed files with 212 additions and 275 deletions.
1 change: 1 addition & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5139,6 +5139,7 @@ dependencies = [
"rustc_session",
"rustc_span",
"rustc_target",
"rustc_trait_selection",
"tracing",
]

Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_passes/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ rustc_span = { path = "../rustc_span" }
rustc_lexer = { path = "../rustc_lexer" }
rustc_ast_pretty = { path = "../rustc_ast_pretty" }
rustc_feature = { path = "../rustc_feature" }
rustc_trait_selection = { path = "../rustc_trait_selection" }
21 changes: 1 addition & 20 deletions compiler/rustc_passes/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -720,26 +720,7 @@ passes_ignored_derived_impls =
*[other] traits {$trait_list}, but these are
} intentionally ignored during dead code analysis
passes_proc_macro_typeerror = mismatched {$kind} signature
.label = found {$found}, expected type `proc_macro::TokenStream`
.note = {$kind}s must have a signature of `{$expected_signature}`
passes_proc_macro_diff_arg_count = mismatched {$kind} signature
.label = found unexpected {$count ->
[one] argument
*[other] arguments
}
.note = {$kind}s must have a signature of `{$expected_signature}`
passes_proc_macro_missing_args = mismatched {$kind} signature
.label = {$kind} must have {$expected_input_count ->
[one] one argument
*[other] two arguments
} of type `proc_macro::TokenStream`
passes_proc_macro_invalid_abi = proc macro functions may not be `extern "{$abi}"`
passes_proc_macro_unsafe = proc macro functions may not be `unsafe`
passes_proc_macro_bad_sig = {$kind} has incorrect signature
passes_skipping_const_checks = skipping const checks
Expand Down
173 changes: 88 additions & 85 deletions compiler/rustc_passes/src/check_attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ use rustc_hir::{
use rustc_hir::{MethodKind, Target, Unsafety};
use rustc_middle::hir::nested_filter;
use rustc_middle::middle::resolve_bound_vars::ObjectLifetimeDefault;
use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams};
use rustc_middle::traits::ObligationCause;
use rustc_middle::ty::error::{ExpectedFound, TypeError};
use rustc_middle::ty::query::Providers;
use rustc_middle::ty::{ParamEnv, TyCtxt};
use rustc_middle::ty::{self, TyCtxt};
use rustc_session::lint::builtin::{
CONFLICTING_REPR_HINTS, INVALID_DOC_ATTRIBUTES, INVALID_MACRO_EXPORT_ARGUMENTS,
UNUSED_ATTRIBUTES,
Expand All @@ -30,6 +31,9 @@ use rustc_session::parse::feature_err;
use rustc_span::symbol::{kw, sym, Symbol};
use rustc_span::{Span, DUMMY_SP};
use rustc_target::spec::abi::Abi;
use rustc_trait_selection::infer::{TyCtxtInferExt, ValuePairs};
use rustc_trait_selection::traits::error_reporting::TypeErrCtxtExt;
use rustc_trait_selection::traits::ObligationCtxt;
use std::cell::Cell;
use std::collections::hash_map::Entry;

Expand Down Expand Up @@ -2188,100 +2192,99 @@ impl CheckAttrVisitor<'_> {
///
/// If this best effort goes wrong, it will just emit a worse error later (see #102923)
fn check_proc_macro(&self, hir_id: HirId, target: Target, kind: ProcMacroKind) {
let expected_input_count = match kind {
ProcMacroKind::Attribute => 2,
ProcMacroKind::Derive | ProcMacroKind::FunctionLike => 1,
};

let expected_signature = match kind {
ProcMacroKind::Attribute => "fn(TokenStream, TokenStream) -> TokenStream",
ProcMacroKind::Derive | ProcMacroKind::FunctionLike => "fn(TokenStream) -> TokenStream",
};
if target != Target::Fn {
return;
}

let tcx = self.tcx;
if target == Target::Fn {
let Some(tokenstream) = tcx.get_diagnostic_item(sym::TokenStream) else {return};
let tokenstream = tcx.type_of(tokenstream).subst_identity();

let id = hir_id.expect_owner();
let hir_sig = tcx.hir().fn_sig_by_hir_id(hir_id).unwrap();

let sig =
tcx.liberate_late_bound_regions(id.to_def_id(), tcx.fn_sig(id).subst_identity());
let sig = tcx.normalize_erasing_regions(ParamEnv::empty(), sig);

// We don't currently require that the function signature is equal to
// `fn(TokenStream) -> TokenStream`, but instead monomorphizes to
// `fn(TokenStream) -> TokenStream` after some substitution of generic arguments.
//
// Properly checking this means pulling in additional `rustc` crates, so we don't.
let drcx = DeepRejectCtxt { treat_obligation_params: TreatParams::AsCandidateKey };

if sig.abi != Abi::Rust {
tcx.sess.emit_err(errors::ProcMacroInvalidAbi {
span: hir_sig.span,
abi: sig.abi.name(),
});
self.abort.set(true);
}
let Some(token_stream_def_id) = tcx.get_diagnostic_item(sym::TokenStream) else { return; };
let Some(token_stream) = tcx.type_of(token_stream_def_id).no_bound_vars() else { return; };

if sig.unsafety == Unsafety::Unsafe {
tcx.sess.emit_err(errors::ProcMacroUnsafe { span: hir_sig.span });
self.abort.set(true);
}
let def_id = hir_id.expect_owner().def_id;
let param_env = ty::ParamEnv::empty();

let output = sig.output();
let infcx = tcx.infer_ctxt().build();
let ocx = ObligationCtxt::new(&infcx);

// Typecheck the output
if !drcx.types_may_unify(output, tokenstream) {
tcx.sess.emit_err(errors::ProcMacroTypeError {
span: hir_sig.decl.output.span(),
found: output,
kind,
expected_signature,
});
self.abort.set(true);
}
let span = tcx.def_span(def_id);
let fresh_substs = infcx.fresh_substs_for_item(span, def_id.to_def_id());
let sig = tcx.liberate_late_bound_regions(
def_id.to_def_id(),
tcx.fn_sig(def_id).subst(tcx, fresh_substs),
);

if sig.inputs().len() < expected_input_count {
tcx.sess.emit_err(errors::ProcMacroMissingArguments {
expected_input_count,
span: hir_sig.span,
kind,
expected_signature,
});
self.abort.set(true);
}
let mut cause = ObligationCause::misc(span, def_id);
let sig = ocx.normalize(&cause, param_env, sig);

// Check that the inputs are correct, if there are enough.
if sig.inputs().len() >= expected_input_count {
for (arg, input) in
sig.inputs().iter().zip(hir_sig.decl.inputs).take(expected_input_count)
{
if !drcx.types_may_unify(*arg, tokenstream) {
tcx.sess.emit_err(errors::ProcMacroTypeError {
span: input.span,
found: *arg,
kind,
expected_signature,
});
self.abort.set(true);
// proc macro is not WF.
let errors = ocx.select_where_possible();
if !errors.is_empty() {
return;
}

let expected_sig = tcx.mk_fn_sig(
std::iter::repeat(token_stream).take(match kind {
ProcMacroKind::Attribute => 2,
ProcMacroKind::Derive | ProcMacroKind::FunctionLike => 1,
}),
token_stream,
false,
Unsafety::Normal,
Abi::Rust,
);

if let Err(terr) = ocx.eq(&cause, param_env, expected_sig, sig) {
let mut diag = tcx.sess.create_err(errors::ProcMacroBadSig { span, kind });

let hir_sig = tcx.hir().fn_sig_by_hir_id(hir_id);
if let Some(hir_sig) = hir_sig {
match terr {
TypeError::ArgumentMutability(idx) | TypeError::ArgumentSorts(_, idx) => {
if let Some(ty) = hir_sig.decl.inputs.get(idx) {
diag.set_span(ty.span);
cause.span = ty.span;
} else if idx == hir_sig.decl.inputs.len() {
let span = hir_sig.decl.output.span();
diag.set_span(span);
cause.span = span;
}
}
TypeError::ArgCount => {
if let Some(ty) = hir_sig.decl.inputs.get(expected_sig.inputs().len()) {
diag.set_span(ty.span);
cause.span = ty.span;
}
}
TypeError::UnsafetyMismatch(_) => {
// FIXME: Would be nice if we had a span here..
}
TypeError::AbiMismatch(_) => {
// FIXME: Would be nice if we had a span here..
}
TypeError::VariadicMismatch(_) => {
// FIXME: Would be nice if we had a span here..
}
_ => {}
}
}

// Check that there are not too many arguments
let body_id = tcx.hir().body_owned_by(id.def_id);
let excess = tcx.hir().body(body_id).params.get(expected_input_count..);
if let Some(excess @ [begin @ end] | excess @ [begin, .., end]) = excess {
tcx.sess.emit_err(errors::ProcMacroDiffArguments {
span: begin.span.to(end.span),
count: excess.len(),
kind,
expected_signature,
});
self.abort.set(true);
}
infcx.err_ctxt().note_type_err(
&mut diag,
&cause,
None,
Some(ValuePairs::Sigs(ExpectedFound { expected: expected_sig, found: sig })),
terr,
false,
false,
);
diag.emit();
self.abort.set(true);
}

let errors = ocx.select_all_or_error();
if !errors.is_empty() {
infcx.err_ctxt().report_fulfillment_errors(&errors);
self.abort.set(true);
}
}
}
Expand Down
45 changes: 2 additions & 43 deletions compiler/rustc_passes/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1546,52 +1546,11 @@ pub struct ChangeFieldsToBeOfUnitType {
}

#[derive(Diagnostic)]
#[diag(passes_proc_macro_typeerror)]
#[note]
pub(crate) struct ProcMacroTypeError<'tcx> {
#[primary_span]
#[label]
pub span: Span,
pub found: Ty<'tcx>,
pub kind: ProcMacroKind,
pub expected_signature: &'static str,
}

#[derive(Diagnostic)]
#[diag(passes_proc_macro_diff_arg_count)]
pub(crate) struct ProcMacroDiffArguments {
#[primary_span]
#[label]
pub span: Span,
pub count: usize,
pub kind: ProcMacroKind,
pub expected_signature: &'static str,
}

#[derive(Diagnostic)]
#[diag(passes_proc_macro_missing_args)]
pub(crate) struct ProcMacroMissingArguments {
#[diag(passes_proc_macro_bad_sig)]
pub(crate) struct ProcMacroBadSig {
#[primary_span]
#[label]
pub span: Span,
pub expected_input_count: usize,
pub kind: ProcMacroKind,
pub expected_signature: &'static str,
}

#[derive(Diagnostic)]
#[diag(passes_proc_macro_invalid_abi)]
pub(crate) struct ProcMacroInvalidAbi {
#[primary_span]
pub span: Span,
pub abi: &'static str,
}

#[derive(Diagnostic)]
#[diag(passes_proc_macro_unsafe)]
pub(crate) struct ProcMacroUnsafe {
#[primary_span]
pub span: Span,
}

#[derive(Diagnostic)]
Expand Down
15 changes: 15 additions & 0 deletions tests/ui/proc-macro/bad-projection.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// force-host
// no-prefer-dynamic

#![crate_type = "proc-macro"]
#![allow(warnings)]

extern crate proc_macro;

trait Project {
type Assoc;
}

#[proc_macro]
pub fn uwu() -> <() as Project>::Assoc {}
//~^ ERROR the trait bound `(): Project` is not satisfied
9 changes: 9 additions & 0 deletions tests/ui/proc-macro/bad-projection.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
error[E0277]: the trait bound `(): Project` is not satisfied
--> $DIR/bad-projection.rs:14:17
|
LL | pub fn uwu() -> <() as Project>::Assoc {}
| ^^^^^^^^^^^^^^^^^^^^^^ the trait `Project` is not implemented for `()`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0277`.
6 changes: 3 additions & 3 deletions tests/ui/proc-macro/proc-macro-abi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,19 @@ use proc_macro::TokenStream;

#[proc_macro]
pub extern "C" fn abi(a: TokenStream) -> TokenStream {
//~^ ERROR proc macro functions may not be `extern "C"`
//~^ ERROR function-like proc macro has incorrect signature
a
}

#[proc_macro]
pub extern "system" fn abi2(a: TokenStream) -> TokenStream {
//~^ ERROR proc macro functions may not be `extern "system"`
//~^ ERROR function-like proc macro has incorrect signature
a
}

#[proc_macro]
pub extern fn abi3(a: TokenStream) -> TokenStream {
//~^ ERROR proc macro functions may not be `extern "C"`
//~^ ERROR function-like proc macro has incorrect signature
a
}

Expand Down
21 changes: 15 additions & 6 deletions tests/ui/proc-macro/proc-macro-abi.stderr
Original file line number Diff line number Diff line change
@@ -1,20 +1,29 @@
error: proc macro functions may not be `extern "C"`
error: function-like proc macro has incorrect signature
--> $DIR/proc-macro-abi.rs:11:1
|
LL | pub extern "C" fn abi(a: TokenStream) -> TokenStream {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected "Rust" fn, found "C" fn
|
= note: expected signature `fn(proc_macro::TokenStream) -> proc_macro::TokenStream`
found signature `extern "C" fn(proc_macro::TokenStream) -> proc_macro::TokenStream`

error: proc macro functions may not be `extern "system"`
error: function-like proc macro has incorrect signature
--> $DIR/proc-macro-abi.rs:17:1
|
LL | pub extern "system" fn abi2(a: TokenStream) -> TokenStream {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected "Rust" fn, found "system" fn
|
= note: expected signature `fn(proc_macro::TokenStream) -> proc_macro::TokenStream`
found signature `extern "system" fn(proc_macro::TokenStream) -> proc_macro::TokenStream`

error: proc macro functions may not be `extern "C"`
error: function-like proc macro has incorrect signature
--> $DIR/proc-macro-abi.rs:23:1
|
LL | pub extern fn abi3(a: TokenStream) -> TokenStream {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected "Rust" fn, found "C" fn
|
= note: expected signature `fn(proc_macro::TokenStream) -> proc_macro::TokenStream`
found signature `extern "C" fn(proc_macro::TokenStream) -> proc_macro::TokenStream`

error: aborting due to 3 previous errors

Loading

0 comments on commit 8c5ea61

Please sign in to comment.