Skip to content

New const traits syntax #139858

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

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
8 changes: 4 additions & 4 deletions compiler/rustc_ast/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3009,7 +3009,7 @@ impl BoundPolarity {
}
}

/// The constness of a trait bound.
/// The constness of a trait bound or function.
#[derive(Copy, Clone, PartialEq, Eq, Encodable, Decodable, Debug, Hash)]
#[derive(HashStable_Generic)]
pub enum BoundConstness {
Expand Down Expand Up @@ -3461,7 +3461,7 @@ pub struct FnHeader {
/// Whether this is `async`, `gen`, or nothing.
pub coroutine_kind: Option<CoroutineKind>,
/// The `const` keyword, if any
pub constness: Const,
pub constness: BoundConstness,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not a correct usage of BoundConstness, as it's not a bound, but adding another enum that has exactly the same variants seemed kinda bad, too. So I'm reusing it for now, but I can also rename it or duplicate it in a follow-up commit in this PR

/// The `extern` keyword and corresponding ABI string, if any.
pub ext: Extern,
}
Expand All @@ -3472,7 +3472,7 @@ impl FnHeader {
let Self { safety, coroutine_kind, constness, ext } = self;
matches!(safety, Safety::Unsafe(_))
|| coroutine_kind.is_some()
|| matches!(constness, Const::Yes(_))
|| !matches!(constness, BoundConstness::Never)
|| !matches!(ext, Extern::None)
}
}
Expand All @@ -3482,7 +3482,7 @@ impl Default for FnHeader {
FnHeader {
safety: Safety::Default,
coroutine_kind: None,
constness: Const::No,
constness: BoundConstness::Never,
ext: Extern::None,
}
}
Expand Down
15 changes: 10 additions & 5 deletions compiler/rustc_ast/src/mut_visit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -902,6 +902,14 @@ fn visit_constness<T: MutVisitor>(vis: &mut T, constness: &mut Const) {
}
}

// No `noop_` prefix because there isn't a corresponding method in `MutVisitor`.
fn walk_bound_constness<T: MutVisitor>(vis: &mut T, constness: &mut BoundConstness) {
match constness {
BoundConstness::Never => {}
BoundConstness::Always(span) | BoundConstness::Maybe(span) => vis.visit_span(span),
}
}

