Introduce const Trait (always-const trait bounds)

This commit is contained in:
León Orell Valerian Liehr 2023-12-18 17:55:55 +01:00
parent 2fe50cd72c
commit 3eb48a35c8
No known key found for this signature in database
GPG key ID: D17A07215F68E713
69 changed files with 505 additions and 223 deletions

View file

@ -2481,15 +2481,6 @@ pub enum Const {
No,
}
impl From<BoundConstness> for Const {
fn from(constness: BoundConstness) -> Self {
match constness {
BoundConstness::Maybe(span) => Self::Yes(span),
BoundConstness::Never => Self::No,
}
}
}
/// Item defaultness.
/// For details see the [RFC #2532](https://github.com/rust-lang/rfcs/pull/2532).
#[derive(Copy, Clone, PartialEq, Encodable, Decodable, Debug, HashStable_Generic)]
@ -2543,6 +2534,8 @@ impl BoundPolarity {
pub enum BoundConstness {
/// `Type: Trait`
Never,
/// `Type: const Trait`
Always(Span),
/// `Type: ~const Trait`
Maybe(Span),
}
@ -2551,6 +2544,7 @@ impl BoundConstness {
pub fn as_str(self) -> &'static str {
match self {
Self::Never => "",
Self::Always(_) => "const",
Self::Maybe(_) => "~const",
}
}

View file

@ -528,15 +528,6 @@ impl Token {
}
}
/// Returns `true` if the token can appear at the start of a generic bound.
pub fn can_begin_bound(&self) -> bool {
self.is_path_start()
|| self.is_lifetime()
|| self.is_keyword(kw::For)
|| self == &Question
|| self == &OpenDelim(Delimiter::Parenthesis)
}
/// Returns `true` if the token can appear at the start of an item.
pub fn can_begin_item(&self) -> bool {
match self.kind {

View file

@ -339,9 +339,14 @@ impl<'hir> LoweringContext<'_, 'hir> {
let itctx = ImplTraitContext::Universal;
let (generics, (trait_ref, lowered_ty)) =
self.lower_generics(ast_generics, *constness, id, &itctx, |this| {
let constness = match *constness {
Const::Yes(span) => BoundConstness::Maybe(span),
Const::No => BoundConstness::Never,
};
let trait_ref = trait_ref.as_ref().map(|trait_ref| {
this.lower_trait_ref(
*constness,
constness,
trait_ref,
&ImplTraitContext::Disallowed(ImplTraitPosition::Trait),
)

View file

@ -1324,7 +1324,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
span: t.span,
},
itctx,
ast::Const::No,
ast::BoundConstness::Never,
);
let bounds = this.arena.alloc_from_iter([bound]);
let lifetime_bound = this.elided_dyn_bound(t.span);
@ -1435,7 +1435,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
polarity: BoundPolarity::Positive | BoundPolarity::Negative(_),
constness,
},
) => Some(this.lower_poly_trait_ref(ty, itctx, (*constness).into())),
) => Some(this.lower_poly_trait_ref(ty, itctx, *constness)),
// We can safely ignore constness here, since AST validation
// will take care of invalid modifier combinations.
GenericBound::Trait(
@ -2174,7 +2174,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
fn lower_trait_ref(
&mut self,
constness: ast::Const,
constness: ast::BoundConstness,
p: &TraitRef,
itctx: &ImplTraitContext,
) -> hir::TraitRef<'hir> {
@ -2197,7 +2197,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
&mut self,
p: &PolyTraitRef,
itctx: &ImplTraitContext,
constness: ast::Const,
constness: ast::BoundConstness,
) -> hir::PolyTraitRef<'hir> {
let bound_generic_params =
self.lower_lifetime_binder(p.trait_ref.ref_id, &p.bound_generic_params);
@ -2322,9 +2322,11 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
&mut self,
modifiers: TraitBoundModifiers,
) -> hir::TraitBoundModifier {
// Invalid modifier combinations will cause an error during AST validation.
// Arbitrarily pick a placeholder for them to make compilation proceed.
match (modifiers.constness, modifiers.polarity) {
(BoundConstness::Never, BoundPolarity::Positive) => hir::TraitBoundModifier::None,
(BoundConstness::Never, BoundPolarity::Maybe(_)) => hir::TraitBoundModifier::Maybe,
(_, BoundPolarity::Maybe(_)) => hir::TraitBoundModifier::Maybe,
(BoundConstness::Never, BoundPolarity::Negative(_)) => {
if self.tcx.features().negative_bounds {
hir::TraitBoundModifier::Negative
@ -2332,15 +2334,8 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
hir::TraitBoundModifier::None
}
}
(BoundConstness::Maybe(_), BoundPolarity::Positive) => {
hir::TraitBoundModifier::MaybeConst
}
// Invalid modifier combinations will cause an error during AST validation.
// Arbitrarily pick a placeholder for compilation to proceed.
(BoundConstness::Maybe(_), BoundPolarity::Maybe(_)) => hir::TraitBoundModifier::Maybe,
(BoundConstness::Maybe(_), BoundPolarity::Negative(_)) => {
hir::TraitBoundModifier::MaybeConst
}
(BoundConstness::Always(_), _) => hir::TraitBoundModifier::Const,
(BoundConstness::Maybe(_), _) => hir::TraitBoundModifier::MaybeConst,
}
}
@ -2558,45 +2553,62 @@ struct GenericArgsCtor<'hir> {
}
impl<'hir> GenericArgsCtor<'hir> {
fn push_constness(&mut self, lcx: &mut LoweringContext<'_, 'hir>, constness: ast::Const) {
fn push_constness(
&mut self,
lcx: &mut LoweringContext<'_, 'hir>,
constness: ast::BoundConstness,
) {
if !lcx.tcx.features().effects {
return;
}
// if bound is non-const, don't add host effect param
let ast::Const::Yes(span) = constness else { return };
let (span, body) = match constness {
BoundConstness::Never => return,
BoundConstness::Always(span) => {
let span = lcx.lower_span(span);
let span = lcx.lower_span(span);
let body = hir::ExprKind::Lit(
lcx.arena.alloc(hir::Lit { node: LitKind::Bool(false), span }),
);
let id = lcx.next_node_id();
let hir_id = lcx.next_id();
(span, body)
}
BoundConstness::Maybe(span) => {
let span = lcx.lower_span(span);
let Some(host_param_id) = lcx.host_param_id else {
lcx.dcx().span_delayed_bug(
span,
"no host param id for call in const yet no errors reported",
);
return;
};
let Some(host_param_id) = lcx.host_param_id else {
lcx.dcx().span_delayed_bug(
span,
"no host param id for call in const yet no errors reported",
);
return;
};
let body = lcx.lower_body(|lcx| {
(&[], {
let hir_id = lcx.next_id();
let res = Res::Def(DefKind::ConstParam, host_param_id.to_def_id());
let expr_kind = hir::ExprKind::Path(hir::QPath::Resolved(
let body = hir::ExprKind::Path(hir::QPath::Resolved(
None,
lcx.arena.alloc(hir::Path {
span,
res,
segments: arena_vec![lcx; hir::PathSegment::new(Ident {
name: sym::host,
span,
}, hir_id, res)],
segments: arena_vec![
lcx;
hir::PathSegment::new(
Ident { name: sym::host, span },
hir_id,
res
)
],
}),
));
lcx.expr(span, expr_kind)
})
});
(span, body)
}
};
let body = lcx.lower_body(|lcx| (&[], lcx.expr(span, body)));
let id = lcx.next_node_id();
let hir_id = lcx.next_id();
let def_id = lcx.create_def(
lcx.current_hir_id_owner.def_id,

View file

@ -25,7 +25,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
param_mode: ParamMode,
itctx: &ImplTraitContext,
// constness of the impl/bound if this is a trait path
constness: Option<ast::Const>,
constness: Option<ast::BoundConstness>,
) -> hir::QPath<'hir> {
let qself_position = qself.as_ref().map(|q| q.position);
let qself = qself.as_ref().map(|q| self.lower_ty(&q.ty, itctx));
@ -179,7 +179,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
param_mode: ParamMode,
parenthesized_generic_args: ParenthesizedGenericArgs,
itctx: &ImplTraitContext,
constness: Option<ast::Const>,
constness: Option<ast::BoundConstness>,
) -> hir::PathSegment<'hir> {
debug!("path_span: {:?}, lower_path_segment(segment: {:?})", path_span, segment);
let (mut generic_args, infer_args) = if let Some(generic_args) = segment.args.as_deref() {

View file

@ -46,6 +46,8 @@ ast_passes_const_and_c_variadic = functions cannot be both `const` and C-variadi
.const = `const` because of this
.variadic = C-variadic because of this
ast_passes_const_bound_trait_object = const trait bounds are not allowed in trait object types
ast_passes_const_without_body =
free constant item without body
.suggestion = provide a definition for the constant

View file

@ -1207,6 +1207,9 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
(BoundKind::TraitObject, BoundConstness::Never, BoundPolarity::Maybe(_)) => {
self.dcx().emit_err(errors::OptionalTraitObject { span: poly.span });
}
(BoundKind::TraitObject, BoundConstness::Always(_), BoundPolarity::Positive) => {
self.dcx().emit_err(errors::ConstBoundTraitObject { span: poly.span });
}
(_, BoundConstness::Maybe(span), BoundPolarity::Positive)
if let Some(reason) = &self.disallow_tilde_const =>
{
@ -1237,8 +1240,8 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
}
(
_,
BoundConstness::Maybe(_),
BoundPolarity::Maybe(_) | BoundPolarity::Negative(_),
BoundConstness::Always(_) | BoundConstness::Maybe(_),
BoundPolarity::Negative(_) | BoundPolarity::Maybe(_),
) => {
self.dcx().emit_err(errors::IncompatibleTraitBoundModifiers {
span: bound.span(),

View file

@ -540,6 +540,13 @@ pub struct OptionalTraitObject {
pub span: Span,
}
#[derive(Diagnostic)]
#[diag(ast_passes_const_bound_trait_object)]
pub struct ConstBoundTraitObject {
#[primary_span]
pub span: Span,
}
#[derive(Diagnostic)]
#[diag(ast_passes_tilde_const_disallowed)]
pub struct TildeConstDisallowed {

View file

@ -1561,7 +1561,7 @@ impl<'a> State<'a> {
GenericBound::Trait(tref, modifier) => {
match modifier.constness {
ast::BoundConstness::Never => {}
ast::BoundConstness::Maybe(_) => {
ast::BoundConstness::Always(_) | ast::BoundConstness::Maybe(_) => {
self.word_space(modifier.constness.as_str());
}
}

View file

@ -420,9 +420,15 @@ pub enum GenericArgsParentheses {
/// A modifier on a trait bound.
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, HashStable_Generic)]
pub enum TraitBoundModifier {
/// `Type: Trait`
None,
/// `Type: !Trait`
Negative,
/// `Type: ?Trait`
Maybe,
/// `Type: const Trait`
Const,
/// `Type: ~const Trait`
MaybeConst,
}

View file

@ -70,7 +70,7 @@ hir_analysis_coercion_between_struct_same_note = expected coercion between the s
hir_analysis_coercion_between_struct_single_note = expected a single field to be coerced, none found
hir_analysis_const_bound_for_non_const_trait =
~const can only be applied to `#[const_trait]` traits
`{$modifier}` can only be applied to `#[const_trait]` traits
hir_analysis_const_impl_for_non_const_trait =
const `impl` for trait `{$trait_name}` which is not marked with `#[const_trait]`

View file

@ -112,6 +112,9 @@ impl<'tcx> dyn AstConv<'tcx> + '_ {
match ast_bound {
hir::GenericBound::Trait(poly_trait_ref, modifier) => {
let (constness, polarity) = match modifier {
hir::TraitBoundModifier::Const => {
(ty::BoundConstness::Const, ty::ImplPolarity::Positive)
}
hir::TraitBoundModifier::MaybeConst => {
(ty::BoundConstness::ConstIfConst, ty::ImplPolarity::Positive)
}

View file

@ -560,11 +560,14 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
inferred_params: vec![],
infer_args,
};
if let ty::BoundConstness::ConstIfConst = constness
if let ty::BoundConstness::Const | ty::BoundConstness::ConstIfConst = constness
&& generics.has_self
&& !tcx.has_attr(def_id, sym::const_trait)
{
let e = tcx.dcx().emit_err(crate::errors::ConstBoundForNonConstTrait { span });
let e = tcx.dcx().emit_err(crate::errors::ConstBoundForNonConstTrait {
span,
modifier: constness.as_str(),
});
arg_count.correct =
Err(GenericArgCountMismatch { reported: Some(e), invalid_args: vec![] });
}

View file

@ -408,6 +408,7 @@ pub struct ConstImplForNonConstTrait {
pub struct ConstBoundForNonConstTrait {
#[primary_span]
pub span: Span,
pub modifier: &'static str,
}
#[derive(Diagnostic)]

View file

@ -309,23 +309,22 @@ impl Visibility {
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, HashStable, TyEncodable, TyDecodable)]
pub enum BoundConstness {
/// `T: Trait`
/// `Type: Trait`
NotConst,
/// `T: ~const Trait`
/// `Type: const Trait`
Const,
/// `Type: ~const Trait`
///
/// Requires resolving to const only when we are in a const context.
ConstIfConst,
}
impl BoundConstness {
/// Reduce `self` and `constness` to two possible combined states instead of four.
pub fn and(&mut self, constness: hir::Constness) -> hir::Constness {
match (constness, self) {
(hir::Constness::Const, BoundConstness::ConstIfConst) => hir::Constness::Const,
(_, this) => {
*this = BoundConstness::NotConst;
hir::Constness::NotConst
}
pub fn as_str(self) -> &'static str {
match self {
Self::NotConst => "",
Self::Const => "const",
Self::ConstIfConst => "~const",
}
}
}
@ -334,7 +333,7 @@ impl fmt::Display for BoundConstness {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NotConst => f.write_str("normal"),
Self::ConstIfConst => f.write_str("`~const`"),
_ => write!(f, "`{self}`"),
}
}
}

View file

@ -95,9 +95,6 @@ parse_compound_assignment_expression_in_let = can't reassign to an uninitialized
.suggestion = initialize the variable
.help = if you meant to overwrite, remove the `let` binding
parse_const_bounds_missing_tilde = const bounds must start with `~`
.suggestion = add `~`
parse_const_generic_without_braces = expressions must be enclosed in braces to be used as const generic arguments
.suggestion = enclose the `const` expression in braces
@ -555,8 +552,8 @@ parse_missing_trait_in_trait_impl = missing trait in a trait impl
.suggestion_add_trait = add a trait here
.suggestion_remove_for = for an inherent impl, drop this `for`
parse_modifier_lifetime = `{$sigil}` may only modify trait bounds, not lifetime bounds
.suggestion = remove the `{$sigil}`
parse_modifier_lifetime = `{$modifier}` may only modify trait bounds, not lifetime bounds
.suggestion = remove the `{$modifier}`
parse_more_than_one_char = character literal may only contain one codepoint
.followed_by = this `{$chr}` is followed by the combining {$len ->
@ -729,8 +726,6 @@ parse_switch_ref_box_order = switch the order of `ref` and `box`
parse_ternary_operator = Rust has no ternary operator
.help = use an `if-else` expression instead
parse_tilde_const_lifetime = `~const` may only modify trait bounds, not lifetime bounds
parse_tilde_is_not_unary_operator = `~` cannot be used as a unary operator
.suggestion = use `!` to perform bitwise not

View file

@ -2555,20 +2555,13 @@ pub(crate) struct AssocLifetime {
pub lifetime: Span,
}
#[derive(Diagnostic)]
#[diag(parse_tilde_const_lifetime)]
pub(crate) struct TildeConstLifetime {
#[primary_span]
pub span: Span,
}
#[derive(Diagnostic)]
#[diag(parse_modifier_lifetime)]
pub(crate) struct ModifierLifetime {
#[primary_span]
#[suggestion(style = "tool-only", applicability = "maybe-incorrect", code = "")]
pub span: Span,
pub sigil: &'static str,
pub modifier: &'static str,
}
#[derive(Diagnostic)]
@ -2581,15 +2574,6 @@ pub(crate) struct ParenthesizedLifetime {
pub snippet: String,
}
#[derive(Diagnostic)]
#[diag(parse_const_bounds_missing_tilde)]
pub(crate) struct ConstMissingTilde {
#[primary_span]
pub span: Span,
#[suggestion(code = "~", applicability = "machine-applicable")]
pub start: Span,
}
#[derive(Diagnostic)]
#[diag(parse_underscore_literal_suffix)]
pub(crate) struct UnderscoreLiteralSuffix {

View file

@ -86,6 +86,18 @@ fn can_continue_type_after_non_fn_ident(t: &Token) -> bool {
t == &token::ModSep || t == &token::Lt || t == &token::BinOp(token::Shl)
}
fn can_begin_dyn_bound_in_edition_2015(t: &Token) -> bool {
// `Not`, `Tilde` & `Const` are deliberately not part of this list to
// contain the number of potential regressions esp. in MBE code.
// `Const` would regress `rfc-2632-const-trait-impl/mbe-dyn-const-2015.rs`.
// `Not` would regress `dyn!(...)` macro calls in Rust 2015.
t.is_path_start()
|| t.is_lifetime()
|| t == &TokenKind::Question
|| t.is_keyword(kw::For)
|| t == &TokenKind::OpenDelim(Delimiter::Parenthesis)
}
impl<'a> Parser<'a> {
/// Parses a type.
pub fn parse_ty(&mut self) -> PResult<'a, P<Ty>> {
@ -665,7 +677,8 @@ impl<'a> Parser<'a> {
self.check_keyword(kw::Dyn)
&& (self.token.uninterpolated_span().at_least_rust_2018()
|| self.look_ahead(1, |t| {
(t.can_begin_bound() || t.kind == TokenKind::BinOp(token::Star))
(can_begin_dyn_bound_in_edition_2015(t)
|| t.kind == TokenKind::BinOp(token::Star))
&& !can_continue_type_after_non_fn_ident(t)
}))
}
@ -758,12 +771,12 @@ impl<'a> Parser<'a> {
/// Can the current token begin a bound?
fn can_begin_bound(&mut self) -> bool {
// This needs to be synchronized with `TokenKind::can_begin_bound`.
self.check_path()
|| self.check_lifetime()
|| self.check(&token::Not)
|| self.check(&token::Question)
|| self.check(&token::Tilde)
|| self.check_keyword(kw::Const)
|| self.check_keyword(kw::For)
|| self.check(&token::OpenDelim(Delimiter::Parenthesis))
}
@ -812,8 +825,11 @@ impl<'a> Parser<'a> {
fn error_lt_bound_with_modifiers(&self, modifiers: TraitBoundModifiers) {
match modifiers.constness {
BoundConstness::Never => {}
BoundConstness::Maybe(span) => {
self.dcx().emit_err(errors::TildeConstLifetime { span });
BoundConstness::Always(span) | BoundConstness::Maybe(span) => {
self.dcx().emit_err(errors::ModifierLifetime {
span,
modifier: modifiers.constness.as_str(),
});
}
}
@ -822,7 +838,7 @@ impl<'a> Parser<'a> {
BoundPolarity::Negative(span) | BoundPolarity::Maybe(span) => {
self.dcx().emit_err(errors::ModifierLifetime {
span,
sigil: modifiers.polarity.as_str(),
modifier: modifiers.polarity.as_str(),
});
}
}
@ -848,7 +864,7 @@ impl<'a> Parser<'a> {
/// If no modifiers are present, this does not consume any tokens.
///
/// ```ebnf
/// TRAIT_BOUND_MODIFIERS = ["~const"] ["?" | "!"]
/// TRAIT_BOUND_MODIFIERS = [["~"] "const"] ["?" | "!"]
/// ```
fn parse_trait_bound_modifiers(&mut self) -> PResult<'a, TraitBoundModifiers> {
let constness = if self.eat(&token::Tilde) {
@ -858,11 +874,8 @@ impl<'a> Parser<'a> {
self.sess.gated_spans.gate(sym::const_trait_impl, span);
BoundConstness::Maybe(span)
} else if self.eat_keyword(kw::Const) {
let span = self.prev_token.span;
self.sess.gated_spans.gate(sym::const_trait_impl, span);
self.dcx().emit_err(errors::ConstMissingTilde { span, start: span.shrink_to_lo() });
BoundConstness::Maybe(span)
self.sess.gated_spans.gate(sym::const_trait_impl, self.prev_token.span);
BoundConstness::Always(self.prev_token.span)
} else {
BoundConstness::Never
};