Skip to content

Commit 3bc767e

Browse files
committed
Auto merge of #142574 - Kobzol:rollup-ldj386u, r=Kobzol
Rollup of 12 pull requests Successful merges: - #141639 (Expose discriminant values in stable_mir) - #142082 (Refactor `rustc_attr_data_structures` documentation) - #142125 (Stabilize "file_lock" feature) - #142236 (Add documentation for `PathBuf`'s `FromIterator` and `Extend` impls) - #142373 (Fix Debug for Location) - #142416 (Assorted bootstrap cleanups (step 2)) - #142431 (Add initial version of snapshot tests to bootstrap) - #142450 (Add documentation on top of `rustc_middle/src/query/mod.rs`) - #142528 (clarify `rustc_do_not_const_check` comment) - #142530 (use `if let` guards where possible) - #142561 (Remove an `njn:` comment accidentaly left behind.) - #142566 (Fix `-nopt` CI jobs) r? `@ghost` `@rustbot` modify labels: rollup
2 parents d9ca9bd + 78d12b7 commit 3bc767e

File tree

40 files changed

+693
-203
lines changed

40 files changed

+693
-203
lines changed

compiler/rustc_attr_data_structures/src/attributes.rs

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -130,24 +130,54 @@ impl Deprecation {
130130
}
131131
}
132132

