Auto merge of #139023 - jhpratt:rollup-4ou6ei4, r=jhpratt

Rollup of 7 pull requests

Successful merges:

 - #138844 (expand: Leave traces when expanding `cfg` attributes)
 - #138926 (Remove `kw::Empty` uses from `rustc_middle`.)
 - #138989 (Clean up a few things in rustc_hir_analysis::check::region)
 - #138999 (Report compiletest pass mode if forced)
 - #139014 (Improve suggest construct with literal syntax instead of calling)
 - #139015 (Remove unneeded LLVM CI test assertions)
 - #139016 (Add job duration changes to post-merge analysis report)

r? `@ghost`
`@rustbot` modify labels: rollup
This commit is contained in:
bors 2025-03-27 18:33:31 +00:00
commit 3f5502370b
44 changed files with 428 additions and 250 deletions

View file

@ -69,6 +69,8 @@ jobs:
env:
CI_JOB_NAME: ${{ matrix.name }}
CI_JOB_DOC_URL: ${{ matrix.doc_url }}
GITHUB_WORKFLOW_RUN_ID: ${{ github.run_id }}
GITHUB_REPOSITORY: ${{ github.repository }}
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
# commit of PR sha or commit sha. `GITHUB_SHA` is not accurate for PRs.
HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }}

View file

