Skip to content
Closed
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
7 changes: 7 additions & 0 deletions compiler/rustc_hir_analysis/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ pub(crate) use precise_captures::*;

pub(crate) mod remove_or_use_generic;

#[derive(Diagnostic)]
#[diag("complex const arguments must be placed inside of a `const` block")]
pub(crate) struct ComplexConstArg {
#[primary_span]
pub span: Span,
}

#[derive(Diagnostic)]
#[diag("ambiguous associated {$assoc_kind} `{$assoc_ident}` in bounds of `{$qself}`")]
pub(crate) struct AmbiguousAssocItem<'a> {
Expand Down
36 changes: 32 additions & 4 deletions compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// ignore-tidy-file-filelength

//! HIR ty lowering: Lowers type-system entities[^1] from the [HIR][hir] to
//! the [`rustc_middle::ty`] representation.
//!
Expand Down Expand Up @@ -2523,6 +2525,31 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
ty::Const::new_value(tcx, valtree, ty)
}

fn try_recover_misrepresented_function_call(
&self,
hir_self_ty: &hir::Ty<'_>,
span: Span,
) -> Option<ErrorGuaranteed> {
// Only an enum can host a tuple-variant constructor (`<Option<u32>>::Some(..)`).
// For any other self type, a type-relative call is an associated function, not a
// constructor, and must be wrapped in `const { ... }`. We catch that here, before
// lowering the self type, so a generic struct/union written without its args
// (`FieldName::len()`, from `tracing`'s macros) reports this clear error instead
// of a spurious E0107 "missing generics" (#157152), and a primitive or foreign
// type reports it instead of an opaque downstream resolution error. Enums,
// aliases, `Self` and type parameters are let through: each may resolve to an
// enum, so they must reach constructor lowering.
let self_ty_res = match hir_self_ty.kind {
hir::TyKind::Path(hir::QPath::Resolved(_, path)) => path.res,
_ => Res::Err,
};
matches!(
self_ty_res,
Res::Def(DefKind::Struct | DefKind::Union | DefKind::ForeignTy, _) | Res::PrimTy(_)
)
.then(|| self.dcx().emit_err(diagnostics::ComplexConstArg { span }))
}