133-
/// Represent parsed, *built in*, inert attributes.
133+
/// Represents parsed *built-in* inert attributes.
134134
///
135-
/// That means attributes that are not actually ever expanded.
136-
/// For more information on this, see the module docs on the [`rustc_attr_parsing`] crate.
137-
/// They're instead used as markers, to guide the compilation process in various way in most every stage of the compiler.
138-
/// These are kept around after the AST, into the HIR and further on.
135+
/// ## Overview
136+
/// These attributes are markers that guide the compilation process and are never expanded into other code.
137+
/// They persist throughout the compilation phases, from AST to HIR and beyond.
139138
///
140-
/// The word "parsed" could be a little misleading here, because the parser already parses
141-
/// attributes early on. However, the result, an [`ast::Attribute`]
142-
/// is only parsed at a high level, still containing a token stream in many cases. That is
143-
/// because the structure of the contents varies from attribute to attribute.
144-
/// With a parsed attribute I mean that each attribute is processed individually into a
145-
/// final structure, which on-site (the place where the attribute is useful for, think the
146-
/// the place where `must_use` is checked) little to no extra parsing or validating needs to
147-
/// happen.
139+
/// ## Attribute Processing
140+
/// While attributes are initially parsed by [`rustc_parse`] into [`ast::Attribute`], they still contain raw token streams
141+
/// because different attributes have different internal structures. This enum represents the final,
142+
/// fully parsed form of these attributes, where each variant contains contains all the information and
143+
/// structure relevant for the specific attribute.
148144
///
149-
/// For more docs, look in [`rustc_attr_parsing`].
145+
/// Some attributes can be applied multiple times to the same item, and they are "collapsed" into a single
146+
/// semantic attribute. For example:
147+
/// ```rust
148+
/// #[repr(C)]
149+
/// #[repr(packed)]
150+
/// struct S { }
151+
/// ```
152+
/// This is equivalent to `#[repr(C, packed)]` and results in a single [`AttributeKind::Repr`] containing
153+
/// both `C` and `packed` annotations. This collapsing happens during parsing and is reflected in the
154+
/// data structures defined in this enum.
150155
///
156+
/// ## Usage
157+
/// These parsed attributes are used throughout the compiler to:
158+
/// - Control code generation (e.g., `#[repr]`)
159+
/// - Mark API stability (`#[stable]`, `#[unstable]`)
160+
/// - Provide documentation (`#[doc]`)
161+
/// - Guide compiler behavior (e.g., `#[allow_internal_unstable]`)
162+
///
163+
/// ## Note on Attribute Organization
164+
/// Some attributes like `InlineAttr`, `OptimizeAttr`, and `InstructionSetAttr` are defined separately
165+
/// from this enum because they are used in specific compiler phases (like code generation) and don't
166+
/// need to persist throughout the entire compilation process. They are typically processed and
167+
/// converted into their final form earlier in the compilation pipeline.
168+
///
169+
/// For example:
170+
/// - `InlineAttr` is used during code generation to control function inlining
171+
/// - `OptimizeAttr` is used to control optimization levels
172+
/// - `InstructionSetAttr` is used for target-specific code generation
173+
///
174+
/// These attributes are handled by their respective compiler passes in the [`rustc_codegen_ssa`] crate
175+
/// and don't need to be preserved in the same way as the attributes in this enum.
176+
///
177+
/// For more details on attribute parsing, see the [`rustc_attr_parsing`] crate.
178+
///
179+
/// [`rustc_parse`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_parse/index.html
180+
/// [`rustc_codegen_ssa`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_codegen_ssa/index.html
151181
/// [`rustc_attr_parsing`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_attr_parsing/index.html
152182
#[derive(Clone, Debug, HashStable_Generic, Encodable, Decodable, PrintAttribute)]
153183
pub enum AttributeKind {

compiler/rustc_attr_data_structures/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
//! Data structures for representing parsed attributes in the Rust compiler.
2+
//! For detailed documentation about attribute processing,
3+
//! see [rustc_attr_parsing](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_attr_parsing/index.html).
4+
15
// tidy-alphabetical-start
26
#![allow(internal_features)]
37
#![doc(rust_logo)]

compiler/rustc_builtin_macros/src/test.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,10 +118,7 @@ pub(crate) fn expand_test_or_bench(
118118

119119
let (item, is_stmt) = match item {
120120
Annotatable::Item(i) => (i, false),
121-
Annotatable::Stmt(stmt) if matches!(stmt.kind, ast::StmtKind::Item(_)) => {
122-
// FIXME: Use an 'if let' guard once they are implemented
123-
if let ast::StmtKind::Item(i) = stmt.kind { (i, true) } else { unreachable!() }
124-
}
121+
Annotatable::Stmt(box ast::Stmt { kind: ast::StmtKind::Item(i), .. }) => (i, true),
125122
other => {
126123
not_testable_error(cx, attr_sp, None);
127124
return vec![other];

compiler/rustc_expand/src/base.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ use crate::base::ast::MetaItemInner;
3535
use crate::errors;
3636
use crate::expand::{self, AstFragment, Invocation};
3737
use crate::module::DirOwnership;
38+
use crate::stats::MacroStat;
3839

3940
// When adding new variants, make sure to
4041
// adjust the `visit_*` / `flat_map_*` calls in `InvocationCollector`
@@ -1191,7 +1192,7 @@ pub struct ExtCtxt<'a> {
11911192
/// not to expand it again.
11921193
pub(super) expanded_inert_attrs: MarkedAttrs,
11931194
/// `-Zmacro-stats` data.
1194-
pub macro_stats: FxHashMap<(Symbol, MacroKind), crate::stats::MacroStat>, // njn: quals
1195+
pub macro_stats: FxHashMap<(Symbol, MacroKind), MacroStat>,
11951196
}
11961197

11971198
impl<'a> ExtCtxt<'a> {

compiler/rustc_feature/src/builtin_attrs.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -887,7 +887,7 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
887887
rustc_legacy_const_generics, Normal, template!(List: "N"), ErrorFollowing,
888888
EncodeCrossCrate::Yes,
889889
),
890-
// Do not const-check this function's body. It will always get replaced during CTFE.
890+
// Do not const-check this function's body. It will always get replaced during CTFE via `hook_special_const_fn`.
891891
rustc_attr!(
892892
rustc_do_not_const_check, Normal, template!(Word), WarnFollowing,
893893
EncodeCrossCrate::Yes, "`#[rustc_do_not_const_check]` skips const-check for this function's body",

compiler/rustc_middle/src/query/mod.rs

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,64 @@
1-
//! Defines the various compiler queries.
21
//!
3-
//! For more information on the query system, see
4-
//! ["Queries: demand-driven compilation"](https://rustc-dev-guide.rust-lang.org/query.html).
5-
//! This chapter includes instructions for adding new queries.
2+
//! # The rustc Query System: Query Definitions and Modifiers
3+
//!
4+
//! The core processes in rustc are shipped as queries. Each query is a demand-driven function from some key to a value.
5+
//! The execution result of the function is cached and directly read during the next request, thereby improving compilation efficiency.
6+
//! Some results are saved locally and directly read during the next compilation, which are core of incremental compilation.
7+
//!
8+
//! ## How to Read This Module
9+
//!
10+
//! Each `query` block in this file defines a single query, specifying its key and value types, along with various modifiers.
11+
//! These query definitions are processed by the [`rustc_macros`], which expands them into the necessary boilerplate code
12+
//! for the query system—including the [`Providers`] struct (a function table for all query implementations, where each field is
13+
//! a function pointer to the actual provider), caching, and dependency graph integration.
14+
//! **Note:** The `Providers` struct is not a Rust trait, but a struct generated by the `rustc_macros` to hold all provider functions.
15+
//! The `rustc_macros` also supports a set of **query modifiers** (see below) that control the behavior of each query.
16+
//!
17+
//! The actual provider functions are implemented in various modules and registered into the `Providers` struct
18+
//! during compiler initialization (see [`rustc_interface::passes::DEFAULT_QUERY_PROVIDERS`]).
19+
//!
20+
//! [`rustc_macros`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_macros/index.html
21+
//! [`rustc_interface::passes::DEFAULT_QUERY_PROVIDERS`]: ../../rustc_interface/passes/static.DEFAULT_QUERY_PROVIDERS.html
22+
//!
23+
//! ## Query Modifiers
24+
//!
25+
//! Query modifiers are special flags that alter the behavior of a query. They are parsed and processed by the `rustc_macros`
26+
//! The main modifiers are:
27+
//!
28+
//! - `desc { ... }`: Sets the human-readable description for diagnostics and profiling. Required for every query.
29+
//! - `arena_cache`: Use an arena for in-memory caching of the query result.
30+
//! - `cache_on_disk_if { ... }`: Cache the query result to disk if the provided block evaluates to true.
31+
//! - `fatal_cycle`: If a dependency cycle is detected, abort compilation with a fatal error.
32+
//! - `cycle_delay_bug`: If a dependency cycle is detected, emit a delayed bug instead of aborting immediately.
33+
//! - `cycle_stash`: If a dependency cycle is detected, stash the error for later handling.
34+
//! - `no_hash`: Do not hash the query result for incremental compilation; just mark as dirty if recomputed.
35+
//! - `anon`: Make the query anonymous in the dependency graph (no dep node is created).
36+
//! - `eval_always`: Always evaluate the query, ignoring its dependencies and cached results.
37+
//! - `depth_limit`: Impose a recursion depth limit on the query to prevent stack overflows.
38+
//! - `separate_provide_extern`: Use separate provider functions for local and external crates.
39+
//! - `feedable`: Allow the query result to be set from another query ("fed" externally).
40+
//! - `return_result_from_ensure_ok`: When called via `tcx.ensure_ok()`, return `Result<(), ErrorGuaranteed>` instead of `()`.
41+
//! If the query needs to be executed and returns an error, the error is returned to the caller.
42+
//! Only valid for queries returning `Result<_, ErrorGuaranteed>`.
43+
//!
44+
//! For the up-to-date list, see the `QueryModifiers` struct in
45+
//! [`rustc_macros/src/query.rs`](https://github.com/rust-lang/rust/blob/master/compiler/rustc_macros/src/query.rs)
46+
//! and for more details in incremental compilation, see the
47+
//! [Query modifiers in incremental compilation](https://rustc-dev-guide.rust-lang.org/queries/incremental-compilation-in-detail.html#query-modifiers) section of the rustc-dev-guide.
48+
//!
49+
//! ## Query Expansion and Code Generation
50+
//!
51+
//! The [`rustc_macros::rustc_queries`] macro expands each query definition into:
52+
//! - A method on [`TyCtxt`] (and [`TyCtxtAt`]) for invoking the query.
53+
//! - Provider traits and structs for supplying the query's value.
54+
//! - Caching and dependency graph integration.
55+
//! - Support for incremental compilation, disk caching, and arena allocation as controlled by the modifiers.
56+
//!
57+
//! [`rustc_macros::rustc_queries`]: ../../rustc_macros/macro.rustc_queries.html
58+
//!
59+
//! The macro-based approach allows the query system to be highly flexible and maintainable, while minimizing boilerplate.
60+
//!
61+
//! For more details, see the [rustc-dev-guide](https://rustc-dev-guide.rust-lang.org/query.html).
662
763
#![allow(unused_parens)]
864

compiler/rustc_parse/src/parser/diagnostics.rs

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2273,23 +2273,18 @@ impl<'a> Parser<'a> {
22732273
),
22742274
// Also catches `fn foo(&a)`.
22752275
PatKind::Ref(ref inner_pat, mutab)
2276-
if matches!(inner_pat.clone().kind, PatKind::Ident(..)) =>
2276+
if let PatKind::Ident(_, ident, _) = inner_pat.clone().kind =>
22772277
{
2278-
match inner_pat.clone().kind {
2279-
PatKind::Ident(_, ident, _) => {
2280-
let mutab = mutab.prefix_str();
2281-
(
2282-
ident,
2283-
"self: ",
2284-
format!("{ident}: &{mutab}TypeName"),
2285-
"_: ",
2286-
pat.span.shrink_to_lo(),
2287-
pat.span,
2288-
pat.span.shrink_to_lo(),
2289-
)
2290-
}
2291-
_ => unreachable!(),
2292-
}
2278+
let mutab = mutab.prefix_str();
2279+
(
2280+
ident,
2281+
"self: ",
2282+
format!("{ident}: &{mutab}TypeName"),
2283+
"_: ",
2284+
pat.span.shrink_to_lo(),
2285+
pat.span,
2286+
pat.span.shrink_to_lo(),
2287+
)
22932288
}
22942289
_ => {
22952290
// Otherwise, try to get a type and emit a suggestion.

compiler/rustc_smir/src/rustc_smir/context.rs

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ use rustc_middle::ty::layout::{
1212
};
1313
use rustc_middle::ty::print::{with_forced_trimmed_paths, with_no_trimmed_paths};
1414
use rustc_middle::ty::{
15-
GenericPredicates, Instance, List, ScalarInt, TyCtxt, TypeVisitableExt, ValTree,
15+
CoroutineArgsExt, GenericPredicates, Instance, List, ScalarInt, TyCtxt, TypeVisitableExt,
16+
ValTree,
1617
};
1718
use rustc_middle::{mir, ty};
1819
use rustc_span::def_id::LOCAL_CRATE;
@@ -22,9 +23,9 @@ use stable_mir::mir::mono::{InstanceDef, StaticDef};
2223
use stable_mir::mir::{BinOp, Body, Place, UnOp};
2324
use stable_mir::target::{MachineInfo, MachineSize};
2425
use stable_mir::ty::{
25-
AdtDef, AdtKind, Allocation, ClosureDef, ClosureKind, FieldDef, FnDef, ForeignDef,
26-
ForeignItemKind, GenericArgs, IntrinsicDef, LineInfo, MirConst, PolyFnSig, RigidTy, Span, Ty,
27-
TyConst, TyKind, UintTy, VariantDef,
26+
AdtDef, AdtKind, Allocation, ClosureDef, ClosureKind, CoroutineDef, Discr, FieldDef, FnDef,
27+
ForeignDef, ForeignItemKind, GenericArgs, IntrinsicDef, LineInfo, MirConst, PolyFnSig, RigidTy,
28+
Span, Ty, TyConst, TyKind, UintTy, VariantDef, VariantIdx,
2829
};
2930
use stable_mir::{Crate, CrateDef, CrateItem, CrateNum, DefId, Error, Filename, ItemKind, Symbol};
3031

@@ -447,6 +448,30 @@ impl<'tcx> SmirCtxt<'tcx> {
447448
def.internal(&mut *tables, tcx).variants().len()
448449
}
449450

451+
/// Discriminant for a given variant index of AdtDef
452+
pub fn adt_discr_for_variant(&self, adt: AdtDef, variant: VariantIdx) -> Discr {
453+
let mut tables = self.0.borrow_mut();
454+
let tcx = tables.tcx;
455+
let adt = adt.internal(&mut *tables, tcx);
456+
let variant = variant.internal(&mut *tables, tcx);
457+
adt.discriminant_for_variant(tcx, variant).stable(&mut *tables)
458+
}
459+
460+
/// Discriminant for a given variand index and args of a coroutine
461+
pub fn coroutine_discr_for_variant(
462+
&self,
463+
coroutine: CoroutineDef,
464+
args: &GenericArgs,
465+
variant: VariantIdx,
466+
) -> Discr {
467+
let mut tables = self.0.borrow_mut();
468+
let tcx = tables.tcx;
469+
let coroutine = coroutine.def_id().internal(&mut *tables, tcx);
470+
let args = args.internal(&mut *tables, tcx);
471+
let variant = variant.internal(&mut *tables, tcx);
472+
args.as_coroutine().discriminant_for_variant(coroutine, tcx, variant).stable(&mut *tables)
473+
}
474+
450475
/// The name of a variant.
451476
pub fn variant_name(&self, def: VariantDef) -> Symbol {
452477
let mut tables = self.0.borrow_mut();

compiler/rustc_smir/src/rustc_smir/convert/ty.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -960,3 +960,11 @@ impl<'tcx> Stable<'tcx> for ty::ImplTraitInTraitData {
960960
}
961961
}
962962
}
963+
964+
impl<'tcx> Stable<'tcx> for rustc_middle::ty::util::Discr<'tcx> {
965+
type T = stable_mir::ty::Discr;
966+
967+
fn stable(&self, tables: &mut Tables<'_>) -> Self::T {
968+
stable_mir::ty::Discr { val: self.val, ty: self.ty.stable(tables) }
969+
}
970+
}

compiler/rustc_smir/src/stable_mir/compiler_interface.rs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ use stable_mir::mir::mono::{Instance, InstanceDef, StaticDef};
1313
use stable_mir::mir::{BinOp, Body, Place, UnOp};
1414
use stable_mir::target::MachineInfo;
1515
use stable_mir::ty::{
16-
AdtDef, AdtKind, Allocation, ClosureDef, ClosureKind, FieldDef, FnDef, ForeignDef,
17-
ForeignItemKind, ForeignModule, ForeignModuleDef, GenericArgs, GenericPredicates, Generics,
18-
ImplDef, ImplTrait, IntrinsicDef, LineInfo, MirConst, PolyFnSig, RigidTy, Span, TraitDecl,
19-
TraitDef, Ty, TyConst, TyConstId, TyKind, UintTy, VariantDef,
16+
AdtDef, AdtKind, Allocation, ClosureDef, ClosureKind, CoroutineDef, Discr, FieldDef, FnDef,
17+
ForeignDef, ForeignItemKind, ForeignModule, ForeignModuleDef, GenericArgs, GenericPredicates,
18+
Generics, ImplDef, ImplTrait, IntrinsicDef, LineInfo, MirConst, PolyFnSig, RigidTy, Span,
19+
TraitDecl, TraitDef, Ty, TyConst, TyConstId, TyKind, UintTy, VariantDef, VariantIdx,
2020
};
2121
use stable_mir::{
2222
AssocItems, Crate, CrateItem, CrateItems, CrateNum, DefId, Error, Filename, ImplTraitDecls,
@@ -230,6 +230,21 @@ impl<'tcx> SmirInterface<'tcx> {
230230
self.cx.adt_variants_len(def)
231231
}
232232

233+
/// Discriminant for a given variant index of AdtDef
234+
pub(crate) fn adt_discr_for_variant(&self, adt: AdtDef, variant: VariantIdx) -> Discr {
235+
self.cx.adt_discr_for_variant(adt, variant)
236+
}
237+
238+
/// Discriminant for a given variand index and args of a coroutine
239+
pub(crate) fn coroutine_discr_for_variant(
240+
&self,
241+
coroutine: CoroutineDef,
242+
args: &GenericArgs,
243+
variant: VariantIdx,
244+
) -> Discr {
245+
self.cx.coroutine_discr_for_variant(coroutine, args, variant)
246+
}
247+
233248
/// The name of a variant.
234249
pub(crate) fn variant_name(&self, def: VariantDef) -> Symbol {
235250
self.cx.variant_name(def)

compiler/rustc_smir/src/stable_mir/ty.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -756,6 +756,12 @@ crate_def! {
756756
pub CoroutineDef;
757757
}
758758

759+
impl CoroutineDef {
760+
pub fn discriminant_for_variant(&self, args: &GenericArgs, idx: VariantIdx) -> Discr {
761+
with(|cx| cx.coroutine_discr_for_variant(*self, args, idx))
762+
}
763+
}
764+
759765
crate_def! {
760766
#[derive(Serialize)]
761767
pub CoroutineClosureDef;
@@ -831,6 +837,15 @@ impl AdtDef {
831837
pub fn repr(&self) -> ReprOptions {
832838
with(|cx| cx.adt_repr(*self))
833839
}
840+
841+
pub fn discriminant_for_variant(&self, idx: VariantIdx) -> Discr {
842+
with(|cx| cx.adt_discr_for_variant(*self, idx))
843+
}
844+
}
845+
846+
pub struct Discr {
847+
pub val: u128,
848+
pub ty: Ty,
834849
}
835850

836851
/// Definition of a variant, which can be either a struct / union field or an enum variant.

library/core/src/panic/location.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ use crate::fmt;
3030
/// Files are compared as strings, not `Path`, which could be unexpected.
3131
/// See [`Location::file`]'s documentation for more discussion.
3232
#[lang = "panic_location"]
33-
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
33+
#[derive(Copy, Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
3434
#[stable(feature = "panic_hooks", since = "1.10.0")]
3535
pub struct Location<'a> {
3636
// Note: this filename will have exactly one nul byte at its end, but otherwise
@@ -43,6 +43,17 @@ pub struct Location<'a> {
4343
col: u32,
4444
}
4545

46+
#[stable(feature = "panic_hooks", since = "1.10.0")]
47+
impl fmt::Debug for Location<'_> {
48+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49+
f.debug_struct("Location")
50+
.field("file", &self.file())
51+
.field("line", &self.line)
52+
.field("column", &self.col)
53+
.finish()
54+
}
55+
}
56+
4657
impl<'a> Location<'a> {
4758
/// Returns the source location of the caller of this function. If that function's caller is
4859
/// annotated then its call location will be returned, and so on up the stack to the first call

library/coretests/tests/panic/location.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,11 @@ fn location_const_column() {
2929
const COLUMN: u32 = CALLER.column();
3030
assert_eq!(COLUMN, 40);
3131
}
32+
33+
#[test]
34+
fn location_debug() {
35+
let f = format!("{:?}", Location::caller());
36+
assert!(f.contains(&format!("{:?}", file!())));
37+
assert!(f.contains("35"));
38+
assert!(f.contains("29"));
39+
}

0 commit comments

Comments
 (0)