@ -334,8 +334,7 @@ impl<'a> AstValidator<'a> {
.filter(|attr| {
let arr = [
sym::allow,
sym::cfg,
sym::cfg_attr,
sym::cfg_trace,
sym::cfg_attr_trace,
sym::deny,
sym::expect,

View file

@ -593,7 +593,7 @@ pub trait PrintState<'a>: std::ops::Deref<Target = pp::Printer> + std::ops::Dere
}
fn print_attribute_inline(&mut self, attr: &ast::Attribute, is_inline: bool) -> bool {
if attr.has_name(sym::cfg_attr_trace) {
if attr.has_name(sym::cfg_trace) || attr.has_name(sym::cfg_attr_trace) {
// It's not a valid identifier, so avoid printing it
// to keep the printed code reasonably parse-able.
return false;

View file

@ -156,6 +156,19 @@ pub fn pre_configure_attrs(sess: &Session, attrs: &[Attribute]) -> ast::AttrVec
.collect()
}
pub(crate) fn attr_into_trace(mut attr: Attribute, trace_name: Symbol) -> Attribute {
match &mut attr.kind {
AttrKind::Normal(normal) => {
let NormalAttr { item, tokens } = &mut **normal;
item.path.segments[0].ident.name = trace_name;
// This makes the trace attributes unobservable to token-based proc macros.
*tokens = Some(LazyAttrTokenStream::new(AttrTokenStream::default()));
}
AttrKind::DocComment(..) => unreachable!(),
}
attr
}
#[macro_export]
macro_rules! configure {
($this:ident, $node:ident) => {
@ -280,16 +293,7 @@ impl<'a> StripUnconfigured<'a> {
// A trace attribute left in AST in place of the original `cfg_attr` attribute.
// It can later be used by lints or other diagnostics.
let mut trace_attr = cfg_attr.clone();
match &mut trace_attr.kind {
AttrKind::Normal(normal) => {
let NormalAttr { item, tokens } = &mut **normal;
item.path.segments[0].ident.name = sym::cfg_attr_trace;
// This makes the trace attributes unobservable to token-based proc macros.
*tokens = Some(LazyAttrTokenStream::new(AttrTokenStream::default()));
}
AttrKind::DocComment(..) => unreachable!(),
}
let trace_attr = attr_into_trace(cfg_attr.clone(), sym::cfg_attr_trace);
let Some((cfg_predicate, expanded_attrs)) =
rustc_parse::parse_cfg_attr(cfg_attr, &self.sess.psess)

View file

@ -33,7 +33,7 @@ use rustc_span::{ErrorGuaranteed, FileName, Ident, LocalExpnId, Span, sym};
use smallvec::SmallVec;
use crate::base::*;
use crate::config::StripUnconfigured;
use crate::config::{StripUnconfigured, attr_into_trace};
use crate::errors::{
EmptyDelegationMac, GlobDelegationOutsideImpls, GlobDelegationTraitlessQpath, IncompleteParse,
RecursionLimitReached, RemoveExprNotSupported, RemoveNodeNotSupported, UnsupportedKeyValue,
@ -2003,7 +2003,7 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
let attr_name = attr.ident().unwrap().name;
// `#[cfg]` and `#[cfg_attr]` are special - they are
// eagerly evaluated.
if attr_name != sym::cfg && attr_name != sym::cfg_attr_trace {
if attr_name != sym::cfg_trace && attr_name != sym::cfg_attr_trace {
self.cx.sess.psess.buffer_lint(
UNUSED_ATTRIBUTES,
attr.span,
@ -2027,11 +2027,10 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
) -> (bool, Option<ast::MetaItem>) {
let (res, meta_item) = self.cfg().cfg_true(&attr);
if res {
// FIXME: `cfg(TRUE)` attributes do not currently remove themselves during expansion,
// and some tools like rustdoc and clippy rely on that. Find a way to remove them
// while keeping the tools working.
self.cx.expanded_inert_attrs.mark(&attr);
node.visit_attrs(|attrs| attrs.insert(pos, attr));
// A trace attribute left in AST in place of the original `cfg` attribute.
// It can later be used by lints or other diagnostics.
let trace_attr = attr_into_trace(attr, sym::cfg_trace);
node.visit_attrs(|attrs| attrs.insert(pos, trace_attr));
}
(res, meta_item)

View file

@ -760,10 +760,14 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
template!(Word, List: r#""...""#), DuplicatesOk,
EncodeCrossCrate::Yes, INTERNAL_UNSTABLE
),
// Trace that is left when a `cfg_attr` attribute is expanded.
// The attribute is not gated, to avoid stability errors, but it cannot be used in stable or
// unstable code directly because `sym::cfg_attr_trace` is not a valid identifier, it can only
// be generated by the compiler.
// Traces that are left when `cfg` and `cfg_attr` attributes are expanded.
// The attributes are not gated, to avoid stability errors, but they cannot be used in stable
// or unstable code directly because `sym::cfg_(attr_)trace` are not valid identifiers, they
// can only be generated by the compiler.
ungated!(
cfg_trace, Normal, template!(Word /* irrelevant */), DuplicatesOk,
EncodeCrossCrate::No
),
ungated!(
cfg_attr_trace, Normal, template!(Word /* irrelevant */), DuplicatesOk,
EncodeCrossCrate::No

View file

@ -25,12 +25,18 @@ use tracing::debug;
struct Context {
/// The scope that contains any new variables declared, plus its depth in
/// the scope tree.
var_parent: Option<(Scope, ScopeDepth)>,
var_parent: Option<Scope>,
/// Region parent of expressions, etc., plus its depth in the scope tree.
parent: Option<(Scope, ScopeDepth)>,
}
impl Context {
fn set_var_parent(&mut self) {
self.var_parent = self.parent.map(|(p, _)| p);
}
}
struct ScopeResolutionVisitor<'tcx> {
tcx: TyCtxt<'tcx>,
@ -78,7 +84,7 @@ fn record_var_lifetime(visitor: &mut ScopeResolutionVisitor<'_>, var_id: hir::It
//
// extern fn isalnum(c: c_int) -> c_int
}
Some((parent_scope, _)) => visitor.scope_tree.record_var_scope(var_id, parent_scope),
Some(parent_scope) => visitor.scope_tree.record_var_scope(var_id, parent_scope),
}
}
@ -113,7 +119,7 @@ fn resolve_block<'tcx>(visitor: &mut ScopeResolutionVisitor<'tcx>, blk: &'tcx hi
// itself has returned.
visitor.enter_node_scope_with_dtor(blk.hir_id.local_id);
visitor.cx.var_parent = visitor.cx.parent;
visitor.cx.set_var_parent();
{
// This block should be kept approximately in sync with
@ -132,7 +138,7 @@ fn resolve_block<'tcx>(visitor: &mut ScopeResolutionVisitor<'tcx>, blk: &'tcx hi
local_id: blk.hir_id.local_id,
data: ScopeData::Remainder(FirstStatementIndex::new(i)),
});
visitor.cx.var_parent = visitor.cx.parent;
visitor.cx.set_var_parent();
visitor.visit_stmt(statement);
// We need to back out temporarily to the last enclosing scope
// for the `else` block, so that even the temporaries receiving
@ -157,7 +163,7 @@ fn resolve_block<'tcx>(visitor: &mut ScopeResolutionVisitor<'tcx>, blk: &'tcx hi
local_id: blk.hir_id.local_id,
data: ScopeData::Remainder(FirstStatementIndex::new(i)),
});
visitor.cx.var_parent = visitor.cx.parent;
visitor.cx.set_var_parent();
visitor.visit_stmt(statement)
}
hir::StmtKind::Item(..) => {
@ -207,7 +213,7 @@ fn resolve_arm<'tcx>(visitor: &mut ScopeResolutionVisitor<'tcx>, arm: &'tcx hir:
visitor.terminating_scopes.insert(arm.hir_id.local_id);
visitor.enter_node_scope_with_dtor(arm.hir_id.local_id);
visitor.cx.var_parent = visitor.cx.parent;
visitor.cx.set_var_parent();
if let Some(expr) = arm.guard
&& !has_let_expr(expr)
@ -221,8 +227,6 @@ fn resolve_arm<'tcx>(visitor: &mut ScopeResolutionVisitor<'tcx>, arm: &'tcx hir:
}
fn resolve_pat<'tcx>(visitor: &mut ScopeResolutionVisitor<'tcx>, pat: &'tcx hir::Pat<'tcx>) {
visitor.record_child_scope(Scope { local_id: pat.hir_id.local_id, data: ScopeData::Node });
// If this is a binding then record the lifetime of that binding.
if let PatKind::Binding(..) = pat.kind {
record_var_lifetime(visitor, pat.hir_id.local_id);
@ -486,7 +490,7 @@ fn resolve_expr<'tcx>(visitor: &mut ScopeResolutionVisitor<'tcx>, expr: &'tcx hi
ScopeData::IfThen
};
visitor.enter_scope(Scope { local_id: then.hir_id.local_id, data });
visitor.cx.var_parent = visitor.cx.parent;
visitor.cx.set_var_parent();
visitor.visit_expr(cond);
visitor.visit_expr(then);
visitor.cx = expr_cx;
@ -501,7 +505,7 @@ fn resolve_expr<'tcx>(visitor: &mut ScopeResolutionVisitor<'tcx>, expr: &'tcx hi
ScopeData::IfThen
};
visitor.enter_scope(Scope { local_id: then.hir_id.local_id, data });
visitor.cx.var_parent = visitor.cx.parent;
visitor.cx.set_var_parent();
visitor.visit_expr(cond);
visitor.visit_expr(then);
visitor.cx = expr_cx;
@ -560,7 +564,7 @@ fn resolve_local<'tcx>(
) {
debug!("resolve_local(pat={:?}, init={:?})", pat, init);
let blk_scope = visitor.cx.var_parent.map(|(p, _)| p);
let blk_scope = visitor.cx.var_parent;
// As an exception to the normal rules governing temporary
// lifetimes, initializers in a let have a temporary lifetime
@ -625,10 +629,7 @@ fn resolve_local<'tcx>(
if is_binding_pat(pat) {
visitor.scope_tree.record_rvalue_candidate(
expr.hir_id,
RvalueCandidateType::Pattern {
target: expr.hir_id.local_id,
lifetime: blk_scope,
},
RvalueCandidate { target: expr.hir_id.local_id, lifetime: blk_scope },
);
}
}
@ -733,10 +734,7 @@ fn resolve_local<'tcx>(
record_rvalue_scope_if_borrow_expr(visitor, subexpr, blk_id);
visitor.scope_tree.record_rvalue_candidate(
subexpr.hir_id,
RvalueCandidateType::Borrow {
target: subexpr.hir_id.local_id,
lifetime: blk_id,
},
RvalueCandidate { target: subexpr.hir_id.local_id, lifetime: blk_id },
);
}
hir::ExprKind::Struct(_, fields, _) => {
@ -857,13 +855,12 @@ impl<'tcx> Visitor<'tcx> for ScopeResolutionVisitor<'tcx> {
self.enter_body(body.value.hir_id, |this| {
if this.tcx.hir_body_owner_kind(owner_id).is_fn_or_closure() {
// The arguments and `self` are parented to the fn.
this.cx.var_parent = this.cx.parent.take();
this.cx.set_var_parent();
for param in body.params {
this.visit_pat(param.pat);
}
// The body of the every fn is a root scope.
this.cx.parent = this.cx.var_parent;
this.visit_expr(body.value)
} else {
// Only functions have an outer terminating (drop) scope, while

View file

@ -2,7 +2,7 @@ use hir::Node;
use hir::def_id::DefId;
use rustc_hir as hir;
use rustc_middle::bug;
use rustc_middle::middle::region::{RvalueCandidateType, Scope, ScopeTree};
use rustc_middle::middle::region::{RvalueCandidate, Scope, ScopeTree};
use rustc_middle::ty::RvalueScopes;
use tracing::debug;
@ -55,15 +55,11 @@ fn record_rvalue_scope_rec(
fn record_rvalue_scope(
rvalue_scopes: &mut RvalueScopes,
expr: &hir::Expr<'_>,
candidate: &RvalueCandidateType,
candidate: &RvalueCandidate,
) {
debug!("resolve_rvalue_scope(expr={expr:?}, candidate={candidate:?})");
match candidate {
RvalueCandidateType::Borrow { lifetime, .. }
| RvalueCandidateType::Pattern { lifetime, .. } => {
record_rvalue_scope_rec(rvalue_scopes, expr, *lifetime)
} // FIXME(@dingxiangfei2009): handle the candidates in the function call arguments
}
record_rvalue_scope_rec(rvalue_scopes, expr, candidate.lifetime)
// FIXME(@dingxiangfei2009): handle the candidates in the function call arguments
}
pub(crate) fn resolve_rvalue_scopes<'a, 'tcx>(

View file

@ -224,7 +224,7 @@ pub struct ScopeTree {
/// and not the enclosing *statement*. Expressions that are not present in this
/// table are not rvalue candidates. The set of rvalue candidates is computed
/// during type check based on a traversal of the AST.
pub rvalue_candidates: HirIdMap<RvalueCandidateType>,
pub rvalue_candidates: HirIdMap<RvalueCandidate>,
/// Backwards incompatible scoping that will be introduced in future editions.
/// This information is used later for linting to identify locals and
@ -308,15 +308,14 @@ pub struct ScopeTree {
pub yield_in_scope: UnordMap<Scope, Vec<YieldData>>,
}
/// Identifies the reason that a given expression is an rvalue candidate
/// (see the `rvalue_candidates` field for more information what rvalue
/// candidates in general). In constants, the `lifetime` field is None
/// to indicate that certain expressions escape into 'static and
/// should have no local cleanup scope.
/// See the `rvalue_candidates` field for more information on rvalue
/// candidates in general.
/// The `lifetime` field is None to indicate that certain expressions escape
/// into 'static and should have no local cleanup scope.
#[derive(Debug, Copy, Clone, HashStable)]
pub enum RvalueCandidateType {
Borrow { target: hir::ItemLocalId, lifetime: Option<Scope> },
Pattern { target: hir::ItemLocalId, lifetime: Option<Scope> },
pub struct RvalueCandidate {
pub target: hir::ItemLocalId,
pub lifetime: Option<Scope>,
}
#[derive(Debug, Copy, Clone, HashStable)]
@ -344,16 +343,12 @@ impl ScopeTree {
self.var_map.insert(var, lifetime);
}
pub fn record_rvalue_candidate(&mut self, var: HirId, candidate_type: RvalueCandidateType) {
debug!("record_rvalue_candidate(var={var:?}, type={candidate_type:?})");
match &candidate_type {
RvalueCandidateType::Borrow { lifetime: Some(lifetime), .. }
| RvalueCandidateType::Pattern { lifetime: Some(lifetime), .. } => {
assert!(var.local_id != lifetime.local_id)
}
_ => {}
pub fn record_rvalue_candidate(&mut self, var: HirId, candidate: RvalueCandidate) {
debug!("record_rvalue_candidate(var={var:?}, candidate={candidate:?})");
if let Some(lifetime) = &candidate.lifetime {
assert!(var.local_id != lifetime.local_id)
}
self.rvalue_candidates.insert(var, candidate_type);
self.rvalue_candidates.insert(var, candidate);
}
/// Returns the narrowest scope that encloses `id`, if any.

View file

@ -73,9 +73,7 @@ impl GenericParamDef {
pub fn is_anonymous_lifetime(&self) -> bool {
match self.kind {
GenericParamDefKind::Lifetime => {
self.name == kw::UnderscoreLifetime || self.name == kw::Empty
}
GenericParamDefKind::Lifetime => self.name == kw::UnderscoreLifetime,
_ => false,
}
}

View file

@ -457,7 +457,7 @@ impl EarlyParamRegion {
/// Does this early bound region have a name? Early bound regions normally
/// always have names except when using anonymous lifetimes (`'_`).
pub fn has_name(&self) -> bool {
self.name != kw::UnderscoreLifetime && self.name != kw::Empty
self.name != kw::UnderscoreLifetime
}
}

View file

@ -2591,11 +2591,9 @@ impl<'tcx> FmtPrinter<'_, 'tcx> {
// to fit that into a short string. Hence the recommendation to use
// `explain_region()` or `note_and_explain_region()`.
match *region {
ty::ReEarlyParam(ref data) => {
if data.name != kw::Empty {
p!(write("{}", data.name));
return Ok(());
}
ty::ReEarlyParam(data) => {
p!(write("{}", data.name));
return Ok(());
}
ty::ReLateParam(ty::LateParamRegion { kind, .. }) => {
if let Some(name) = kind.get_name() {
@ -2834,7 +2832,7 @@ impl<'tcx> FmtPrinter<'_, 'tcx> {
(name, ty::BoundRegionKind::Named(CRATE_DEF_ID.to_def_id(), name))
}
ty::BoundRegionKind::Named(def_id, kw::UnderscoreLifetime | kw::Empty) => {
ty::BoundRegionKind::Named(def_id, kw::UnderscoreLifetime) => {
let name = next_name(self);
if let Some(lt_idx) = lifetime_idx {

View file

@ -400,9 +400,7 @@ impl LateParamRegionKind {
pub fn is_named(&self) -> bool {
match *self {
LateParamRegionKind::Named(_, name) => {
name != kw::UnderscoreLifetime && name != kw::Empty
}
LateParamRegionKind::Named(_, name) => name != kw::UnderscoreLifetime,
_ => false,
}
}
@ -475,7 +473,7 @@ impl core::fmt::Debug for BoundRegion {
impl BoundRegionKind {
pub fn is_named(&self) -> bool {
match *self {
BoundRegionKind::Named(_, name) => name != kw::UnderscoreLifetime && name != kw::Empty,
BoundRegionKind::Named(_, name) => name != kw::UnderscoreLifetime,
_ => false,
}
}

View file

@ -16,7 +16,8 @@ use rustc_span::{Span, Symbol, sym};
use crate::{errors, parse_in};
pub fn check_attr(psess: &ParseSess, attr: &Attribute) {
if attr.is_doc_comment() || attr.has_name(sym::cfg_attr_trace) {
if attr.is_doc_comment() || attr.has_name(sym::cfg_trace) || attr.has_name(sym::cfg_attr_trace)
{
return;
}
@ -215,11 +216,7 @@ pub fn check_builtin_meta_item(
template: AttributeTemplate,
deny_unsafety: bool,
) {
// Some special attributes like `cfg` must be checked
// before the generic check, so we skip them here.
let should_skip = |name| name == sym::cfg;
if !should_skip(name) && !is_attr_template_compatible(&template, &meta.kind) {
if !is_attr_template_compatible(&template, &meta.kind) {
emit_malformed_attribute(psess, style, meta.span, name, template);
}

View file

@ -272,6 +272,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
| sym::forbid
| sym::cfg
| sym::cfg_attr
| sym::cfg_trace
| sym::cfg_attr_trace
// need to be fixed
| sym::cfi_encoding // FIXME(cfi_encoding)
@ -574,8 +575,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
// NOTE: when making changes to this list, check that `error_codes/E0736.md` remains accurate
const ALLOW_LIST: &[rustc_span::Symbol] = &[
// conditional compilation
sym::cfg,
sym::cfg_attr,
sym::cfg_trace,
sym::cfg_attr_trace,
// testing (allowed here so better errors can be generated in `rustc_builtin_macros::test`)
sym::test,
@ -2656,7 +2656,7 @@ impl<'tcx> Visitor<'tcx> for CheckAttrVisitor<'tcx> {
// only `#[cfg]` and `#[cfg_attr]` are allowed, but it should be removed
// if we allow more attributes (e.g., tool attributes and `allow/deny/warn`)
// in where clauses. After that, only `self.check_attributes` should be enough.
const ATTRS_ALLOWED: &[Symbol] = &[sym::cfg, sym::cfg_attr, sym::cfg_attr_trace];
const ATTRS_ALLOWED: &[Symbol] = &[sym::cfg_trace, sym::cfg_attr_trace];
let spans = self
.tcx
.hir_attrs(where_predicate.hir_id)

View file

@ -8,7 +8,7 @@ mod hcx;
mod impls_syntax;
pub const IGNORED_ATTRIBUTES: &[Symbol] = &[
sym::cfg,
sym::cfg_trace, // FIXME should this really be ignored?
sym::rustc_if_this_changed,
sym::rustc_then_this_would_need,
sym::rustc_dirty,

View file

@ -1665,41 +1665,81 @@ impl<'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
// the struct literal syntax at all, as that will cause a subsequent error.
let fields = this.r.field_idents(def_id);
let has_fields = fields.as_ref().is_some_and(|f| !f.is_empty());
let (fields, applicability) = match fields {
Some(fields) => {
let fields = if let Some(old_fields) = old_fields {
fields
.iter()
.enumerate()
.map(|(idx, new)| (new, old_fields.get(idx)))
.map(|(new, old)| {
if let Some(Some(old)) = old
&& new.as_str() != old
{
format!("{new}: {old}")
} else {
new.to_string()
}
})
.collect::<Vec<String>>()
} else {
fields
.iter()
.map(|f| format!("{f}{tail}"))
.collect::<Vec<String>>()
};
(fields.join(", "), applicability)
}
None => ("/* fields */".to_string(), Applicability::HasPlaceholders),
};
let pad = if has_fields { " " } else { "" };
err.span_suggestion(
if let PathSource::Expr(Some(Expr {
kind: ExprKind::Call(path, args),
span,
format!("use struct {descr} syntax instead"),
format!("{path_str} {{{pad}{fields}{pad}}}"),
applicability,
);
..
})) = source
&& !args.is_empty()
&& let Some(fields) = &fields
&& args.len() == fields.len()
// Make sure we have same number of args as fields
{
let path_span = path.span;
let mut parts = Vec::new();
// Start with the opening brace
parts.push((
path_span.shrink_to_hi().until(args[0].span),
"{".to_owned(),
));
for (field, arg) in fields.iter().zip(args.iter()) {
// Add the field name before the argument
parts.push((arg.span.shrink_to_lo(), format!("{}: ", field)));
}
// Add the closing brace
parts.push((
args.last().unwrap().span.shrink_to_hi().until(span.shrink_to_hi()),
"}".to_owned(),
));
err.multipart_suggestion_verbose(
format!("use struct {descr} syntax instead of calling"),
parts,
applicability,
);
} else {
let (fields, applicability) = match fields {
Some(fields) => {
let fields = if let Some(old_fields) = old_fields {
fields
.iter()
.enumerate()
.map(|(idx, new)| (new, old_fields.get(idx)))
.map(|(new, old)| {
if let Some(Some(old)) = old
&& new.as_str() != old
{
format!("{new}: {old}")
} else {
new.to_string()
}
})
.collect::<Vec<String>>()
} else {
fields
.iter()
.map(|f| format!("{f}{tail}"))
.collect::<Vec<String>>()
};
(fields.join(", "), applicability)
}
None => {
("/* fields */".to_string(), Applicability::HasPlaceholders)
}
};
let pad = if has_fields { " " } else { "" };
err.span_suggestion(
span,
format!("use struct {descr} syntax instead"),
format!("{path_str} {{{pad}{fields}{pad}}}"),
applicability,
);
}
}
if let PathSource::Expr(Some(Expr {
kind: ExprKind::Call(path, args),

View file

@ -623,6 +623,7 @@ symbols! {
cfg_target_has_atomic_equal_alignment,
cfg_target_thread_local,
cfg_target_vendor,
cfg_trace: "<cfg>", // must not be a valid identifier
cfg_ub_checks,
cfg_version,
cfi,

View file

@ -24,31 +24,11 @@ pub(crate) fn parse(config: &str) -> Config {
#[test]
fn download_ci_llvm() {
let config = parse("");
let is_available = llvm::is_ci_llvm_available_for_target(&config, config.llvm_assertions);
if is_available {
assert!(config.llvm_from_ci);
}
let config = Config::parse_inner(
Flags::parse(&[
"check".to_string(),
"--config=/does/not/exist".to_string(),
"--ci".to_string(),
"false".to_string(),
]),
|&_| toml::from_str("llvm.download-ci-llvm = true"),
);
let is_available = llvm::is_ci_llvm_available_for_target(&config, config.llvm_assertions);
if is_available {
assert!(config.llvm_from_ci);
}
let config = parse("llvm.download-ci-llvm = false");
assert!(!config.llvm_from_ci);
let if_unchanged_config = parse("llvm.download-ci-llvm = \"if-unchanged\"");
if if_unchanged_config.llvm_from_ci {
if if_unchanged_config.llvm_from_ci && if_unchanged_config.is_running_on_ci {
let has_changes = if_unchanged_config
.last_modified_commit(LLVM_INVALIDATION_PATHS, "download-ci-llvm", true)
.is_none();

View file

@ -9,9 +9,10 @@ use std::fs::File;
use std::io::BufWriter;
use std::time::{Duration, Instant, SystemTime};
use build_helper::ci::CiEnv;
use build_helper::metrics::{
JsonInvocation, JsonInvocationSystemStats, JsonNode, JsonRoot, JsonStepSystemStats, Test,
TestOutcome, TestSuite, TestSuiteMetadata,
CiMetadata, JsonInvocation, JsonInvocationSystemStats, JsonNode, JsonRoot, JsonStepSystemStats,
Test, TestOutcome, TestSuite, TestSuiteMetadata,
};
use sysinfo::{CpuRefreshKind, RefreshKind, System};
@ -217,7 +218,12 @@ impl BuildMetrics {
children: steps.into_iter().map(|step| self.prepare_json_step(step)).collect(),
});
let json = JsonRoot { format_version: CURRENT_FORMAT_VERSION, system_stats, invocations };
let json = JsonRoot {
format_version: CURRENT_FORMAT_VERSION,
system_stats,
invocations,
ci_metadata: get_ci_metadata(CiEnv::current()),
};
t!(std::fs::create_dir_all(dest.parent().unwrap()));
let mut file = BufWriter::new(t!(File::create(&dest)));
@ -245,6 +251,16 @@ impl BuildMetrics {
}
}
fn get_ci_metadata(ci_env: CiEnv) -> Option<CiMetadata> {
if ci_env != CiEnv::GitHubActions {
return None;
}
let workflow_run_id =
std::env::var("GITHUB_WORKFLOW_RUN_ID").ok().and_then(|id| id.parse::<u64>().ok())?;
let repository = std::env::var("GITHUB_REPOSITORY").ok()?;
Some(CiMetadata { workflow_run_id, repository })
}
struct MetricsState {
finished_steps: Vec<StepMetrics>,
running_steps: Vec<StepMetrics>,

View file

@ -9,6 +9,19 @@ pub struct JsonRoot {
pub format_version: usize,
pub system_stats: JsonInvocationSystemStats,
pub invocations: Vec<JsonInvocation>,
#[serde(default)]
pub ci_metadata: Option<CiMetadata>,
}
/// Represents metadata about bootstrap's execution in CI.
#[derive(Serialize, Deserialize)]
pub struct CiMetadata {
/// GitHub run ID of the workflow where bootstrap was executed.
/// Note that the run ID will be shared amongst all jobs executed in that workflow.
pub workflow_run_id: u64,
/// Full name of a GitHub repository where bootstrap was executed in CI.
/// e.g. `rust-lang-ci/rust`.
pub repository: String,
}
#[derive(Serialize, Deserialize)]

View file

@ -1,5 +1,6 @@
use std::collections::{BTreeMap, HashMap, HashSet};
use std::fmt::Debug;
use std::time::Duration;
use build_helper::metrics::{
BuildStep, JsonRoot, TestOutcome, TestSuite, TestSuiteMetadata, escape_step_name,
@ -184,11 +185,70 @@ fn render_table(suites: BTreeMap<String, TestSuiteRecord>) -> String {
}
/// Outputs a report of test differences between the `parent` and `current` commits.
pub fn output_test_diffs(job_metrics: HashMap<JobName, JobMetrics>) {
pub fn output_test_diffs(job_metrics: &HashMap<JobName, JobMetrics>) {
let aggregated_test_diffs = aggregate_test_diffs(&job_metrics);
report_test_diffs(aggregated_test_diffs);
}
/// Prints the ten largest differences in bootstrap durations.
pub fn output_largest_duration_changes(job_metrics: &HashMap<JobName, JobMetrics>) {
struct Entry<'a> {
job: &'a JobName,
before: Duration,
after: Duration,
change: f64,
}
let mut changes: Vec<Entry> = vec![];
for (job, metrics) in job_metrics {
if let Some(parent) = &metrics.parent {
let duration_before = parent
.invocations
.iter()
.map(|i| BuildStep::from_invocation(i).duration)
.sum::<Duration>();
let duration_after = metrics
.current
.invocations
.iter()
.map(|i| BuildStep::from_invocation(i).duration)
.sum::<Duration>();
let pct_change = duration_after.as_secs_f64() / duration_before.as_secs_f64();
let pct_change = pct_change * 100.0;
// Normalize around 100, to get + for regression and - for improvements
let pct_change = pct_change - 100.0;
changes.push(Entry {
job,
before: duration_before,
after: duration_after,
change: pct_change,
});
}
}
changes.sort_by(|e1, e2| e1.change.partial_cmp(&e2.change).unwrap().reverse());
println!("# Job duration changes");
for (index, entry) in changes.into_iter().take(10).enumerate() {
println!(
"{}. `{}`: {:.1}s -> {:.1}s ({:.1}%)",
index + 1,
entry.job,
entry.before.as_secs_f64(),
entry.after.as_secs_f64(),
entry.change
);
}
println!();
output_details("How to interpret the job duration changes?", || {
println!(
r#"Job durations can vary a lot, based on the actual runner instance
that executed the job, system noise, invalidated caches, etc. The table above is provided
mostly for t-infra members, for simpler debugging of potential CI slow-downs."#
);
});
}
#[derive(Default)]
struct TestSuiteRecord {
passed: u64,

View file

@ -15,7 +15,7 @@ use clap::Parser;
use jobs::JobDatabase;
use serde_yaml::Value;
use crate::analysis::output_test_diffs;
use crate::analysis::{output_largest_duration_changes, output_test_diffs};
use crate::cpu_usage::load_cpu_usage;
use crate::datadog::upload_datadog_metric;
use crate::jobs::RunType;
@ -160,7 +160,7 @@ fn postprocess_metrics(
job_name,
JobMetrics { parent: Some(parent_metrics), current: metrics },
)]);
output_test_diffs(job_metrics);
output_test_diffs(&job_metrics);
return Ok(());
}
Err(error) => {
@ -180,7 +180,8 @@ fn post_merge_report(db: JobDatabase, current: String, parent: String) -> anyhow
let metrics = download_auto_job_metrics(&db, &parent, &current)?;
println!("\nComparing {parent} (parent) -> {current} (this PR)\n");
output_test_diffs(metrics);
output_test_diffs(&metrics);
output_largest_duration_changes(&metrics);
Ok(())
}

View file

@ -1,5 +1,5 @@
use std::collections::HashMap;
use std::path::Path;
use std::path::{Path, PathBuf};
use anyhow::Context;
use build_helper::metrics::{JsonNode, JsonRoot, TestSuite};
@ -74,6 +74,17 @@ Maybe it was newly added?"#,
}
pub fn download_job_metrics(job_name: &str, sha: &str) -> anyhow::Result<JsonRoot> {
// Best effort cache to speed-up local re-executions of citool
let cache_path = PathBuf::from(".citool-cache").join(sha).join(format!("{job_name}.json"));
if cache_path.is_file() {
if let Ok(metrics) = std::fs::read_to_string(&cache_path)
.map_err(|err| err.into())
.and_then(|data| anyhow::Ok::<JsonRoot>(serde_json::from_str::<JsonRoot>(&data)?))
{
return Ok(metrics);
}
}
let url = get_metrics_url(job_name, sha);
let mut response = ureq::get(&url).call()?;
if !response.status().is_success() {
@ -87,6 +98,13 @@ pub fn download_job_metrics(job_name: &str, sha: &str) -> anyhow::Result<JsonRoo
.body_mut()
.read_json()
.with_context(|| anyhow::anyhow!("cannot deserialize metrics from {url}"))?;
if let Ok(_) = std::fs::create_dir_all(cache_path.parent().unwrap()) {
if let Ok(data) = serde_json::to_string(&data) {
let _ = std::fs::write(cache_path, data);
}
}
Ok(data)
}

View file

@ -23,7 +23,6 @@ where
println!(
r"<details>
<summary>{summary}</summary>
"
);
func();

View file

@ -355,6 +355,8 @@ docker \
--env GITHUB_ACTIONS \
--env GITHUB_REF \
--env GITHUB_STEP_SUMMARY="/checkout/obj/${SUMMARY_FILE}" \
--env GITHUB_WORKFLOW_RUN_ID \
--env GITHUB_REPOSITORY \
--env RUST_BACKTRACE \
--env TOOLSTATE_REPO_ACCESS_TOKEN \
--env TOOLSTATE_REPO \

View file

@ -1943,14 +1943,11 @@ fn clean_trait_object_lifetime_bound<'tcx>(
// latter contrary to `clean_middle_region`.
match *region {
ty::ReStatic => Some(Lifetime::statik()),
ty::ReEarlyParam(region) if region.name != kw::Empty => Some(Lifetime(region.name)),
ty::ReBound(_, ty::BoundRegion { kind: ty::BoundRegionKind::Named(_, name), .. })
if name != kw::Empty =>
{
ty::ReEarlyParam(region) => Some(Lifetime(region.name)),
ty::ReBound(_, ty::BoundRegion { kind: ty::BoundRegionKind::Named(_, name), .. }) => {
Some(Lifetime(name))
}
ty::ReEarlyParam(_)
| ty::ReBound(..)
ty::ReBound(..)
| ty::ReLateParam(_)
| ty::ReVar(_)
| ty::RePlaceholder(_)
@ -2773,7 +2770,7 @@ fn add_without_unwanted_attributes<'hir>(
if ident == sym::doc {
filter_doc_attr(&mut normal.args, is_inline);
attrs.push((Cow::Owned(attr), import_parent));
} else if is_inline || ident != sym::cfg {
} else if is_inline || ident != sym::cfg_trace {
// If it's not a `cfg()` attribute, we keep it.
attrs.push((Cow::Owned(attr), import_parent));
}

View file

@ -1068,7 +1068,7 @@ pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator<Item = &'a hir::Attribute>
// `doc(cfg())` overrides `cfg()`).
attrs
.clone()
.filter(|attr| attr.has_name(sym::cfg))
.filter(|attr| attr.has_name(sym::cfg_trace))
.filter_map(|attr| single(attr.meta_item_list()?))
.filter_map(|attr| Cfg::parse_without(attr.meta_item()?, hidden_cfg).ok().flatten())
.fold(Cfg::True, |cfg, new_cfg| cfg & new_cfg)

View file

@ -37,7 +37,6 @@ fn check_duplicated_attr(
let Some(ident) = attr.ident() else { return };
let name = ident.name;
if name == sym::doc
|| name == sym::cfg_attr
|| name == sym::cfg_attr_trace
|| name == sym::rustc_on_unimplemented
|| name == sym::reason {
@ -47,7 +46,7 @@ fn check_duplicated_attr(
return;
}
if let Some(direct_parent) = parent.last()
&& ["cfg", "cfg_attr"].contains(&direct_parent.as_str())
&& direct_parent == sym::cfg_trace.as_str()
&& [sym::all, sym::not, sym::any].contains(&name)
{
// FIXME: We don't correctly check `cfg`s for now, so if it's more complex than just a one

View file

@ -32,7 +32,7 @@ declare_lint_pass!(CfgNotTest => [CFG_NOT_TEST]);
impl EarlyLintPass for CfgNotTest {
fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &rustc_ast::Attribute) {
if attr.has_name(rustc_span::sym::cfg) && contains_not_test(attr.meta_item_list().as_deref(), false) {
if attr.has_name(rustc_span::sym::cfg_trace) && contains_not_test(attr.meta_item_list().as_deref(), false) {
span_lint_and_then(
cx,
CFG_NOT_TEST,

View file

@ -41,7 +41,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &'_ Expr<'_>, receiver: &Expr<'_
fn is_under_cfg(cx: &LateContext<'_>, id: HirId) -> bool {
cx.tcx
.hir_parent_id_iter(id)
.any(|id| cx.tcx.hir_attrs(id).iter().any(|attr| attr.has_name(sym::cfg)))
.any(|id| cx.tcx.hir_attrs(id).iter().any(|attr| attr.has_name(sym::cfg_trace)))
}
/// Similar to [`clippy_utils::expr_or_init`], but does not go up the chain if the initialization

View file

@ -2629,7 +2629,7 @@ pub fn peel_ref_operators<'hir>(cx: &LateContext<'_>, mut expr: &'hir Expr<'hir>
pub fn is_hir_ty_cfg_dependant(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> bool {
if let TyKind::Path(QPath::Resolved(_, path)) = ty.kind {
if let Res::Def(_, def_id) = path.res {
return cx.tcx.has_attr(def_id, sym::cfg) || cx.tcx.has_attr(def_id, sym::cfg_attr);
return cx.tcx.has_attr(def_id, sym::cfg_trace) || cx.tcx.has_attr(def_id, sym::cfg_attr);
}
}
false
@ -2699,7 +2699,7 @@ pub fn is_in_test_function(tcx: TyCtxt<'_>, id: HirId) -> bool {
/// use [`is_in_cfg_test`]
pub fn is_cfg_test(tcx: TyCtxt<'_>, id: HirId) -> bool {
tcx.hir_attrs(id).iter().any(|attr| {
if attr.has_name(sym::cfg)
if attr.has_name(sym::cfg_trace)
&& let Some(items) = attr.meta_item_list()
&& let [item] = &*items
&& item.has_name(sym::test)
@ -2723,11 +2723,11 @@ pub fn is_in_test(tcx: TyCtxt<'_>, hir_id: HirId) -> bool {
/// Checks if the item of any of its parents has `#[cfg(...)]` attribute applied.
pub fn inherits_cfg(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
tcx.has_attr(def_id, sym::cfg)
tcx.has_attr(def_id, sym::cfg_trace)
|| tcx
.hir_parent_iter(tcx.local_def_id_to_hir_id(def_id))
.flat_map(|(parent_id, _)| tcx.hir_attrs(parent_id))
.any(|attr| attr.has_name(sym::cfg))
.any(|attr| attr.has_name(sym::cfg_trace))
}
/// Walks up the HIR tree from the given expression in an attempt to find where the value is

View file

@ -22,6 +22,7 @@ pub mod util;
use core::panic;
use std::collections::HashSet;
use std::ffi::OsString;
use std::fmt::Write;
use std::io::{self, ErrorKind};
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
@ -570,18 +571,22 @@ pub fn run_tests(config: Arc<Config>) {
// easy to miss which tests failed, and as such fail to reproduce
// the failure locally.
println!(
"Some tests failed in compiletest suite={}{} mode={} host={} target={}",
config.suite,
config
.compare_mode
.as_ref()
.map(|c| format!(" compare_mode={:?}", c))
.unwrap_or_default(),
config.mode,
config.host,
config.target
);
let mut msg = String::from("Some tests failed in compiletest");
write!(msg, " suite={}", config.suite).unwrap();
if let Some(compare_mode) = config.compare_mode.as_ref() {
write!(msg, " compare_mode={}", compare_mode).unwrap();
}
if let Some(pass_mode) = config.force_pass_mode.as_ref() {
write!(msg, " pass_mode={}", pass_mode).unwrap();
}
write!(msg, " mode={}", config.mode).unwrap();
write!(msg, " host={}", config.host).unwrap();
write!(msg, " target={}", config.target).unwrap();
println!("{msg}");
std::process::exit(1);
}

View file

@ -10,7 +10,6 @@ extern crate std;
//@ pp-exact:tests-are-sorted.pp
extern crate test;
#[cfg(test)]
#[rustc_test_marker = "m_test"]
#[doc(hidden)]
pub const m_test: test::TestDescAndFn =
@ -35,7 +34,6 @@ pub const m_test: test::TestDescAndFn =
fn m_test() {}
extern crate test;
#[cfg(test)]
#[rustc_test_marker = "z_test"]
#[doc(hidden)]
pub const z_test: test::TestDescAndFn =
@ -61,7 +59,6 @@ pub const z_test: test::TestDescAndFn =
fn z_test() {}
extern crate test;
#[cfg(test)]
#[rustc_test_marker = "a_test"]
#[doc(hidden)]
pub const a_test: test::TestDescAndFn =

View file

@ -29,7 +29,6 @@ macro_rules! generate_s10 {
($expr: expr) => {
#[cfg(feature = $expr)]
//~^ ERROR expected unsuffixed literal, found expression `concat!("nonexistent")`
//~| ERROR expected unsuffixed literal, found expression `concat!("nonexistent")`
struct S10;
}
}

View file

@ -65,19 +65,7 @@ LL | generate_s10!(concat!("nonexistent"));
|
= note: this error originates in the macro `generate_s10` (in Nightly builds, run with -Z macro-backtrace for more info)
error: expected unsuffixed literal, found expression `concat!("nonexistent")`
--> $DIR/cfg-attr-syntax-validation.rs:30:25
|
LL | #[cfg(feature = $expr)]
| ^^^^^
...
LL | generate_s10!(concat!("nonexistent"));
| ------------------------------------- in this macro invocation
|
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
= note: this error originates in the macro `generate_s10` (in Nightly builds, run with -Z macro-backtrace for more info)
error: aborting due to 11 previous errors
error: aborting due to 10 previous errors
Some errors have detailed explanations: E0537, E0565.
For more information about an error, try `rustc --explain E0537`.

View file

@ -1,11 +1,12 @@
// This was triggering an assertion failure in `NodeRange::new`.
//@ check-pass
#![feature(cfg_eval)]
#![feature(stmt_expr_attributes)]
fn f() -> u32 {
#[cfg_eval] #[cfg(not(FALSE))] 0
//~^ ERROR removing an expression is not supported in this position
}
fn main() {}

View file

@ -1,8 +0,0 @@
error: removing an expression is not supported in this position
--> $DIR/invalid-node-range-issue-129166.rs:7:17
|
LL | #[cfg_eval] #[cfg(not(FALSE))] 0
| ^^^^^^^^^^^^^^^^^^
error: aborting due to 1 previous error

View file

@ -2,7 +2,6 @@ macro_rules! mac {
($attr_item: meta) => {
#[cfg($attr_item)]
//~^ ERROR expected unsuffixed literal, found `meta` metavariable
//~| ERROR expected unsuffixed literal, found `meta` metavariable
struct S;
}
}
@ -11,7 +10,6 @@ mac!(an(arbitrary token stream));
#[cfg(feature = -1)]
//~^ ERROR expected unsuffixed literal, found `-`
//~| ERROR expected unsuffixed literal, found `-`
fn handler() {}
fn main() {}

View file

@ -1,5 +1,5 @@
error: expected unsuffixed literal, found `-`
--> $DIR/attr-bad-meta-4.rs:12:17
--> $DIR/attr-bad-meta-4.rs:11:17
|
LL | #[cfg(feature = -1)]
| ^
@ -15,25 +15,5 @@ LL | mac!(an(arbitrary token stream));
|
= note: this error originates in the macro `mac` (in Nightly builds, run with -Z macro-backtrace for more info)
error: expected unsuffixed literal, found `meta` metavariable
--> $DIR/attr-bad-meta-4.rs:3:15
|
LL | #[cfg($attr_item)]
| ^^^^^^^^^^
...
LL | mac!(an(arbitrary token stream));
| -------------------------------- in this macro invocation
|
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
= note: this error originates in the macro `mac` (in Nightly builds, run with -Z macro-backtrace for more info)
error: expected unsuffixed literal, found `-`
--> $DIR/attr-bad-meta-4.rs:12:17
|
LL | #[cfg(feature = -1)]
| ^
|
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
error: aborting due to 4 previous errors
error: aborting due to 2 previous errors

View file

@ -3,6 +3,7 @@
//@ check-pass
//@ proc-macro: test-macros.rs
#![feature(cfg_boolean_literals)]
#![feature(cfg_eval)]
#[macro_use]
@ -10,8 +11,13 @@ extern crate test_macros;
#[cfg_eval]
#[test_macros::print_attr]
#[cfg_attr(FALSE, test_macros::print_attr)]
#[cfg_attr(all(), test_macros::print_attr)]
#[cfg_attr(false, test_macros::print_attr)]
#[cfg_attr(true, test_macros::print_attr)]
struct S;
#[cfg_eval]
#[test_macros::print_attr]
#[cfg(true)]
struct Z;
fn main() {}

View file

@ -4,59 +4,75 @@ PRINT-ATTR INPUT (DEBUG): TokenStream [
Punct {
ch: '#',
spacing: Alone,
span: #0 bytes(271..272),
span: #0 bytes(305..306),
},
Group {
delimiter: Bracket,
stream: TokenStream [
Ident {
ident: "test_macros",
span: #0 bytes(289..300),
span: #0 bytes(322..333),
},
Punct {
ch: ':',
spacing: Joint,
span: #0 bytes(300..301),
span: #0 bytes(333..334),
},
Punct {
ch: ':',
spacing: Alone,
span: #0 bytes(301..302),
span: #0 bytes(334..335),
},
Ident {
ident: "print_attr",
span: #0 bytes(302..312),
span: #0 bytes(335..345),
},
],
span: #0 bytes(272..314),
span: #0 bytes(306..347),
},
Ident {
ident: "struct",
span: #0 bytes(315..321),
span: #0 bytes(348..354),
},
Ident {
ident: "S",
span: #0 bytes(322..323),
span: #0 bytes(355..356),
},
Punct {
ch: ';',
spacing: Alone,
span: #0 bytes(323..324),
span: #0 bytes(356..357),
},
]
PRINT-ATTR INPUT (DISPLAY): struct S;
PRINT-ATTR INPUT (DEBUG): TokenStream [
Ident {
ident: "struct",
span: #0 bytes(315..321),
span: #0 bytes(348..354),
},
Ident {
ident: "S",
span: #0 bytes(322..323),
span: #0 bytes(355..356),
},
Punct {
ch: ';',
spacing: Alone,
span: #0 bytes(323..324),
span: #0 bytes(356..357),
},
]
PRINT-ATTR INPUT (DISPLAY): struct Z;
PRINT-ATTR INPUT (DEBUG): TokenStream [
Ident {
ident: "struct",
span: #0 bytes(411..417),
},
Ident {
ident: "Z",
span: #0 bytes(418..419),
},
Punct {
ch: ';',
spacing: Alone,
span: #0 bytes(419..420),
},
]

View file

@ -0,0 +1,25 @@
struct PersonOnlyName {
name: String
}
struct PersonWithAge {
name: String,
age: u8,
height: u8,
}
fn main() {
let wilfred = PersonOnlyName("Name1".to_owned());
//~^ ERROR expected function, tuple struct or tuple variant, found struct `PersonOnlyName` [E0423]
let bill = PersonWithAge( //~ ERROR expected function, tuple struct or tuple variant, found struct `PersonWithAge` [E0423]
"Name2".to_owned(),
20,
180,
);
let person = PersonWithAge("Name3".to_owned());
//~^ ERROR expected function, tuple struct or tuple variant, found struct `PersonWithAge` [E0423]
}

View file

@ -0,0 +1,58 @@
error[E0423]: expected function, tuple struct or tuple variant, found struct `PersonOnlyName`
--> $DIR/struct-construct-with-call-issue-138931.rs:14:19
|
LL | / struct PersonOnlyName {
LL | | name: String
LL | | }
| |_- `PersonOnlyName` defined here
...
LL | let wilfred = PersonOnlyName("Name1".to_owned());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: use struct literal syntax instead of calling
|
LL - let wilfred = PersonOnlyName("Name1".to_owned());
LL + let wilfred = PersonOnlyName{name: "Name1".to_owned()};
|
error[E0423]: expected function, tuple struct or tuple variant, found struct `PersonWithAge`
--> $DIR/struct-construct-with-call-issue-138931.rs:17:16
|
LL | / struct PersonWithAge {
LL | | name: String,
LL | | age: u8,
LL | | height: u8,
LL | | }
| |_- `PersonWithAge` defined here
...
LL | let bill = PersonWithAge(
| ________________^
LL | | "Name2".to_owned(),
LL | | 20,
LL | | 180,
LL | | );
| |_____^
|
help: use struct literal syntax instead of calling
|
LL ~ let bill = PersonWithAge{name: "Name2".to_owned(),
LL ~ age: 20,
LL ~ height: 180};
|
error[E0423]: expected function, tuple struct or tuple variant, found struct `PersonWithAge`
--> $DIR/struct-construct-with-call-issue-138931.rs:23:18
|
LL | / struct PersonWithAge {
LL | | name: String,
LL | | age: u8,
LL | | height: u8,
LL | | }
| |_- `PersonWithAge` defined here
...
LL | let person = PersonWithAge("Name3".to_owned());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use struct literal syntax instead: `PersonWithAge { name: val, age: val, height: val }`
error: aborting due to 3 previous errors
For more information about this error, try `rustc --explain E0423`.