fn lower_const_arg_tuple_call(
&self,
hir_id: HirId,
Expand All @@ -2543,6 +2570,10 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
self.lower_resolved_const_path(opt_self_ty, path, hir_id)
}
hir::QPath::TypeRelative(hir_self_ty, segment) => {
if let Some(e) = self.try_recover_misrepresented_function_call(hir_self_ty, span) {
return ty::Const::new_error(tcx, e);
}

let self_ty = self.lower_ty(hir_self_ty);
match self.lower_type_relative_const_path(
self_ty,
Expand Down Expand Up @@ -2576,10 +2607,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
(tcx.adt_def(parent_did), fn_args, parent_did)
}
_ => {
let e = self.dcx().span_err(
span,
"complex const arguments must be placed inside of a `const` block",
);
let e = self.dcx().emit_err(diagnostics::ComplexConstArg { span });
return Const::new_error(tcx, e);
}
};
Expand Down
6 changes: 6 additions & 0 deletions library/alloc/src/vec/partial_eq.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ __impl_slice_eq1! { [A: Allocator] Cow<'_, [T]>, Vec<U, A> where T: Clone, #[sta
__impl_slice_eq1! { [] Cow<'_, [T]>, &[U] where T: Clone, #[stable(feature = "rust1", since = "1.0.0")] }
#[cfg(not(no_global_oom_handling))]
__impl_slice_eq1! { [] Cow<'_, [T]>, &mut [U] where T: Clone, #[stable(feature = "rust1", since = "1.0.0")] }
#[cfg(not(no_global_oom_handling))]
__impl_slice_eq1! { [A: Allocator] Vec<T, A>, Cow<'_, [U]> where U: Clone, #[stable(feature = "partialeq_cow_for_vec_and_slice", since = "CURRENT_RUSTC_VERSION")] }
#[cfg(not(no_global_oom_handling))]
__impl_slice_eq1! { [] &[T], Cow<'_, [U]> where U: Clone, #[stable(feature = "partialeq_cow_for_vec_and_slice", since = "CURRENT_RUSTC_VERSION")] }
#[cfg(not(no_global_oom_handling))]
__impl_slice_eq1! { [] &mut [T], Cow<'_, [U]> where U: Clone, #[stable(feature = "partialeq_cow_for_vec_and_slice", since = "CURRENT_RUSTC_VERSION")] }
__impl_slice_eq1! { const, [A: Allocator, const N: usize] Vec<T, A>, [U; N], #[rustc_const_unstable(feature = "const_cmp", issue = "143800")] #[stable(feature = "rust1", since = "1.0.0")] }
__impl_slice_eq1! { const, [A: Allocator, const N: usize] Vec<T, A>, &[U; N], #[rustc_const_unstable(feature = "const_cmp", issue = "143800")] #[stable(feature = "rust1", since = "1.0.0")] }

Expand Down
19 changes: 19 additions & 0 deletions library/alloctests/tests/vec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1339,6 +1339,25 @@ fn test_from_cow() {
assert_eq!(Vec::from(Cow::Owned(owned)), vec!["owned", "(vec)"]);
}

#[test]
fn test_partial_eq_cow_symmetric() {
let v: Vec<i32> = vec![1, 2, 3];
let c: Cow<'_, [i32]> = Cow::Borrowed(&[1, 2, 3]);

assert_eq!(c, v);
assert_eq!(v, c);

let s: &[i32] = &[1, 2, 3];
assert_eq!(s, c);

let mut arr = [1, 2, 3];
let ms: &mut [i32] = &mut arr;
assert_eq!(ms, c);

let v2: Vec<i32> = vec![1, 2, 4];
assert!(v2 != c);
}

#[allow(dead_code)]
fn assert_covariance() {
fn drain<'new>(d: Drain<'static, &'static str>) -> Drain<'new, &'new str> {
Expand Down
3 changes: 3 additions & 0 deletions src/bootstrap/src/core/build_steps/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2565,6 +2565,9 @@ Please disable assertions with `rust.debug-assertions = false`.
if builder.config.rust_optimize_tests {
cmd.arg("--optimize-tests");
}
if !builder.config.docs_minification {
cmd.arg("--disable-minification");
}
if builder.config.rust_randomize_layout {
cmd.arg("--rust-randomized-layout");
}
Expand Down
4 changes: 4 additions & 0 deletions src/tools/compiletest/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,9 @@ struct Args {
/// Run tests with optimizations enabled.
#[arg(long)]
optimize_tests: bool,
/// Pass `--disable-minification` to rustdoc when generating docs for tests.
#[arg(long)]
disable_minification: bool,
/// Run tests verbosely, showing all output.
#[arg(long)]
verbose: bool,
Expand Down Expand Up @@ -441,6 +444,7 @@ pub(crate) fn parse_config(args: Vec<String>) -> Config {
cxxflags: args.cxxflags,
default_codegen_backend,
diff_command: args.compiletest_diff_tool,
disable_minification: args.disable_minification,

edition: args.edition,

Expand Down
5 changes: 5 additions & 0 deletions src/tools/compiletest/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,11 @@ pub(crate) struct Config {
/// *only* applied to the [`PassFailMode::RunPass`] test crate and not its auxiliaries.
pub(crate) optimize_tests: bool,

/// Whether rustdoc should disable CSS/JS minification when generating docs for tests.
///
/// Forwarded from bootstrap's `build.docs-minification = false`.
pub(crate) disable_minification: bool,

/// Target platform tuple.
pub(crate) target: String,

Expand Down
18 changes: 18 additions & 0 deletions src/tools/compiletest/src/directives/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ struct ConfigBuilder {
rustc_debug_assertions: bool,
std_debug_assertions: bool,
std_remap_debuginfo: bool,
disable_minification: bool,
}

impl ConfigBuilder {
Expand Down Expand Up @@ -200,6 +201,11 @@ impl ConfigBuilder {
self
}

fn disable_minification(&mut self, is_enabled: bool) -> &mut Self {
self.disable_minification = is_enabled;
self
}

fn build(&mut self) -> Config {
let args = &[
"compiletest",
Expand Down Expand Up @@ -266,6 +272,9 @@ impl ConfigBuilder {
if self.std_remap_debuginfo {
args.push("--with-std-remap-debuginfo".to_owned());
}
if self.disable_minification {
args.push("--disable-minification".to_owned());
}

args.push("--rustc-path".to_string());
args.push(std::env::var("TEST_RUSTC").expect("must be configured by bootstrap"));
Expand Down Expand Up @@ -309,6 +318,15 @@ fn should_fail() {
assert_eq!(d.should_fail, ShouldFail::Yes);
}

#[test]
fn disable_minification_flag() {
let config: Config = cfg().build();
assert!(!config.disable_minification);

let config: Config = cfg().disable_minification(true).build();
assert!(config.disable_minification);
}

#[test]
fn revisions() {
let config: Config = cfg().build();
Expand Down
19 changes: 18 additions & 1 deletion src/tools/compiletest/src/runtest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1049,10 +1049,18 @@ impl<'test> TestCx<'test> {
match kind {
DocKind::Html => {}
DocKind::Json => {
rustdoc.arg("--output-format").arg("json").arg("-Zunstable-options");
rustdoc.arg("--output-format").arg("json");
}
}

// Both JSON output and `--disable-minification` are unstable rustdoc options.
if matches!(kind, DocKind::Json) || self.config.disable_minification {
rustdoc.arg("-Zunstable-options");
}
if self.config.disable_minification {
rustdoc.arg("--disable-minification");
}

if let Some(ref linker) = self.config.target_linker {
rustdoc.arg(format!("-Clinker={}", linker));
}
Expand Down Expand Up @@ -1611,6 +1619,15 @@ impl<'test> TestCx<'test> {
compiler.arg("-Zwasm-proc-macros");
}

// `--disable-minification` is an unstable rustdoc option. Rustdoc UI tests intentionally
// exercise diagnostics for unstable options, so don't enable them for that suite.
if compiler_kind == CompilerKind::Rustdoc
&& self.config.disable_minification
&& self.config.mode != TestMode::Ui
{
compiler.arg("-Zunstable-options").arg("--disable-minification");
}

// Hide libstd sources from ui tests to make sure we generate the stderr
// output that users will see.
// Without this, we may be producing good diagnostics in-tree but users
Expand Down
1 change: 1 addition & 0 deletions src/tools/compiletest/src/rustdoc_gui_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ fn incomplete_config_for_rustdoc_gui_test() -> Config {
target_rustcflags: Default::default(),
rust_randomized_layout: Default::default(),
optimize_tests: Default::default(),
disable_minification: Default::default(),
target: Default::default(),
host: Default::default(),
cdb: Default::default(),
Expand Down
84 changes: 84 additions & 0 deletions tests/ui/const-generics/gca/direct-const-arg-fn-call.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
//! Regression test for #157152.
//!
//! Under `min_generic_const_args` with `macroless_generic_const_args`, a braced const
//! argument containing an associated-function call (e.g. `FieldName::len()`, as generated
//! by `tracing`'s logging macros as `FieldName<{ FieldName::len(name) }>`) was lowered as
//! a tuple-struct constructor. Lowering the callee's `Self` type `FieldName`, written
//! without its `const N: usize` argument, then produced a spurious "missing generics"
//! error (E0107) plus follow-on errors, which made `tracing` fail to compile in any crate
//! enabling the feature.
//!
//! It should instead report that the call must be wrapped in a `const` block, and
//! the wrapped form must compile. The same holds for any self type that cannot host a
//! tuple-variant constructor (unions, primitives, foreign types), not just structs.
//@ compile-flags: -Znext-solver

#![feature(min_generic_const_args, macroless_generic_const_args)]
#![feature(generic_const_args)]
#![feature(extern_types)]
#![expect(incomplete_features)]

struct FieldName<const N: usize>([u8; N]);

impl FieldName<0> {
const fn len() -> usize {
5
}

const fn len_of(name: &str) -> usize {
name.len()
}
}

// The associated-function call is not a constructor, so the bare braces are
// rejected with a clear diagnostic instead of a spurious "missing generics" error.
fn bad(_: FieldName<{ FieldName::len() }>) {}
//~^ ERROR complex const arguments must be placed inside of a `const` block

// Wrapping the call in a `const` block makes it an anonymous const and compiles.
fn good(_: FieldName<{ const { FieldName::len() } }>) {}

// The exact shape from #157152: `tracing`'s macros expand a field name to
// `FieldName::len(stringify!(field))`. Same as `bad` but with a string argument, which
// the diagnostic ignores; the self type is still a bare generic struct.
fn bad_tracing(_: FieldName<{ FieldName::len_of("id") }>) {}
//~^ ERROR complex const arguments must be placed inside of a `const` block

fn good_tracing(_: FieldName<{ const { FieldName::len_of("id") } }>) {}

union Tag<const N: usize> {
bytes: [u8; N],
}

impl Tag<0> {
const fn width() -> usize {
7
}
}

// Unions behave exactly like structs: the call is an associated function, not a
// constructor, so the bare braces are rejected the same way.
fn bad_union(_: Tag<{ Tag::width() }>) {}
//~^ ERROR complex const arguments must be placed inside of a `const` block

fn good_union(_: Tag<{ const { Tag::width() } }>) {}

// A primitive can't host a constructor either, and has no generics to omit, so it never
// hits the "missing generics" path. No `good_` counterpart: `from_str_radix` returns a
// `Result`, not a `usize`, so the wrapped form can't form a valid const arg. This case
// only checks that the bare form is rejected.
fn bad_prim(_: FieldName<{ u32::from_str_radix("10", 10) }>) {}
//~^ ERROR complex const arguments must be placed inside of a `const` block

unsafe extern "C" {
type Opaque;
}

// A foreign type has no constructor and no inherent associated functions. The guard
// rejects it from the self type's resolution alone, before the `foo` segment is resolved.
// Without that, downstream resolution gives an opaque "invalid base path" error (plus an
// E0223) rather than this clear one.
fn bad_foreign(_: FieldName<{ Opaque::foo() }>) {}
//~^ ERROR complex const arguments must be placed inside of a `const` block

fn main() {}
32 changes: 32 additions & 0 deletions tests/ui/const-generics/gca/direct-const-arg-fn-call.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
error: complex const arguments must be placed inside of a `const` block
--> $DIR/direct-const-arg-fn-call.rs:35:23
|
LL | fn bad(_: FieldName<{ FieldName::len() }>) {}
| ^^^^^^^^^^^^^^^^

error: complex const arguments must be placed inside of a `const` block
--> $DIR/direct-const-arg-fn-call.rs:44:31
|
LL | fn bad_tracing(_: FieldName<{ FieldName::len_of("id") }>) {}
| ^^^^^^^^^^^^^^^^^^^^^^^

error: complex const arguments must be placed inside of a `const` block
--> $DIR/direct-const-arg-fn-call.rs:61:23
|
LL | fn bad_union(_: Tag<{ Tag::width() }>) {}
| ^^^^^^^^^^^^

error: complex const arguments must be placed inside of a `const` block
--> $DIR/direct-const-arg-fn-call.rs:70:28
|
LL | fn bad_prim(_: FieldName<{ u32::from_str_radix("10", 10) }>) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: complex const arguments must be placed inside of a `const` block
--> $DIR/direct-const-arg-fn-call.rs:81:31
|
LL | fn bad_foreign(_: FieldName<{ Opaque::foo() }>) {}
| ^^^^^^^^^^^^^

error: aborting due to 5 previous errors

6 changes: 3 additions & 3 deletions tests/ui/consts/too_generic_eval_ice.current.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,15 @@ LL | [5; Self::HOST_SIZE] == [6; 0]
|
= help: the trait `PartialEq<[{integer}; 0]>` is not implemented for `[{integer}; Self::HOST_SIZE]`
= help: the following other types implement trait `PartialEq<Rhs>`:
`&[T]` implements `PartialEq<Cow<'_, [U]>>`
`&[T]` implements `PartialEq<Vec<U, A>>`
`&[T]` implements `PartialEq<[U; N]>`
`&[u8; N]` implements `PartialEq<ByteStr>`
`&[u8; N]` implements `PartialEq<ByteString>`
`&[u8]` implements `PartialEq<ByteStr>`
`&[u8]` implements `PartialEq<ByteString>`
`&mut [T]` implements `PartialEq<Vec<U, A>>`
`&mut [T]` implements `PartialEq<[U; N]>`
and 11 others
`&mut [T]` implements `PartialEq<Cow<'_, [U]>>`
and 13 others

error: aborting due to 4 previous errors

Expand Down
Loading
Loading