diff --git a/src/librustc_ast_lowering/diagnostics.rs b/src/librustc_ast_lowering/diagnostics.rs new file mode 100644 index 0000000000000..69702497d962a --- /dev/null +++ b/src/librustc_ast_lowering/diagnostics.rs @@ -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) -> Option> { + 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 + } +} diff --git a/src/librustc_ast_lowering/lib.rs b/src/librustc_ast_lowering/lib.rs index 2cf81af04166c..9ba926e86bc08 100644 --- a/src/librustc_ast_lowering/lib.rs +++ b/src/librustc_ast_lowering/lib.rs @@ -78,6 +78,7 @@ macro_rules! arena_vec { }); } +mod diagnostics; mod expr; mod item; mod pat; @@ -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 { diff --git a/src/librustc_middle/ty/sty.rs b/src/librustc_middle/ty/sty.rs index 2ad673b2c1943..242bd274eabd6 100644 --- a/src/librustc_middle/ty/sty.rs +++ b/src/librustc_middle/ty/sty.rs @@ -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()), diff --git a/src/librustc_resolve/late.rs b/src/librustc_resolve/late.rs index e541920e89ed4..3a9104acce82e 100644 --- a/src/librustc_resolve/late.rs +++ b/src/librustc_resolve/late.rs @@ -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| { @@ -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); diff --git a/src/test/ui/const-generics/const-expression-missing-braces.rs b/src/test/ui/const-generics/const-expression-missing-braces.rs new file mode 100644 index 0000000000000..60cf9472d1ab0 --- /dev/null +++ b/src/test/ui/const-generics/const-expression-missing-braces.rs @@ -0,0 +1,22 @@ +#![allow(incomplete_features)] +#![feature(const_generics)] + +fn foo() {} + +fn a() { + let bar = 3; + foo::(); + //~^ ERROR expected one of `!`, `(`, `,`, `>`, `?`, `for`, lifetime, or path, found `3` +} +fn b() { + let bar = 3; + foo::(); + //~^ ERROR likely `const` expression parsed as trait bounds +} +fn c() { + let bar = 3; + foo::<3 + 3>(); + //~^ ERROR expected one of `,` or `>`, found `+` +} + +fn main() {} diff --git a/src/test/ui/const-generics/const-expression-missing-braces.stderr b/src/test/ui/const-generics/const-expression-missing-braces.stderr new file mode 100644 index 0000000000000..c1dcc865b4326 --- /dev/null +++ b/src/test/ui/const-generics/const-expression-missing-braces.stderr @@ -0,0 +1,25 @@ +error: expected one of `!`, `(`, `,`, `>`, `?`, `for`, lifetime, or path, found `3` + --> $DIR/const-expression-missing-braces.rs:8:17 + | +LL | foo::(); + | ^ 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::(); + | ^^^^^^^^^ 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 +