unlock unlimited code runs for $79, limited time →
NOSSLETTER.techLIVE
back to OSSpedia
rust-lang/rust
senior7/10
configuration-managementcompiler-internalstesting-strategy

Enable -Znext-solver on nightly by default

Project: rust-lang/rust | Language: Rust | Concepts: configuration-management, compiler-internals, testing-strategy | Difficulty: senior
Link: https://github.com/rust-lang/rust/pull/160619

The problem

The Rust compiler has a new trait solver, enabled via the unstable -Znext-solver flag. The goal is to make this new solver the default on nightly builds for broader testing. However, some features (like generic_const_exprs) are not yet fully compatible with the new solver, and the test suite and tools expect the old behavior. The challenge is to enable the new solver by default on nightly, but ensure that incompatible features and tests still use the old solver, with clear diagnostics.

Why it's non-trivial

A naive approach would simply flip the default for -Znext-solver on nightly. This would break many tests, tools, and workflows that rely on the old solver, especially for features that are not yet supported by the new solver. Care must be taken to:

  • Enable the new solver only when appropriate (e.g., not for tests or incompatible features).
  • Provide clear warnings or fallbacks when the new solver cannot be used.
  • Ensure that the change is easily reversible and does not affect stable or beta channels.

The approach

1. Make the new solver the default on nightly via build configuration

The default for NextSolverConfig is changed to enable globally if the build environment variable is set (which is only true for nightly):

+impl Default for NextSolverConfig {
+    fn default() -> Self {
+        if option_env!("CFG_DEFAULT_NEXT_SOLVER_GLOBALLY").is_some() {
+            Self { coherence: true, globally: true }
+        } else {
+            Self { coherence: true, globally: false }
+        }
+    }
+}

This code checks for the CFG_DEFAULT_NEXT_SOLVER_GLOBALLY environment variable, set only on nightly builds, to decide whether to enable the new solver globally by default.

In the build system, this variable is set for nightly/dev channels:

+    if nightly {
+        // We want to enable Polonius Alpha and Next Trait Solver by default on nighty
+        cargo.env("CFG_DEFAULT_POLONIUS_NEXT", "1");
+        cargo.env("CFG_DEFAULT_NEXT_SOLVER_GLOBALLY", "1");
+    }

This ensures the nightly channel sets the env var, triggering the new default.

2. Downgrade the solver and warn when incompatible features are used

Instead of aborting compilation when generic_const_exprs is used with the new solver, emit a warning and revert to the old solver:

-fn check_new_solver_banned_features(sess: &Session, features: &Features) {
+fn warn_next_solver_and_gce(sess: &Session, features: &Features) {
     if !sess.opts.unstable_opts.next_solver.globally {
         return;
     }
 
-    // Ban GCE with the new solver, because it does not implement GCE correctly.
+    // Warn people who uses GCE and -Znext-solver=globally
+    // that their trait solver was downgraded to -Znext-solver=no
     if let Some(gce_span) = features
         .enabled_lang_features()
         .iter()
         .find(|feat| feat.gate_name == sym::generic_const_exprs)
         .map(|feat| feat.attr_sp)
     {
-        // Abort immediately, otherwise GCE can lower to `ConstKind::Expr`,
-        // which the new solver intentionally does not support.
-        #[allow(rustc::symbol_intern_string_literal)]
-        sess.dcx().emit_fatal(diagnostics::IncompatibleFeatures {
-            spans: vec![gce_span],
-            f1: Symbol::intern("-Znext-solver=globally"),
-            f2: sym::generic_const_exprs,
-        });
+        sess.dcx()
+            .emit_warn(diagnostics::NextSolverDisabledForGenericConstExprs { span: gce_span });
     }
 }

This changes a fatal error to a warning, and documents the fallback to the old solver.

A new diagnostic struct is added for this warning:

+#[derive(Diagnostic)]
+#[diag("`feature(generic_const_exprs)` is not supported with the next-generation trait solver")]
+#[note("`-Znext-solver=globally` is currently enabled by default for testing")]
+#[note("reverted the setting to `-Znext-solver=coherence` for this crate")]
+#[note("the currently stable trait solver will be used for this crate")]
+#[note("see issues #160895 <https://github.com/rust-lang/rust/issues/160895> for more information")]
+pub(crate) struct NextSolverDisabledForGenericConstExprs {
+    #[primary_span]
+    pub span: Span,
+}

This provides a clear, actionable warning to users.

3. Ensure the old solver is used for tests and tools

Many test runners and tools are updated to explicitly pass -Znext-solver=coherence to avoid accidental breakage:

+    // FIXME(#160895): While the new solver is enabled by default on nightly,
+    // we don't want to use it in our tests for now.
+    cmd.arg("-Znext-solver=coherence");

This pattern is repeated in various test and tool harnesses (compiletest, clippy, miri, lint-docs, etc.) to ensure consistent test results.

4. Adjust tests and diagnostics to expect warnings, not errors

Tests that previously expected a hard error now expect a warning and fallback behavior:

-//~^ ERROR `-Znext-solver=globally` and `generic_const_exprs` are incompatible
+//~^ WARN: `feature(generic_const_exprs)` is not supported with the next-generation trait solver

The test and its stderr output are updated to match the new, softer diagnostic.

5. Guard test code that assumes the old default

Some internal tests are skipped or adjusted if the new solver is enabled by default:

+    // FIXME(#160895): We don't test this when the next-solver is enabled by default.
+    if option_env!("CFG_DEFAULT_NEXT_SOLVER_GLOBALLY").is_none() {
+        tracked!(next_solver, NextSolverConfig { coherence: true, globally: true });
+    }

This prevents false positives in tests that assume the old default.

From the review thread

No notable review discussion on this PR.

Takeaways

  • Feature flag rollouts should be opt-in for tests and tools until full compatibility is ensured. When changing a default that affects core behavior, explicitly override it in test and tool harnesses to avoid widespread breakage.
  • Graceful degradation with clear diagnostics is preferable to abrupt failure when enabling experimental features. Instead of aborting, emit warnings and document fallback behavior to help users understand what is happening.
  • Use environment variables and build-time configuration to control experimental defaults by channel. This allows for easy toggling and ensures that changes are isolated to intended builds (e.g., nightly vs. stable).