Skip to content

Commit

Permalink
Custom error for const expression parsed as trait object
Browse files Browse the repository at this point in the history
  • Loading branch information
estebank committed Apr 28, 2020
1 parent 2d57b1a commit 870a7de
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 55 deletions.
67 changes: 66 additions & 1 deletion src/librustc_ast_lowering/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ use rustc_ast_pretty::pprust;
use rustc_data_structures::captures::Captures;
use rustc_data_structures::fx::FxHashSet;
use rustc_data_structures::sync::Lrc;
use rustc_errors::struct_span_err;
use rustc_errors::{struct_span_err, Applicability};
use rustc_hir as hir;
use rustc_hir::def::{DefKind, Namespace, PartialRes, PerNS, Res};
use rustc_hir::def_id::{DefId, DefIdMap, LocalDefId, CRATE_DEF_INDEX};
Expand Down Expand Up @@ -1136,6 +1136,71 @@ 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.
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| hir::AnonConst {
hir_id: this.lower_node_id(node_id),
body: this.lower_const_body(path_expr.span, Some(&path_expr)),
});
return GenericArg::Const(ConstArg { value, span: ty.span });
}
}
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 @@ -2275,6 +2275,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
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,7 @@ fn a() {
fn b() {
let bar = 3;
foo::<bar + bar>();
//~^ ERROR expected trait, found local variable `bar`
//~| ERROR expected trait, found local variable `bar`
//~| ERROR wrong number of const arguments: expected 1, found 0
//~| ERROR wrong number of type arguments: expected 0, found 1
//~| WARNING trait objects without an explicit `dyn` are deprecated
//~^ ERROR likely `const` expression parsed as trait bounds
}
fn c() {
let bar = 3;
Expand Down
37 changes: 7 additions & 30 deletions src/test/ui/const-generics/const-expression-missing-braces.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -5,44 +5,21 @@ LL | foo::<bar + 3>();
| ^ expected one of 8 possible tokens

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

error[E0404]: expected trait, found local variable `bar`
error: likely `const` expression parsed as trait bounds
--> $DIR/const-expression-missing-braces.rs:13:11
|
LL | foo::<bar + bar>();
| ^^^ not a trait

error[E0404]: expected trait, found local variable `bar`
--> $DIR/const-expression-missing-braces.rs:13:17
| ^^^^^^^^^ parsed as trait bounds but traits weren't found
|
LL | foo::<bar + bar>();
| ^^^ not a trait

warning: trait objects without an explicit `dyn` are deprecated
--> $DIR/const-expression-missing-braces.rs:13:11
help: if you meant to write a `const` expression, surround the expression with braces
|
LL | foo::<bar + bar>();
| ^^^^^^^^^ help: use `dyn`: `dyn bar + bar`
|
= note: `#[warn(bare_trait_objects)]` on by default

error[E0107]: wrong number of const arguments: expected 1, found 0
--> $DIR/const-expression-missing-braces.rs:13:5
|
LL | foo::<bar + bar>();
| ^^^^^^^^^^^^^^^^ expected 1 const argument

error[E0107]: wrong number of type arguments: expected 0, found 1
--> $DIR/const-expression-missing-braces.rs:13:11
|
LL | foo::<bar + bar>();
| ^^^^^^^^^ unexpected type argument
LL | foo::<{ bar + bar }>();
| ^ ^

error: aborting due to 6 previous errors; 1 warning emitted
error: aborting due to 3 previous errors

Some errors have detailed explanations: E0107, E0404.
For more information about an error, try `rustc --explain E0107`.

0 comments on commit 870a7de

Please sign in to comment.