fn walk_closure_binder<T: MutVisitor>(vis: &mut T, binder: &mut ClosureBinder) {
match binder {
ClosureBinder::NotPresent => {}
Expand Down Expand Up @@ -1131,10 +1139,7 @@ fn walk_poly_trait_ref<T: MutVisitor>(vis: &mut T, p: &mut PolyTraitRef) {

fn walk_modifiers<V: MutVisitor>(vis: &mut V, m: &mut TraitBoundModifiers) {
let TraitBoundModifiers { constness, asyncness, polarity } = m;
match constness {
BoundConstness::Never => {}
BoundConstness::Always(span) | BoundConstness::Maybe(span) => vis.visit_span(span),
}
walk_bound_constness(vis, constness);
match asyncness {
BoundAsyncness::Normal => {}
BoundAsyncness::Async(span) => vis.visit_span(span),
Expand Down Expand Up @@ -1443,7 +1448,7 @@ fn walk_const_item<T: MutVisitor>(vis: &mut T, item: &mut ConstItem) {

fn walk_fn_header<T: MutVisitor>(vis: &mut T, header: &mut FnHeader) {
let FnHeader { safety, coroutine_kind, constness, ext: _ } = header;
visit_constness(vis, constness);
walk_bound_constness(vis, constness);
coroutine_kind.as_mut().map(|coroutine_kind| vis.visit_coroutine_kind(coroutine_kind));
visit_safety(vis, safety);
}
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_ast_lowering/src/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1600,7 +1600,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
hir::FnHeader {
safety,
asyncness,
constness: self.lower_constness(h.constness),
constness: self.lower_fn_header_constness(h.constness),
abi: self.lower_extern(h.ext),
}
}
Expand Down
9 changes: 9 additions & 0 deletions compiler/rustc_ast_lowering/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,15 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
pub(crate) fn dcx(&self) -> DiagCtxtHandle<'hir> {
self.tcx.dcx()
}

fn lower_fn_header_constness(&self, constness: BoundConstness) -> hir::Constness {
match constness {
BoundConstness::Never => hir::Constness::NotConst,
BoundConstness::Always(_) => hir::Constness::Const,
// FIXME(const_trait_impls): support non-const fn and `(const)` fns within the same trait
BoundConstness::Maybe(_) => hir::Constness::NotConst,
}
}
}

#[extension(trait ResolverAstLoweringExt)]
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_ast_passes/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ ast_passes_const_and_coroutine = functions cannot be both `const` and `{$corouti

ast_passes_const_bound_trait_object = const trait bounds are not allowed in trait object types

ast_passes_const_in_trait = `const fn` in traits is unstable

ast_passes_const_without_body =
free constant item without body
.suggestion = provide a definition for the constant
Expand Down
82 changes: 64 additions & 18 deletions compiler/rustc_ast_passes/src/ast_validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -253,12 +253,55 @@ impl<'a> AstValidator<'a> {
}
}

fn check_trait_fn_not_const(&self, constness: Const, parent: &TraitOrTraitImpl) {
let Const::Yes(span) = constness else {
return;
fn check_trait_fn_not_const(
&self,
constness: BoundConstness,
sig_span: Span,
parent: &TraitOrTraitImpl,
) {
let const_trait_impl = self.features.const_trait_impl();

let span = match (constness, parent) {
(BoundConstness::Never, toti) => {
// only `(const)` or `const` fn are allowed in traits or impls respectively.
// But for bootstrap purposes we allow the stage1 std and the stage0 std to be the same.
if toti.constness().is_some() && !self.features.staged_api() {
// FIXME(const_trait_impls): allow non-const fns
self.dcx()
.struct_span_err(
sig_span.shrink_to_lo(),
"non-const fn in const traits are not supported yet",
)
.with_span_suggestion(
sig_span.shrink_to_lo(),
"mark the function as const",
match toti {
TraitOrTraitImpl::Trait { .. } => "(const) ",
TraitOrTraitImpl::TraitImpl { .. } => "const ",
},
rustc_errors::Applicability::MachineApplicable,
)
.emit();
}
return;
}
// `(const) fn` in `const Trait` or `impl const Trait` is ok
(BoundConstness::Always(span), _) => span,
(
BoundConstness::Maybe(span),
TraitOrTraitImpl::Trait { constness_span: Some(_), .. }
| TraitOrTraitImpl::TraitImpl { constness: Const::Yes(_), .. },
) => {
if !const_trait_impl {
self.sess
.create_feature_err(errors::ConstInTrait { span }, sym::const_trait_impl)
.emit();
}
return;
}
(BoundConstness::Maybe(span), _) => span,
};

let const_trait_impl = self.features.const_trait_impl();
let make_impl_const_sugg = if const_trait_impl
&& let TraitOrTraitImpl::TraitImpl {
constness: Const::No,
Expand Down Expand Up @@ -500,8 +543,9 @@ impl<'a> AstValidator<'a> {
None => (),
}
match constness {
Const::Yes(span) => report_err(span, "const"),
Const::No => (),
BoundConstness::Always(span) => report_err(span, "const"),
BoundConstness::Maybe(span) => report_err(span, "~const"),
BoundConstness::Never => (),
}
match ext {
Extern::None => (),
Expand Down Expand Up @@ -538,7 +582,9 @@ impl<'a> AstValidator<'a> {
}

if let Some(header) = fk.header() {
if let Const::Yes(const_span) = header.constness {
if let BoundConstness::Always(const_span) | BoundConstness::Maybe(const_span) =
header.constness
{
let mut spans = variadic_spans.clone();
spans.push(const_span);
self.dcx().emit_err(errors::ConstAndCVariadic {
Expand Down Expand Up @@ -1348,7 +1394,7 @@ impl<'a> Visitor<'a> for AstValidator<'a> {

// Functions cannot both be `const async` or `const gen`
if let Some(&FnHeader {
constness: Const::Yes(const_span),
constness: BoundConstness::Always(const_span) | BoundConstness::Maybe(const_span),
coroutine_kind: Some(coroutine_kind),
..
}) = fk.header()
Expand Down Expand Up @@ -1399,14 +1445,14 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
});
}

let tilde_const_allowed =
matches!(fk.header(), Some(FnHeader { constness: ast::Const::Yes(_), .. }))
|| matches!(fk.ctxt(), Some(FnCtxt::Assoc(_)))
&& self
.outer_trait_or_trait_impl
.as_ref()
.and_then(TraitOrTraitImpl::constness)
.is_some();
let tilde_const_allowed = matches!(fk.header(), Some(FnHeader { constness: ast::BoundConstness::Always(_) | ast::BoundConstness::Maybe(_), .. }))
// FIXME(const_trait_impls): remove this, we don't want to allow `~const` trait bounds in non-const methods
|| matches!(fk.ctxt(), Some(FnCtxt::Assoc(_)))
&& self
.outer_trait_or_trait_impl
.as_ref()
.and_then(TraitOrTraitImpl::constness)
.is_some();

let disallowed = (!tilde_const_allowed).then(|| match fk {
FnKind::Fn(_, _, f) => TildeConstReason::Function { ident: f.ident.span },
Expand Down Expand Up @@ -1475,7 +1521,7 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
if let Some(parent) = &self.outer_trait_or_trait_impl {
self.visibility_not_permitted(&item.vis, errors::VisibilityNotPermittedNote::TraitImpl);
if let AssocItemKind::Fn(box Fn { sig, .. }) = &item.kind {
self.check_trait_fn_not_const(sig.header.constness, parent);
self.check_trait_fn_not_const(sig.header.constness, sig.span, parent);
}
}

Expand All @@ -1490,7 +1536,7 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
AssocItemKind::Fn(func)
if parent_is_const
|| ctxt == AssocCtxt::Trait
|| matches!(func.sig.header.constness, Const::Yes(_)) =>
|| !matches!(func.sig.header.constness, ast::BoundConstness::Never) =>
{
self.visit_attrs_vis_ident(&item.attrs, &item.vis, &func.ident);
let kind = FnKind::Fn(FnCtxt::Assoc(ctxt), &item.vis, &*func);
Expand Down
7 changes: 7 additions & 0 deletions compiler/rustc_ast_passes/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,13 @@ pub(crate) struct NestedLifetimes {
pub span: Span,
}

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

#[derive(Diagnostic)]
#[diag(ast_passes_optional_trait_supertrait)]
#[note]
Expand Down
10 changes: 9 additions & 1 deletion compiler/rustc_ast_pretty/src/pprust/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1981,7 +1981,7 @@ impl<'a> State<'a> {
}

fn print_fn_header_info(&mut self, header: ast::FnHeader) {
self.print_constness(header.constness);
self.print_bound_constness(header.constness);
header.coroutine_kind.map(|coroutine_kind| self.print_coroutine_kind(coroutine_kind));
self.print_safety(header.safety);

Expand Down Expand Up @@ -2015,6 +2015,14 @@ impl<'a> State<'a> {
}
}

fn print_bound_constness(&mut self, s: ast::BoundConstness) {
match s {
ast::BoundConstness::Never => {}
ast::BoundConstness::Always(_) => self.word_nbsp("const"),
ast::BoundConstness::Maybe(_) => self.word_nbsp("~const"),
}
}

fn print_is_auto(&mut self, s: ast::IsAuto) {
match s {
ast::IsAuto::Yes => self.word_nbsp("auto"),
Expand Down
13 changes: 12 additions & 1 deletion compiler/rustc_builtin_macros/src/deriving/generic/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1018,7 +1018,18 @@ impl<'a> MethodDef<'a> {

let trait_lo_sp = span.shrink_to_lo();

let sig = ast::FnSig { header: ast::FnHeader::default(), decl: fn_decl, span };
let sig = ast::FnSig {
header: ast::FnHeader {
constness: if trait_.is_const {
ast::BoundConstness::Maybe(span)
} else {
ast::BoundConstness::Never
},
..Default::default()
},
decl: fn_decl,
span,
};
let defaultness = ast::Defaultness::Final;

// Create the method.
Expand Down
42 changes: 36 additions & 6 deletions compiler/rustc_parse/src/parser/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -857,13 +857,20 @@ impl<'a> Parser<'a> {
false
}

fn is_paren_const(&self, i: usize) -> bool {
self.look_ahead(i, |t| *t == token::OpenParen)
&& self.look_ahead(1 + i, |t| t.is_keyword(kw::Const))
&& self.look_ahead(2 + i, |t| *t == token::CloseParen)
}

/// Parses defaultness (i.e., `default` or nothing).
fn parse_defaultness(&mut self) -> Defaultness {
// We are interested in `default` followed by another identifier.
// However, we must avoid keywords that occur as binary operators.
// Currently, the only applicable keyword is `as` (`default as Ty`).
if self.check_keyword(exp!(Default))
&& self.look_ahead(1, |t| t.is_non_raw_ident_where(|i| i.name != kw::As))
&& (self.look_ahead(1, |t| t.is_non_raw_ident_where(|i| i.name != kw::As))
|| self.is_paren_const(1))
{
self.bump(); // `default`
Defaultness::Default(self.prev_token_uninterpolated_span())
Expand Down Expand Up @@ -2613,6 +2620,8 @@ impl<'a> Parser<'a> {
// Rule out `async gen {` and `async gen move {`
&& !self.is_async_gen_block())
})
// `(const)`
|| self.is_paren_const(0)
// `extern ABI fn`
|| self.check_keyword_case(exp!(Extern), case)
// Use `tree_look_ahead` because `ABI` might be a metavariable,
Expand Down Expand Up @@ -2647,12 +2656,31 @@ impl<'a> Parser<'a> {
)
}

pub fn parse_fn_constness(&mut self, case: Case) -> PResult<'a, BoundConstness> {
Ok(if self.eat_keyword_case(exp!(Const), case) {
BoundConstness::Always(self.prev_token.span)
} else if self.check(exp!(OpenParen))
&& self.look_ahead(1, |t| t.is_keyword(kw::Const))
&& self.look_ahead(2, |t| *t == token::CloseParen)
{
let start = self.token.span;
self.bump();
self.expect_keyword(exp!(Const)).unwrap();
self.bump();
let span = start.to(self.prev_token.span);
self.psess.gated_spans.gate(sym::const_trait_impl, span);
BoundConstness::Maybe(span)
} else {
BoundConstness::Never
})
}

/// Parses all the "front matter" (or "qualifiers") for a `fn` declaration,
/// up to and including the `fn` keyword. The formal grammar is:
///
/// ```text
/// Extern = "extern" StringLit? ;
/// FnQual = "const"? "async"? "unsafe"? Extern? ;
/// FnQual = "(const)"? "const"? "async"? "unsafe"? Extern? ;
/// FnFrontMatter = FnQual "fn" ;
/// ```
///
Expand All @@ -2664,7 +2692,7 @@ impl<'a> Parser<'a> {
case: Case,
) -> PResult<'a, FnHeader> {
let sp_start = self.token.span;
let constness = self.parse_constness(case);
let constness = self.parse_fn_constness(case)?;

let async_start_sp = self.token.span;
let coroutine_kind = self.parse_coroutine_kind(case);
Expand Down Expand Up @@ -2713,9 +2741,11 @@ impl<'a> Parser<'a> {
// that the keyword is already present and the second instance should be removed.
let wrong_kw = if self.check_keyword(exp!(Const)) {
match constness {
Const::Yes(sp) => Some(WrongKw::Duplicated(sp)),
Const::No => {
recover_constness = Const::Yes(self.token.span);
BoundConstness::Always(sp) | BoundConstness::Maybe(sp) => {
Some(WrongKw::Duplicated(sp))
}
BoundConstness::Never => {
recover_constness = BoundConstness::Always(self.token.span);
Some(WrongKw::Misplaced(async_start_sp))
}
}
Expand Down
Loading
Loading