Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide suggestion for const argument parsed as trait object #72273

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions src/librustc_ast_lowering/diagnostics.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
use crate::LoweringContext;
use rustc_ast::ast::{
AttrVec, Expr, ExprKind, GenericBound, PolyTraitRef, TraitBoundModifier, TraitObjectSyntax,
TraitRef, Ty, TyKind,
};
use rustc_ast::ptr::P;
use rustc_errors::Applicability;
use rustc_hir::def::Namespace;
use rustc_hir::definitions::DefPathData;
use rustc_hir::{AnonConst, ConstArg, GenericArg};
use rustc_span::hygiene::ExpnId;

impl<'a, 'hir> LoweringContext<'a, 'hir> {
/// Possible `a + b` expression that should be surrounded in braces but was parsed
/// as trait bounds in a trait object. Suggest surrounding with braces.
crate fn detect_const_expr_as_trait_object(&mut self, ty: &P<Ty>) -> Option<GenericArg<'hir>> {
if let TyKind::TraitObject(ref bounds, TraitObjectSyntax::None) = ty.kind {
// We cannot disambiguate multi-segment paths right now as that requires type
// checking.
let const_expr_without_braces = bounds.iter().all(|bound| match bound {
GenericBound::Trait(
PolyTraitRef { bound_generic_params, trait_ref: TraitRef { path, .. }, .. },
TraitBoundModifier::None,
) if bound_generic_params.is_empty()
&& path.segments.len() == 1
&& path.segments[0].args.is_none() =>
{
let part_res = self.resolver.get_partial_res(path.segments[0].id);
match part_res.map(|r| r.base_res()) {
Some(res) => {
!res.matches_ns(Namespace::TypeNS) && res.matches_ns(Namespace::ValueNS)
}
None => true,
}
}
_ => false,
});
if const_expr_without_braces {
self.sess.struct_span_err(ty.span, "likely `const` expression parsed as trait bounds")
.span_label(ty.span, "parsed as trait bounds but traits weren't found")
.multipart_suggestion(
"if you meant to write a `const` expression, surround the expression with braces",
vec![
(ty.span.shrink_to_lo(), "{ ".to_string()),
(ty.span.shrink_to_hi(), " }".to_string()),
],
Applicability::MachineApplicable,
)
.emit();

let parent_def_id = self.current_hir_id_owner.last().unwrap().0;
let node_id = self.resolver.next_node_id();
// Add a definition for the in-band const def.
self.resolver.definitions().create_def_with_parent(
parent_def_id,
node_id,
DefPathData::AnonConst,
ExpnId::root(),
ty.span,
);

let path_expr =
Expr { id: ty.id, kind: ExprKind::Err, span: ty.span, attrs: AttrVec::new() };
let value = self.with_new_scopes(|this| AnonConst {
hir_id: this.lower_node_id(node_id),
body: this.lower_const_body(path_expr.span, Some(&path_expr)),
});
return Some(GenericArg::Const(ConstArg { value, span: ty.span }));
}
}
None
}
}
7 changes: 7 additions & 0 deletions src/librustc_ast_lowering/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ macro_rules! arena_vec {
});
}

mod diagnostics;
mod expr;
mod item;
mod pat;
Expand Down Expand Up @@ -1136,6 +1137,12 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
}
}
}
if let Some(arg) = self.detect_const_expr_as_trait_object(ty) {
// Possible `a + b` expression that should be surrounded in braces but was
// parsed as trait bounds in a trait object. Suggest surrounding with braces
// and recover by returning err expression const argument.
return arg;
}
GenericArg::Type(self.lower_ty_direct(&ty, itctx))
}
ast::GenericArg::Const(ct) => GenericArg::Const(ConstArg {
Expand Down
1 change: 1 addition & 0 deletions src/librustc_middle/ty/sty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2268,6 +2268,7 @@ impl<'tcx> Const<'tcx> {
let name = tcx.hir().name(hir_id);
ty::ConstKind::Param(ty::ParamConst::new(index, name))
}
ExprKind::Err => ty::ConstKind::Error,
_ => ty::ConstKind::Unevaluated(
def_id.to_def_id(),
InternalSubsts::identity_for_item(tcx, def_id.to_def_id()),
Expand Down
67 changes: 48 additions & 19 deletions src/librustc_resolve/late.rs
Original file line number Diff line number Diff line change
Expand Up @@ -558,25 +558,22 @@ impl<'a, 'ast> Visitor<'ast> for LateResolutionVisitor<'a, '_, 'ast> {
let prev = replace(&mut self.diagnostic_metadata.currently_processing_generics, true);
match arg {
GenericArg::Type(ref ty) => {
// We parse const arguments as path types as we cannot distinguish them during
// parsing. We try to resolve that ambiguity by attempting resolution the type
// namespace first, and if that fails we try again in the value namespace. If
// resolution in the value namespace succeeds, we have an generic const argument on
// our hands.
if let TyKind::Path(ref qself, ref path) = ty.kind {
// We cannot disambiguate multi-segment paths right now as that requires type
// checking.
if path.segments.len() == 1 && path.segments[0].args.is_none() {
let mut check_ns = |ns| {
self.resolve_ident_in_lexical_scope(
path.segments[0].ident,
ns,
None,
path.span,
)
.is_some()
};
if !check_ns(TypeNS) && check_ns(ValueNS) {
let mut check_ns = |path: &Path, ns| {
self.resolve_ident_in_lexical_scope(path.segments[0].ident, ns, None, path.span)
.is_some()
&& path.segments.len() == 1
&& path.segments[0].args.is_none()
};
match ty.kind {
// We parse const arguments as path types as we cannot distinguish them during
// parsing. We try to resolve that ambiguity by attempting resolution the type
// namespace first, and if that fails we try again in the value namespace. If
// resolution in the value namespace succeeds, we have an generic const argument
// on our hands.
TyKind::Path(ref qself, ref path) => {
// We cannot disambiguate multi-segment paths right now as that requires type
// checking.
if !check_ns(path, TypeNS) && check_ns(path, ValueNS) {
// This must be equivalent to `visit_anon_const`, but we cannot call it
// directly due to visitor lifetimes so we have to copy-paste some code.
self.with_constant_rib(|this| {
Expand All @@ -597,6 +594,38 @@ impl<'a, 'ast> Visitor<'ast> for LateResolutionVisitor<'a, '_, 'ast> {
return;
}
}

// Possible `a + b` expression that should be surrounded in braces but was
// parsed as trait bounds in a trait object. Suggest surrounding with braces.
TyKind::TraitObject(ref bounds, TraitObjectSyntax::None) => {
// We cannot disambiguate multi-segment paths right now as that requires
// type checking.
let const_expr_without_braces = bounds.iter().all(|bound| match bound {
GenericBound::Trait(
PolyTraitRef {
bound_generic_params,
trait_ref: TraitRef { path, .. },
..
},
TraitBoundModifier::None,
) if bound_generic_params.is_empty() => {
!check_ns(path, TypeNS) && check_ns(path, ValueNS)
}
_ => false,
});
if const_expr_without_braces {
// This will be handled and emit an appropriate error in
// `rustc_ast_lowering::LoweringContext::lower_generic_arg`. We do not
// `visit_ty` in this case to avoid extra unnecessary output.
self.r.session.delay_span_bug(
ty.span,
"`const` expression parsed as trait bounds",
);
self.diagnostic_metadata.currently_processing_generics = prev;
return;
}
}
_ => {}
}

self.visit_ty(ty);
Expand Down
22 changes: 22 additions & 0 deletions src/test/ui/const-generics/const-expression-missing-braces.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#![allow(incomplete_features)]
#![feature(const_generics)]

fn foo<const C: usize>() {}

fn a() {
let bar = 3;
foo::<bar + 3>();
//~^ ERROR expected one of `!`, `(`, `,`, `>`, `?`, `for`, lifetime, or path, found `3`
}
fn b() {
let bar = 3;
foo::<bar + bar>();
//~^ ERROR likely `const` expression parsed as trait bounds
}
fn c() {
let bar = 3;
foo::<3 + 3>();
//~^ ERROR expected one of `,` or `>`, found `+`
}

fn main() {}
25 changes: 25 additions & 0 deletions src/test/ui/const-generics/const-expression-missing-braces.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
error: expected one of `!`, `(`, `,`, `>`, `?`, `for`, lifetime, or path, found `3`
--> $DIR/const-expression-missing-braces.rs:8:17
|
LL | foo::<bar + 3>();
| ^ expected one of 8 possible tokens

error: expected one of `,` or `>`, found `+`
--> $DIR/const-expression-missing-braces.rs:18:13
|
LL | foo::<3 + 3>();
| ^ expected one of `,` or `>`

error: likely `const` expression parsed as trait bounds
--> $DIR/const-expression-missing-braces.rs:13:11
|
LL | foo::<bar + bar>();
| ^^^^^^^^^ parsed as trait bounds but traits weren't found
|
help: if you meant to write a `const` expression, surround the expression with braces
|
LL | foo::<{ bar + bar }>();
| ^ ^

error: aborting due to 3 previous errors