Auto merge of #122483 - matthiaskrgr:rollup-n07dsh5, r=matthiaskrgr

Rollup of 9 pull requests

Successful merges:

 - #104353 (Add CStr::bytes iterator)
 - #119676 (rustdoc-search: search types by higher-order functions)
 - #120699 (Document `TRACK_DIAGNOSTIC` calls.)
 - #121899 (Document how removing a type's field can be bad and what to do instead)
 - #122405 (Add methods to create StableMIR constant)
 - #122416 (Various style improvements to `rustc_lint::levels`)
 - #122421 (Improve `Step` docs)
 - #122440 (const-eval: organize and extend tests for required-consts)
 - #122461 (fix unsoundness in Step::forward_unchecked for signed integers)

Failed merges:

 - #122397 (Various cleanups around the const eval query providers)

r? `@ghost`
`@rustbot` modify labels: rollup
This commit is contained in:
bors 2024-03-14 12:17:08 +00:00
commit c2fbe404d2
46 changed files with 2226 additions and 446 deletions

View File

@ -2086,7 +2086,7 @@ impl HumanEmitter {
}
if !self.short_message {
for child in children {
assert!(child.level.can_be_top_or_sub().1);
assert!(child.level.can_be_subdiag());
let span = &child.span;
if let Err(err) = self.emit_messages_default_inner(
span,

View File

@ -526,12 +526,15 @@ pub enum StashKey {
UndeterminedMacroResolution,
}
fn default_track_diagnostic(diag: DiagInner, f: &mut dyn FnMut(DiagInner)) {
fn default_track_diagnostic<R>(diag: DiagInner, f: &mut dyn FnMut(DiagInner) -> R) -> R {
(*f)(diag)
}
pub static TRACK_DIAGNOSTIC: AtomicRef<fn(DiagInner, &mut dyn FnMut(DiagInner))> =
AtomicRef::new(&(default_track_diagnostic as _));
/// Diagnostics emitted by `DiagCtxtInner::emit_diagnostic` are passed through this function. Used
/// for tracking by incremental, to replay diagnostics as necessary.
pub static TRACK_DIAGNOSTIC: AtomicRef<
fn(DiagInner, &mut dyn FnMut(DiagInner) -> Option<ErrorGuaranteed>) -> Option<ErrorGuaranteed>,
> = AtomicRef::new(&(default_track_diagnostic as _));
#[derive(Copy, Clone, Default)]
pub struct DiagCtxtFlags {
@ -1422,74 +1425,103 @@ impl DiagCtxtInner {
// Return value is only `Some` if the level is `Error` or `DelayedBug`.
fn emit_diagnostic(&mut self, mut diagnostic: DiagInner) -> Option<ErrorGuaranteed> {
assert!(diagnostic.level.can_be_top_or_sub().0);
if let Some(expectation_id) = diagnostic.level.get_expectation_id() {
// The `LintExpectationId` can be stable or unstable depending on when it was created.
// Diagnostics created before the definition of `HirId`s are unstable and can not yet
// be stored. Instead, they are buffered until the `LintExpectationId` is replaced by
// a stable one by the `LintLevelsBuilder`.
if let LintExpectationId::Unstable { .. } = expectation_id {
self.unstable_expect_diagnostics.push(diagnostic);
return None;
}
self.suppressed_expected_diag = true;
self.fulfilled_expectations.insert(expectation_id.normalize());
}
if diagnostic.has_future_breakage() {
// Future breakages aren't emitted if they're `Level::Allow`,
// but they still need to be constructed and stashed below,
// so they'll trigger the must_produce_diag check.
self.suppressed_expected_diag = true;
assert!(matches!(diagnostic.level, Error | Warning | Allow));
self.future_breakage_diagnostics.push(diagnostic.clone());
}
// Note that because this comes before the `match` below,
// `-Zeagerly-emit-delayed-bugs` continues to work even after we've
// issued an error and stopped recording new delayed bugs.
if diagnostic.level == DelayedBug && self.flags.eagerly_emit_delayed_bugs {
diagnostic.level = Error;
}
// We call TRACK_DIAGNOSTIC with an empty closure for the cases that
// return early *and* have some kind of side-effect, except where
// noted.
match diagnostic.level {
// This must come after the possible promotion of `DelayedBug` to
// `Error` above.
Fatal | Error if self.treat_next_err_as_bug() => {
diagnostic.level = Bug;
Bug => {}
Fatal | Error => {
if self.treat_next_err_as_bug() {
// `Fatal` and `Error` can be promoted to `Bug`.
diagnostic.level = Bug;
}
}
DelayedBug => {
// If we have already emitted at least one error, we don't need
// to record the delayed bug, because it'll never be used.
return if let Some(guar) = self.has_errors() {
Some(guar)
// Note that because we check these conditions first,
// `-Zeagerly-emit-delayed-bugs` and `-Ztreat-err-as-bug`
// continue to work even after we've issued an error and
// stopped recording new delayed bugs.
if self.flags.eagerly_emit_delayed_bugs {
// `DelayedBug` can be promoted to `Error` or `Bug`.
if self.treat_next_err_as_bug() {
diagnostic.level = Bug;
} else {
diagnostic.level = Error;
}
} else {
let backtrace = std::backtrace::Backtrace::capture();
// This `unchecked_error_guaranteed` is valid. It is where the
// `ErrorGuaranteed` for delayed bugs originates. See
// `DiagCtxtInner::drop`.
#[allow(deprecated)]
let guar = ErrorGuaranteed::unchecked_error_guaranteed();
self.delayed_bugs
.push((DelayedDiagInner::with_backtrace(diagnostic, backtrace), guar));
Some(guar)
};
// If we have already emitted at least one error, we don't need
// to record the delayed bug, because it'll never be used.
return if let Some(guar) = self.has_errors() {
Some(guar)
} else {
// No `TRACK_DIAGNOSTIC` call is needed, because the
// incremental session is deleted if there is a delayed
// bug. This also saves us from cloning the diagnostic.
let backtrace = std::backtrace::Backtrace::capture();
// This `unchecked_error_guaranteed` is valid. It is where the
// `ErrorGuaranteed` for delayed bugs originates. See
// `DiagCtxtInner::drop`.
#[allow(deprecated)]
let guar = ErrorGuaranteed::unchecked_error_guaranteed();
self.delayed_bugs
.push((DelayedDiagInner::with_backtrace(diagnostic, backtrace), guar));
Some(guar)
};
}
}
Warning if !self.flags.can_emit_warnings => {
ForceWarning(None) => {} // `ForceWarning(Some(...))` is below, with `Expect`
Warning => {
if !self.flags.can_emit_warnings {
// We are not emitting warnings.
if diagnostic.has_future_breakage() {
// The side-effect is at the top of this method.
TRACK_DIAGNOSTIC(diagnostic, &mut |_| None);
}
return None;
}
}
Note | Help | FailureNote => {}
OnceNote | OnceHelp => panic!("bad level: {:?}", diagnostic.level),
Allow => {
// Nothing emitted for allowed lints.
if diagnostic.has_future_breakage() {
(*TRACK_DIAGNOSTIC)(diagnostic, &mut |_| {});
// The side-effect is at the top of this method.
TRACK_DIAGNOSTIC(diagnostic, &mut |_| None);
self.suppressed_expected_diag = true;
}
return None;
}
Allow | Expect(_) => {
(*TRACK_DIAGNOSTIC)(diagnostic, &mut |_| {});
return None;
Expect(expect_id) | ForceWarning(Some(expect_id)) => {
// Diagnostics created before the definition of `HirId`s are
// unstable and can not yet be stored. Instead, they are
// buffered until the `LintExpectationId` is replaced by a
// stable one by the `LintLevelsBuilder`.
if let LintExpectationId::Unstable { .. } = expect_id {
// We don't call TRACK_DIAGNOSTIC because we wait for the
// unstable ID to be updated, whereupon the diagnostic will
// be passed into this method again.
self.unstable_expect_diagnostics.push(diagnostic);
return None;
}
self.fulfilled_expectations.insert(expect_id.normalize());
if let Expect(_) = diagnostic.level {
// Nothing emitted here for expected lints.
TRACK_DIAGNOSTIC(diagnostic, &mut |_| None);
self.suppressed_expected_diag = true;
return None;
}
}
_ => {}
}
let mut guaranteed = None;
(*TRACK_DIAGNOSTIC)(diagnostic, &mut |mut diagnostic| {
TRACK_DIAGNOSTIC(diagnostic, &mut |mut diagnostic| {
if let Some(code) = diagnostic.code {
self.emitted_diagnostic_codes.insert(code);
}
@ -1552,17 +1584,17 @@ impl DiagCtxtInner {
// `ErrorGuaranteed` for errors and lint errors originates.
#[allow(deprecated)]
let guar = ErrorGuaranteed::unchecked_error_guaranteed();
guaranteed = Some(guar);
if is_lint {
self.lint_err_guars.push(guar);
} else {
self.err_guars.push(guar);
}
self.panic_if_treat_err_as_bug();
Some(guar)
} else {
None
}
});
guaranteed
})
}
fn treat_err_as_bug(&self) -> bool {
@ -1863,23 +1895,13 @@ impl Level {
matches!(*self, FailureNote)
}
pub fn get_expectation_id(&self) -> Option<LintExpectationId> {
match self {
Expect(id) | ForceWarning(Some(id)) => Some(*id),
_ => None,
}
}
// Can this level be used in a top-level diagnostic message and/or a
// subdiagnostic message?
fn can_be_top_or_sub(&self) -> (bool, bool) {
// Can this level be used in a subdiagnostic message?
fn can_be_subdiag(&self) -> bool {
match self {
Bug | DelayedBug | Fatal | Error | ForceWarning(_) | FailureNote | Allow
| Expect(_) => (true, false),
| Expect(_) => false,
Warning | Note | Help => (true, true),
OnceNote | OnceHelp => (false, true),
Warning | Note | Help | OnceNote | OnceHelp => true,
}
}
}

View File

@ -29,7 +29,7 @@ fn track_span_parent(def_id: rustc_span::def_id::LocalDefId) {
/// This is a callback from `rustc_errors` as it cannot access the implicit state
/// in `rustc_middle` otherwise. It is used when diagnostic messages are
/// emitted and stores them in the current query, if there is one.
fn track_diagnostic(diagnostic: DiagInner, f: &mut dyn FnMut(DiagInner)) {
fn track_diagnostic<R>(diagnostic: DiagInner, f: &mut dyn FnMut(DiagInner) -> R) -> R {
tls::with_context_opt(|icx| {
if let Some(icx) = icx {
if let Some(diagnostics) = icx.diagnostics {
@ -38,11 +38,11 @@ fn track_diagnostic(diagnostic: DiagInner, f: &mut dyn FnMut(DiagInner)) {
// Diagnostics are tracked, we can ignore the dependency.
let icx = tls::ImplicitCtxt { task_deps: TaskDepsRef::Ignore, ..icx.clone() };
return tls::enter_context(&icx, move || (*f)(diagnostic));
tls::enter_context(&icx, move || (*f)(diagnostic))
} else {
// In any other case, invoke diagnostics anyway.
(*f)(diagnostic)
}
// In any other case, invoke diagnostics anyway.
(*f)(diagnostic);
})
}

View File

@ -103,11 +103,12 @@ impl LintLevelSets {
mut idx: LintStackIndex,
aux: Option<&FxIndexMap<LintId, LevelAndSource>>,
) -> (Option<Level>, LintLevelSource) {
if let Some(specs) = aux {
if let Some(&(level, src)) = specs.get(&id) {
return (Some(level), src);
}
if let Some(specs) = aux
&& let Some(&(level, src)) = specs.get(&id)
{
return (Some(level), src);
}
loop {
let LintSet { ref specs, parent } = self.list[idx];
if let Some(&(level, src)) = specs.get(&id) {
@ -177,7 +178,7 @@ fn shallow_lint_levels_on(tcx: TyCtxt<'_>, owner: hir::OwnerId) -> ShallowLintLe
// There is only something to do if there are attributes at all.
[] => {}
// Most of the time, there is only one attribute. Avoid fetching HIR in that case.
[(local_id, _)] => levels.add_id(HirId { owner, local_id: *local_id }),
&[(local_id, _)] => levels.add_id(HirId { owner, local_id }),
// Otherwise, we need to visit the attributes in source code order, so we fetch HIR and do
// a standard visit.
// FIXME(#102522) Just iterate on attrs once that iteration order matches HIR's.
@ -643,63 +644,61 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
//
// This means that this only errors if we're truly lowering the lint
// level from forbid.
if self.lint_added_lints && level != Level::Forbid {
if let Level::Forbid = old_level {
// Backwards compatibility check:
//
// We used to not consider `forbid(lint_group)`
// as preventing `allow(lint)` for some lint `lint` in
// `lint_group`. For now, issue a future-compatibility
// warning for this case.
let id_name = id.lint.name_lower();
let fcw_warning = match old_src {
LintLevelSource::Default => false,
LintLevelSource::Node { name, .. } => self.store.is_lint_group(name),
LintLevelSource::CommandLine(symbol, _) => self.store.is_lint_group(symbol),
};
debug!(
"fcw_warning={:?}, specs.get(&id) = {:?}, old_src={:?}, id_name={:?}",
fcw_warning,
self.current_specs(),
old_src,
id_name
);
let sub = match old_src {
LintLevelSource::Default => {
OverruledAttributeSub::DefaultSource { id: id.to_string() }
}
LintLevelSource::Node { span, reason, .. } => {
OverruledAttributeSub::NodeSource { span, reason }
}
LintLevelSource::CommandLine(_, _) => OverruledAttributeSub::CommandLineSource,
};
if !fcw_warning {
self.sess.dcx().emit_err(OverruledAttribute {
span: src.span(),
if self.lint_added_lints && level != Level::Forbid && old_level == Level::Forbid {
// Backwards compatibility check:
//
// We used to not consider `forbid(lint_group)`
// as preventing `allow(lint)` for some lint `lint` in
// `lint_group`. For now, issue a future-compatibility
// warning for this case.
let id_name = id.lint.name_lower();
let fcw_warning = match old_src {
LintLevelSource::Default => false,
LintLevelSource::Node { name, .. } => self.store.is_lint_group(name),
LintLevelSource::CommandLine(symbol, _) => self.store.is_lint_group(symbol),
};
debug!(
"fcw_warning={:?}, specs.get(&id) = {:?}, old_src={:?}, id_name={:?}",
fcw_warning,
self.current_specs(),
old_src,
id_name
);
let sub = match old_src {
LintLevelSource::Default => {
OverruledAttributeSub::DefaultSource { id: id.to_string() }
}
LintLevelSource::Node { span, reason, .. } => {
OverruledAttributeSub::NodeSource { span, reason }
}
LintLevelSource::CommandLine(_, _) => OverruledAttributeSub::CommandLineSource,
};
if !fcw_warning {
self.sess.dcx().emit_err(OverruledAttribute {
span: src.span(),
overruled: src.span(),
lint_level: level.as_str(),
lint_source: src.name(),
sub,
});
} else {
self.emit_span_lint(
FORBIDDEN_LINT_GROUPS,
src.span().into(),
OverruledAttributeLint {
overruled: src.span(),
lint_level: level.as_str(),
lint_source: src.name(),
sub,
});
} else {
self.emit_span_lint(
FORBIDDEN_LINT_GROUPS,
src.span().into(),
OverruledAttributeLint {
overruled: src.span(),
lint_level: level.as_str(),
lint_source: src.name(),
sub,
},
);
}
},
);
}
// Retain the forbid lint level, unless we are
// issuing a FCW. In the FCW case, we want to
// respect the new setting.
if !fcw_warning {
return;
}
// Retain the forbid lint level, unless we are
// issuing a FCW. In the FCW case, we want to
// respect the new setting.
if !fcw_warning {
return;
}
}
@ -770,15 +769,15 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
let Some(mut metas) = attr.meta_item_list() else { continue };
if metas.is_empty() {
// Check whether `metas` is empty, and get its last element.
let Some(tail_li) = metas.last() else {
// This emits the unused_attributes lint for `#[level()]`
continue;
}
};
// Before processing the lint names, look for a reason (RFC 2383)
// at the end.
let mut reason = None;
let tail_li = &metas[metas.len() - 1];
if let Some(item) = tail_li.meta_item() {
match item.kind {
ast::MetaItemKind::Word => {} // actual lint names handled later
@ -834,21 +833,16 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
let meta_item = match li {
ast::NestedMetaItem::MetaItem(meta_item) if meta_item.is_word() => meta_item,
_ => {
if let Some(item) = li.meta_item() {
if let ast::MetaItemKind::NameValue(_) = item.kind {
if item.path == sym::reason {
sess.dcx().emit_err(MalformedAttribute {
span: sp,
sub: MalformedAttributeSub::ReasonMustComeLast(sp),
});
continue;
}
}
}
sess.dcx().emit_err(MalformedAttribute {
span: sp,
sub: MalformedAttributeSub::BadAttributeArgument(sp),
});
let sub = if let Some(item) = li.meta_item()
&& let ast::MetaItemKind::NameValue(_) = item.kind
&& item.path == sym::reason
{
MalformedAttributeSub::ReasonMustComeLast(sp)
} else {
MalformedAttributeSub::BadAttributeArgument(sp)
};
sess.dcx().emit_err(MalformedAttribute { span: sp, sub });
continue;
}
};
@ -987,11 +981,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
}
CheckLintNameResult::NoLint(suggestion) => {
let name = if let Some(tool_ident) = tool_ident {
format!("{}::{}", tool_ident.name, name)
} else {
name.to_string()
};
let name = tool_ident.map(|tool| format!("{tool}::{name}")).unwrap_or(name);
let suggestion = suggestion.map(|(replace, from_rustc)| {
UnknownLintSuggestion::WithSpan { suggestion: sp, replace, from_rustc }
});
@ -1005,27 +995,24 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
if let CheckLintNameResult::Renamed(new_name) = lint_result {
// Ignore any errors or warnings that happen because the new name is inaccurate
// NOTE: `new_name` already includes the tool name, so we don't have to add it again.
if let CheckLintNameResult::Ok(ids) =
let CheckLintNameResult::Ok(ids) =
self.store.check_lint_name(&new_name, None, self.registered_tools)
{
let src = LintLevelSource::Node {
name: Symbol::intern(&new_name),
span: sp,
reason,
};
for &id in ids {
if self.check_gated_lint(id, attr.span, false) {
self.insert_spec(id, (level, src));
}
}
if let Level::Expect(expect_id) = level {
self.provider.push_expectation(
expect_id,
LintExpectation::new(reason, sp, false, tool_name),
);
}
} else {
else {
panic!("renamed lint does not exist: {new_name}");
};
let src =
LintLevelSource::Node { name: Symbol::intern(&new_name), span: sp, reason };
for &id in ids {
if self.check_gated_lint(id, attr.span, false) {
self.insert_spec(id, (level, src));
}
}
if let Level::Expect(expect_id) = level {
self.provider.push_expectation(
expect_id,
LintExpectation::new(reason, sp, false, tool_name),
);
}
}
}
@ -1058,38 +1045,44 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
/// Returns `true` if the lint's feature is enabled.
#[track_caller]
fn check_gated_lint(&self, lint_id: LintId, span: Span, lint_from_cli: bool) -> bool {
if let Some(feature) = lint_id.lint.feature_gate {
if !self.features.active(feature) {
if self.lint_added_lints {
let lint = builtin::UNKNOWN_LINTS;
let (level, src) = self.lint_level(builtin::UNKNOWN_LINTS);
// FIXME: make this translatable
#[allow(rustc::diagnostic_outside_of_impl)]
#[allow(rustc::untranslatable_diagnostic)]
lint_level(
self.sess,
let feature = if let Some(feature) = lint_id.lint.feature_gate
&& !self.features.active(feature)
{
// Lint is behind a feature that is not enabled; eventually return false.
feature
} else {
// Lint is ungated or its feature is enabled; exit early.
return true;
};
if self.lint_added_lints {
let lint = builtin::UNKNOWN_LINTS;
let (level, src) = self.lint_level(builtin::UNKNOWN_LINTS);
// FIXME: make this translatable
#[allow(rustc::diagnostic_outside_of_impl)]
#[allow(rustc::untranslatable_diagnostic)]
lint_level(
self.sess,
lint,
level,
src,
Some(span.into()),
fluent::lint_unknown_gated_lint,
|lint| {
lint.arg("name", lint_id.lint.name_lower());
lint.note(fluent::lint_note);
rustc_session::parse::add_feature_diagnostics_for_issue(
lint,
level,
src,
Some(span.into()),
fluent::lint_unknown_gated_lint,
|lint| {
lint.arg("name", lint_id.lint.name_lower());
lint.note(fluent::lint_note);
rustc_session::parse::add_feature_diagnostics_for_issue(
lint,
&self.sess,
feature,
GateIssue::Language,
lint_from_cli,
);
},
&self.sess,
feature,
GateIssue::Language,
lint_from_cli,
);
}
return false;
}
},
);
}
true
false
}
/// Find the lint level for a lint.

View File

@ -704,6 +704,20 @@ declare_lint! {
/// `PhantomData`.
///
/// Otherwise consider removing the unused code.
///
/// ### Limitations
///
/// Removing fields that are only used for side-effects and never
/// read will result in behavioral changes. Examples of this
/// include:
///
/// - If a field's value performs an action when it is dropped.
/// - If a field's type does not implement an auto trait
/// (e.g. `Send`, `Sync`, `Unpin`).
///
/// For side-effects from dropping field values, this lint should
/// be allowed on those fields. For side-effects from containing
/// field types, `PhantomData` should be used.
pub DEAD_CODE,
Warn,
"detect unused, unexported items"

View File

@ -23,7 +23,8 @@ use stable_mir::mir::Body;
use stable_mir::target::{MachineInfo, MachineSize};
use stable_mir::ty::{
AdtDef, AdtKind, Allocation, ClosureDef, ClosureKind, Const, FieldDef, FnDef, ForeignDef,
ForeignItemKind, GenericArgs, LineInfo, PolyFnSig, RigidTy, Span, Ty, TyKind, VariantDef,
ForeignItemKind, GenericArgs, LineInfo, PolyFnSig, RigidTy, Span, Ty, TyKind, UintTy,
VariantDef,
};
use stable_mir::{Crate, CrateDef, CrateItem, CrateNum, DefId, Error, Filename, ItemKind, Symbol};
use std::cell::RefCell;
@ -341,15 +342,56 @@ impl<'tcx> Context for TablesWrapper<'tcx> {
.ok_or_else(|| Error::new(format!("Const `{cnst:?}` cannot be encoded as u64")))
}
fn usize_to_const(&self, val: u64) -> Result<Const, Error> {
fn try_new_const_zst(&self, ty: Ty) -> Result<Const, Error> {
let mut tables = self.0.borrow_mut();
let ty = tables.tcx.types.usize;
let tcx = tables.tcx;
let ty_internal = ty.internal(&mut *tables, tcx);
let size = tables
.tcx
.layout_of(ParamEnv::empty().and(ty_internal))
.map_err(|err| {
Error::new(format!(
"Cannot create a zero-sized constant for type `{ty_internal}`: {err}"
))
})?
.size;
if size.bytes() != 0 {
return Err(Error::new(format!(
"Cannot create a zero-sized constant for type `{ty_internal}`: \
Type `{ty_internal}` has {} bytes",
size.bytes()
)));
}
Ok(ty::Const::zero_sized(tables.tcx, ty_internal).stable(&mut *tables))
}
fn new_const_str(&self, value: &str) -> Const {
let mut tables = self.0.borrow_mut();
let tcx = tables.tcx;
let ty = ty::Ty::new_static_str(tcx);
let bytes = value.as_bytes();
let val_tree = ty::ValTree::from_raw_bytes(tcx, bytes);
ty::Const::new_value(tcx, val_tree, ty).stable(&mut *tables)
}
fn new_const_bool(&self, value: bool) -> Const {
let mut tables = self.0.borrow_mut();
ty::Const::from_bool(tables.tcx, value).stable(&mut *tables)
}
fn try_new_const_uint(&self, value: u128, uint_ty: UintTy) -> Result<Const, Error> {
let mut tables = self.0.borrow_mut();
let tcx = tables.tcx;
let ty = ty::Ty::new_uint(tcx, uint_ty.internal(&mut *tables, tcx));
let size = tables.tcx.layout_of(ParamEnv::empty().and(ty)).unwrap().size;
let scalar = ScalarInt::try_from_uint(val, size).ok_or_else(|| {
Error::new(format!("Value overflow: cannot convert `{val}` to usize."))
// We don't use Const::from_bits since it doesn't have any error checking.
let scalar = ScalarInt::try_from_uint(value, size).ok_or_else(|| {
Error::new(format!("Value overflow: cannot convert `{value}` to `{ty}`."))
})?;
Ok(rustc_middle::ty::Const::new_value(tables.tcx, ValTree::from_scalar_int(scalar), ty)
Ok(ty::Const::new_value(tables.tcx, ValTree::from_scalar_int(scalar), ty)
.stable(&mut *tables))
}
@ -556,7 +598,9 @@ impl<'tcx> Context for TablesWrapper<'tcx> {
global_alloc: &GlobalAlloc,
) -> Option<stable_mir::mir::alloc::AllocId> {
let mut tables = self.0.borrow_mut();
let GlobalAlloc::VTable(ty, trait_ref) = global_alloc else { return None };
let GlobalAlloc::VTable(ty, trait_ref) = global_alloc else {
return None;
};
let tcx = tables.tcx;
let alloc_id = tables.tcx.vtable_allocation((
ty.internal(&mut *tables, tcx),

View File

@ -14,7 +14,7 @@ use crate::ty::{
AdtDef, AdtKind, Allocation, ClosureDef, ClosureKind, Const, FieldDef, FnDef, ForeignDef,
ForeignItemKind, ForeignModule, ForeignModuleDef, GenericArgs, GenericPredicates, Generics,
ImplDef, ImplTrait, LineInfo, PolyFnSig, RigidTy, Span, TraitDecl, TraitDef, Ty, TyKind,
VariantDef,
UintTy, VariantDef,
};
use crate::{
mir, Crate, CrateItem, CrateItems, CrateNum, DefId, Error, Filename, ImplTraitDecls, ItemKind,
@ -101,8 +101,17 @@ pub trait Context {
/// Evaluate constant as a target usize.
fn eval_target_usize(&self, cnst: &Const) -> Result<u64, Error>;
/// Create a target usize constant for the given value.
fn usize_to_const(&self, val: u64) -> Result<Const, Error>;
/// Create a new zero-sized constant.
fn try_new_const_zst(&self, ty: Ty) -> Result<Const, Error>;
/// Create a new constant that represents the given string value.
fn new_const_str(&self, value: &str) -> Const;
/// Create a new constant that represents the given boolean value.
fn new_const_bool(&self, value: bool) -> Const;
/// Create a new constant that represents the given value.
fn try_new_const_uint(&self, value: u128, uint_ty: UintTy) -> Result<Const, Error>;
/// Create a new type from the given kind.
fn new_rigid_ty(&self, kind: RigidTy) -> Ty;
@ -200,7 +209,7 @@ pub trait Context {
// A thread local variable that stores a pointer to the tables mapping between TyCtxt
// datastructures and stable MIR datastructures
scoped_thread_local! (static TLV: Cell<*const ()>);
scoped_thread_local!(static TLV: Cell<*const ()>);
pub fn run<F, T>(context: &dyn Context, f: F) -> Result<T, Error>
where

View File

@ -128,13 +128,38 @@ impl Const {
/// Creates an interned usize constant.
fn try_from_target_usize(val: u64) -> Result<Self, Error> {
with(|cx| cx.usize_to_const(val))
with(|cx| cx.try_new_const_uint(val.into(), UintTy::Usize))
}
/// Try to evaluate to a target `usize`.
pub fn eval_target_usize(&self) -> Result<u64, Error> {
with(|cx| cx.eval_target_usize(self))
}
/// Create a constant that represents a new zero-sized constant of type T.
/// Fails if the type is not a ZST or if it doesn't have a known size.
pub fn try_new_zero_sized(ty: Ty) -> Result<Const, Error> {
with(|cx| cx.try_new_const_zst(ty))
}
/// Build a new constant that represents the given string.
///
/// Note that there is no guarantee today about duplication of the same constant.
/// I.e.: Calling this function multiple times with the same argument may or may not return
/// the same allocation.
pub fn from_str(value: &str) -> Const {
with(|cx| cx.new_const_str(value))
}
/// Build a new constant that represents the given boolean value.
pub fn from_bool(value: bool) -> Const {
with(|cx| cx.new_const_bool(value))
}
/// Build a new constant that represents the given unsigned integer.
pub fn try_from_uint(value: u128, uint_ty: UintTy) -> Result<Const, Error> {
with(|cx| cx.try_new_const_uint(value, uint_ty))
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]

View File

@ -5,8 +5,11 @@ use crate::error::Error;
use crate::ffi::c_char;
use crate::fmt;
use crate::intrinsics;
use crate::iter::FusedIterator;
use crate::marker::PhantomData;
use crate::ops;
use crate::ptr::addr_of;
use crate::ptr::NonNull;
use crate::slice;
use crate::slice::memchr;
use crate::str;
@ -504,6 +507,13 @@ impl CStr {
self.inner.as_ptr()
}
/// We could eventually expose this publicly, if we wanted.
#[inline]
#[must_use]
const fn as_non_null_ptr(&self) -> NonNull<c_char> {
NonNull::from(&self.inner).as_non_null_ptr()
}
/// Returns the length of `self`. Like C's `strlen`, this does not include the nul terminator.
///
/// > **Note**: This method is currently implemented as a constant-time
@ -617,6 +627,26 @@ impl CStr {
unsafe { &*(addr_of!(self.inner) as *const [u8]) }
}
/// Iterates over the bytes in this C string.
///
/// The returned iterator will **not** contain the trailing nul terminator
/// that this C string has.
///
/// # Examples
///
/// ```
/// #![feature(cstr_bytes)]
/// use std::ffi::CStr;
///
/// let cstr = CStr::from_bytes_with_nul(b"foo\0").expect("CStr::from_bytes_with_nul failed");
/// assert!(cstr.bytes().eq(*b"foo"));
/// ```
#[inline]
#[unstable(feature = "cstr_bytes", issue = "112115")]
pub fn bytes(&self) -> Bytes<'_> {
Bytes::new(self)
}
/// Yields a <code>&[str]</code> slice if the `CStr` contains valid UTF-8.
///
/// If the contents of the `CStr` are valid UTF-8 data, this
@ -735,3 +765,64 @@ const unsafe fn const_strlen(ptr: *const c_char) -> usize {
intrinsics::const_eval_select((ptr,), strlen_ct, strlen_rt)
}
}
/// An iterator over the bytes of a [`CStr`], without the nul terminator.
///
/// This struct is created by the [`bytes`] method on [`CStr`].
/// See its documentation for more.
///
/// [`bytes`]: CStr::bytes
#[must_use = "iterators are lazy and do nothing unless consumed"]
#[unstable(feature = "cstr_bytes", issue = "112115")]
#[derive(Clone, Debug)]
pub struct Bytes<'a> {
// since we know the string is nul-terminated, we only need one pointer
ptr: NonNull<u8>,
phantom: PhantomData<&'a u8>,
}
impl<'a> Bytes<'a> {
#[inline]
fn new(s: &'a CStr) -> Self {
Self { ptr: s.as_non_null_ptr().cast(), phantom: PhantomData }
}
#[inline]
fn is_empty(&self) -> bool {
// SAFETY: We uphold that the pointer is always valid to dereference
// by starting with a valid C string and then never incrementing beyond
// the nul terminator.
unsafe { self.ptr.read() == 0 }
}
}
#[unstable(feature = "cstr_bytes", issue = "112115")]
impl Iterator for Bytes<'_> {
type Item = u8;
#[inline]
fn next(&mut self) -> Option<u8> {
// SAFETY: We only choose a pointer from a valid C string, which must
// be non-null and contain at least one value. Since we always stop at
// the nul terminator, which is guaranteed to exist, we can assume that
// the pointer is non-null and valid. This lets us safely dereference
// it and assume that adding 1 will create a new, non-null, valid
// pointer.
unsafe {
let ret = self.ptr.read();
if ret == 0 {
None
} else {
self.ptr = self.ptr.offset(1);
Some(ret)
}
}
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
if self.is_empty() { (0, Some(0)) } else { (1, None) }
}
}
#[unstable(feature = "cstr_bytes", issue = "112115")]
impl FusedIterator for Bytes<'_> {}

View File

@ -22,7 +22,7 @@ unsafe_impl_trusted_step![AsciiChar char i8 i16 i32 i64 i128 isize u8 u16 u32 u6
///
/// The *successor* operation moves towards values that compare greater.
/// The *predecessor* operation moves towards values that compare lesser.
#[unstable(feature = "step_trait", reason = "recently redesigned", issue = "42168")]
#[unstable(feature = "step_trait", issue = "42168")]
pub trait Step: Clone + PartialOrd + Sized {
/// Returns the number of *successor* steps required to get from `start` to `end`.
///
@ -52,15 +52,12 @@ pub trait Step: Clone + PartialOrd + Sized {
/// For any `a`, `n`, and `m`:
///
/// * `Step::forward_checked(a, n).and_then(|x| Step::forward_checked(x, m)) == Step::forward_checked(a, m).and_then(|x| Step::forward_checked(x, n))`
///
/// For any `a`, `n`, and `m` where `n + m` does not overflow:
///
/// * `Step::forward_checked(a, n).and_then(|x| Step::forward_checked(x, m)) == Step::forward_checked(a, n + m)`
/// * `Step::forward_checked(a, n).and_then(|x| Step::forward_checked(x, m)) == try { Step::forward_checked(a, n.checked_add(m)) }`
///
/// For any `a` and `n`:
///
/// * `Step::forward_checked(a, n) == (0..n).try_fold(a, |x, _| Step::forward_checked(&x, 1))`
/// * Corollary: `Step::forward_checked(&a, 0) == Some(a)`
/// * Corollary: `Step::forward_checked(a, 0) == Some(a)`
fn forward_checked(start: Self, count: usize) -> Option<Self>;
/// Returns the value that would be obtained by taking the *successor*
@ -106,6 +103,7 @@ pub trait Step: Clone + PartialOrd + Sized {
/// * if there exists `b` such that `b > a`, it is safe to call `Step::forward_unchecked(a, 1)`
/// * if there exists `b`, `n` such that `steps_between(&a, &b) == Some(n)`,
/// it is safe to call `Step::forward_unchecked(a, m)` for any `m <= n`.
/// * Corollary: `Step::forward_unchecked(a, 0)` is always safe.
///
/// For any `a` and `n`, where no overflow occurs:
///
@ -128,8 +126,8 @@ pub trait Step: Clone + PartialOrd + Sized {
///
/// For any `a` and `n`:
///
/// * `Step::backward_checked(a, n) == (0..n).try_fold(a, |x, _| Step::backward_checked(&x, 1))`
/// * Corollary: `Step::backward_checked(&a, 0) == Some(a)`
/// * `Step::backward_checked(a, n) == (0..n).try_fold(a, |x, _| Step::backward_checked(x, 1))`
/// * Corollary: `Step::backward_checked(a, 0) == Some(a)`
fn backward_checked(start: Self, count: usize) -> Option<Self>;
/// Returns the value that would be obtained by taking the *predecessor*
@ -175,6 +173,7 @@ pub trait Step: Clone + PartialOrd + Sized {
/// * if there exists `b` such that `b < a`, it is safe to call `Step::backward_unchecked(a, 1)`
/// * if there exists `b`, `n` such that `steps_between(&b, &a) == Some(n)`,
/// it is safe to call `Step::backward_unchecked(a, m)` for any `m <= n`.
/// * Corollary: `Step::backward_unchecked(a, 0)` is always safe.
///
/// For any `a` and `n`, where no overflow occurs:
///
@ -184,8 +183,25 @@ pub trait Step: Clone + PartialOrd + Sized {
}
}
// These are still macro-generated because the integer literals resolve to different types.
macro_rules! step_identical_methods {
// Separate impls for signed ranges because the distance within a signed range can be larger
// than the signed::MAX value. Therefore `as` casting to the signed type would be incorrect.
macro_rules! step_signed_methods {
($unsigned: ty) => {
#[inline]
unsafe fn forward_unchecked(start: Self, n: usize) -> Self {
// SAFETY: the caller has to guarantee that `start + n` doesn't overflow.
unsafe { start.checked_add_unsigned(n as $unsigned).unwrap_unchecked() }
}
#[inline]
unsafe fn backward_unchecked(start: Self, n: usize) -> Self {
// SAFETY: the caller has to guarantee that `start - n` doesn't overflow.
unsafe { start.checked_sub_unsigned(n as $unsigned).unwrap_unchecked() }
}
};
}
macro_rules! step_unsigned_methods {
() => {
#[inline]
unsafe fn forward_unchecked(start: Self, n: usize) -> Self {
@ -198,7 +214,12 @@ macro_rules! step_identical_methods {
// SAFETY: the caller has to guarantee that `start - n` doesn't overflow.
unsafe { start.unchecked_sub(n as Self) }
}
};
}
// These are still macro-generated because the integer literals resolve to different types.
macro_rules! step_identical_methods {
() => {
#[inline]
#[allow(arithmetic_overflow)]
#[rustc_inherit_overflow_checks]
@ -239,6 +260,7 @@ macro_rules! step_integer_impls {
#[unstable(feature = "step_trait", reason = "recently redesigned", issue = "42168")]
impl Step for $u_narrower {
step_identical_methods!();
step_unsigned_methods!();
#[inline]
fn steps_between(start: &Self, end: &Self) -> Option<usize> {
@ -271,6 +293,7 @@ macro_rules! step_integer_impls {
#[unstable(feature = "step_trait", reason = "recently redesigned", issue = "42168")]
impl Step for $i_narrower {
step_identical_methods!();
step_signed_methods!($u_narrower);
#[inline]
fn steps_between(start: &Self, end: &Self) -> Option<usize> {
@ -335,6 +358,7 @@ macro_rules! step_integer_impls {
#[unstable(feature = "step_trait", reason = "recently redesigned", issue = "42168")]
impl Step for $u_wider {
step_identical_methods!();
step_unsigned_methods!();
#[inline]
fn steps_between(start: &Self, end: &Self) -> Option<usize> {
@ -360,6 +384,7 @@ macro_rules! step_integer_impls {
#[unstable(feature = "step_trait", reason = "recently redesigned", issue = "42168")]
impl Step for $i_wider {
step_identical_methods!();
step_signed_methods!($u_wider);
#[inline]
fn steps_between(start: &Self, end: &Self) -> Option<usize> {

View File

@ -325,6 +325,11 @@ fn test_range_advance_by() {
assert_eq!(Ok(()), r.advance_back_by(usize::MAX));
assert_eq!((r.start, r.end), (0u128 + usize::MAX as u128, u128::MAX - usize::MAX as u128));
// issue 122420, Step::forward_unchecked was unsound for signed integers
let mut r = -128i8..127;
assert_eq!(Ok(()), r.advance_by(200));
assert_eq!(r.next(), Some(72));
}
#[test]

View File

@ -63,11 +63,12 @@ Before describing the syntax in more detail, here's a few sample searches of
the standard library and functions that are included in the results list:
| Query | Results |
|-------|--------|
|-------|---------|
| [`usize -> vec`][] | `slice::repeat` and `Vec::with_capacity` |
| [`vec, vec -> bool`][] | `Vec::eq` |
| [`option<T>, fnonce -> option<U>`][] | `Option::map` and `Option::and_then` |
| [`option<T>, fnonce -> option<T>`][] | `Option::filter` and `Option::inspect` |
| [`option<T>, (fnonce (T) -> bool) -> option<T>`][optionfilter] | `Option::filter` |
| [`option<T>, (T -> bool) -> option<T>`][optionfilter2] | `Option::filter` |
| [`option -> default`][] | `Option::unwrap_or_default` |
| [`stdout, [u8]`][stdoutu8] | `Stdout::write` |
| [`any -> !`][] | `panic::panic_any` |
@ -77,7 +78,8 @@ the standard library and functions that are included in the results list:
[`usize -> vec`]: ../../std/vec/struct.Vec.html?search=usize%20-%3E%20vec&filter-crate=std
[`vec, vec -> bool`]: ../../std/vec/struct.Vec.html?search=vec,%20vec%20-%3E%20bool&filter-crate=std
[`option<T>, fnonce -> option<U>`]: ../../std/vec/struct.Vec.html?search=option<T>%2C%20fnonce%20->%20option<U>&filter-crate=std
[`option<T>, fnonce -> option<T>`]: ../../std/vec/struct.Vec.html?search=option<T>%2C%20fnonce%20->%20option<T>&filter-crate=std
[optionfilter]: ../../std/vec/struct.Vec.html?search=option<T>%2C+(fnonce+(T)+->+bool)+->+option<T>&filter-crate=std
[optionfilter2]: ../../std/vec/struct.Vec.html?search=option<T>%2C+(T+->+bool)+->+option<T>&filter-crate=std
[`option -> default`]: ../../std/vec/struct.Vec.html?search=option%20-%3E%20default&filter-crate=std
[`any -> !`]: ../../std/vec/struct.Vec.html?search=any%20-%3E%20!&filter-crate=std
[stdoutu8]: ../../std/vec/struct.Vec.html?search=stdout%2C%20[u8]&filter-crate=std
@ -151,16 +153,26 @@ will match these queries:
But it *does not* match `Result<Vec, u8>` or `Result<u8<Vec>>`.
To search for a function that accepts a function as a parameter,
like `Iterator::all`, wrap the nested signature in parenthesis,
as in [`Iterator<T>, (T -> bool) -> bool`][iterator-all].
You can also search for a specific closure trait,
such as `Iterator<T>, (FnMut(T) -> bool) -> bool`,
but you need to know which one you want.
[iterator-all]: ../../std/vec/struct.Vec.html?search=Iterator<T>%2C+(T+->+bool)+->+bool&filter-crate=std
### Primitives with Special Syntax
| Shorthand | Explicit names |
| --------- | ------------------------------------------------ |
| `[]` | `primitive:slice` and/or `primitive:array` |
| `[T]` | `primitive:slice<T>` and/or `primitive:array<T>` |
| `()` | `primitive:unit` and/or `primitive:tuple` |
| `(T)` | `T` |
| `(T,)` | `primitive:tuple<T>` |
| `!` | `primitive:never` |
| Shorthand | Explicit names |
| ---------------- | ------------------------------------------------- |
| `[]` | `primitive:slice` and/or `primitive:array` |
| `[T]` | `primitive:slice<T>` and/or `primitive:array<T>` |
| `()` | `primitive:unit` and/or `primitive:tuple` |
| `(T)` | `T` |
| `(T,)` | `primitive:tuple<T>` |
| `!` | `primitive:never` |
| `(T, U -> V, W)` | `fn(T, U) -> (V, W)`, `Fn`, `FnMut`, and `FnOnce` |
When searching for `[]`, Rustdoc will return search results with either slices
or arrays. If you know which one you want, you can force it to return results
@ -180,6 +192,10 @@ results for types that match tuples, even though it also matches the type on
its own. That is, `(u32)` matches `(u32,)` for the exact same reason that it
also matches `Result<u32, Error>`.
The `->` operator has lower precedence than comma. If it's not wrapped
in brackets, it delimits the return value for the function being searched for.
To search for functions that take functions as parameters, use parenthesis.
### Limitations and quirks of type-based search
Type-based search is still a buggy, experimental, work-in-progress feature.
@ -218,9 +234,6 @@ Most of these limitations should be addressed in future version of Rustdoc.
* Searching for lifetimes is not supported.
* It's impossible to search for closures based on their parameters or
return values.
* It's impossible to search based on the length of an array.
## Item filtering
@ -237,19 +250,21 @@ Item filters can be used in both name-based and type signature-based searches.
```text
ident = *(ALPHA / DIGIT / "_")
path = ident *(DOUBLE-COLON ident) [!]
path = ident *(DOUBLE-COLON ident) [BANG]
slice-like = OPEN-SQUARE-BRACKET [ nonempty-arg-list ] CLOSE-SQUARE-BRACKET
tuple-like = OPEN-PAREN [ nonempty-arg-list ] CLOSE-PAREN
arg = [type-filter *WS COLON *WS] (path [generics] / slice-like / tuple-like / [!])
arg = [type-filter *WS COLON *WS] (path [generics] / slice-like / tuple-like)
type-sep = COMMA/WS *(COMMA/WS)
nonempty-arg-list = *(type-sep) arg *(type-sep arg) *(type-sep)
nonempty-arg-list = *(type-sep) arg *(type-sep arg) *(type-sep) [ return-args ]
generic-arg-list = *(type-sep) arg [ EQUAL arg ] *(type-sep arg [ EQUAL arg ]) *(type-sep)
generics = OPEN-ANGLE-BRACKET [ generic-arg-list ] *(type-sep)
normal-generics = OPEN-ANGLE-BRACKET [ generic-arg-list ] *(type-sep)
CLOSE-ANGLE-BRACKET
fn-like-generics = OPEN-PAREN [ nonempty-arg-list ] CLOSE-PAREN [ RETURN-ARROW arg ]
generics = normal-generics / fn-like-generics
return-args = RETURN-ARROW *(type-sep) nonempty-arg-list
exact-search = [type-filter *WS COLON] [ RETURN-ARROW ] *WS QUOTE ident QUOTE [ generics ]
type-search = [ nonempty-arg-list ] [ return-args ]
type-search = [ nonempty-arg-list ]
query = *WS (exact-search / type-search) *WS
@ -294,6 +309,7 @@ QUOTE = %x22
COMMA = ","
RETURN-ARROW = "->"
EQUAL = "="
BANG = "!"
ALPHA = %x41-5A / %x61-7A ; A-Z / a-z
DIGIT = %x30-39

View File

@ -4,6 +4,7 @@ use std::collections::{BTreeMap, VecDeque};
use rustc_data_structures::fx::{FxHashMap, FxIndexMap};
use rustc_middle::ty::TyCtxt;
use rustc_span::def_id::DefId;
use rustc_span::sym;
use rustc_span::symbol::Symbol;
use serde::ser::{Serialize, SerializeSeq, SerializeStruct, Serializer};
use thin_vec::ThinVec;
@ -566,6 +567,7 @@ fn get_index_type_id(
// The type parameters are converted to generics in `simplify_fn_type`
clean::Slice(_) => Some(RenderTypeId::Primitive(clean::PrimitiveType::Slice)),
clean::Array(_, _) => Some(RenderTypeId::Primitive(clean::PrimitiveType::Array)),
clean::BareFunction(_) => Some(RenderTypeId::Primitive(clean::PrimitiveType::Fn)),
clean::Tuple(ref n) if n.is_empty() => {
Some(RenderTypeId::Primitive(clean::PrimitiveType::Unit))
}
@ -584,7 +586,7 @@ fn get_index_type_id(
}
}
// Not supported yet
clean::BareFunction(_) | clean::Generic(_) | clean::ImplTrait(_) | clean::Infer => None,
clean::Generic(_) | clean::ImplTrait(_) | clean::Infer => None,
}
}
@ -785,6 +787,42 @@ fn simplify_fn_type<'tcx, 'a>(
);
}
res.push(get_index_type(arg, ty_generics, rgen));
} else if let Type::BareFunction(ref bf) = *arg {
let mut ty_generics = Vec::new();
for ty in bf.decl.inputs.values.iter().map(|arg| &arg.type_) {
simplify_fn_type(
self_,
generics,
ty,
tcx,
recurse + 1,
&mut ty_generics,
rgen,
is_return,
cache,
);
}
// The search index, for simplicity's sake, represents fn pointers and closures
// the same way: as a tuple for the parameters, and an associated type for the
// return type.
let mut ty_output = Vec::new();
simplify_fn_type(
self_,
generics,
&bf.decl.output,
tcx,
recurse + 1,
&mut ty_output,
rgen,
is_return,
cache,
);
let ty_bindings = vec![(RenderTypeId::AssociatedType(sym::Output), ty_output)];
res.push(RenderType {
id: get_index_type_id(&arg, rgen),
bindings: Some(ty_bindings),
generics: Some(ty_generics),
});
} else {
// This is not a type parameter. So for example if we have `T, U: Option<T>`, and we're
// looking at `Option`, we enter this "else" condition, otherwise if it's `T`, we don't.

View File

@ -1,3 +1,4 @@
// ignore-tidy-filelength
/* global addClass, getNakedUrl, getSettingValue */
/* global onEachLazy, removeClass, searchState, browserSupportsHistoryApi, exports */
@ -245,33 +246,49 @@ function initSearch(rawSearchIndex) {
*
* @type {Map<string, {id: integer, assocOnly: boolean}>}
*/
let typeNameIdMap;
const typeNameIdMap = new Map();
const ALIASES = new Map();
/**
* Special type name IDs for searching by array.
*/
let typeNameIdOfArray;
const typeNameIdOfArray = buildTypeMapIndex("array");
/**
* Special type name IDs for searching by slice.
*/
let typeNameIdOfSlice;
const typeNameIdOfSlice = buildTypeMapIndex("slice");
/**
* Special type name IDs for searching by both array and slice (`[]` syntax).
*/
let typeNameIdOfArrayOrSlice;
const typeNameIdOfArrayOrSlice = buildTypeMapIndex("[]");
/**
* Special type name IDs for searching by tuple.
*/
let typeNameIdOfTuple;
const typeNameIdOfTuple = buildTypeMapIndex("tuple");
/**
* Special type name IDs for searching by unit.
*/
let typeNameIdOfUnit;
const typeNameIdOfUnit = buildTypeMapIndex("unit");
/**
* Special type name IDs for searching by both tuple and unit (`()` syntax).
*/
let typeNameIdOfTupleOrUnit;
const typeNameIdOfTupleOrUnit = buildTypeMapIndex("()");
/**
* Special type name IDs for searching `fn`.
*/
const typeNameIdOfFn = buildTypeMapIndex("fn");
/**
* Special type name IDs for searching `fnmut`.
*/
const typeNameIdOfFnMut = buildTypeMapIndex("fnmut");
/**
* Special type name IDs for searching `fnonce`.
*/
const typeNameIdOfFnOnce = buildTypeMapIndex("fnonce");
/**
* Special type name IDs for searching higher order functions (`->` syntax).
*/
const typeNameIdOfHof = buildTypeMapIndex("->");
/**
* Add an item to the type Name->ID map, or, if one already exists, use it.
@ -464,6 +481,21 @@ function initSearch(rawSearchIndex) {
}
}
function makePrimitiveElement(name, extra) {
return Object.assign({
name,
id: null,
fullPath: [name],
pathWithoutLast: [],
pathLast: name,
normalizedPathLast: name,
generics: [],
bindings: new Map(),
typeFilter: "primitive",
bindingName: null,
}, extra);
}
/**
* @param {ParsedQuery} query
* @param {ParserState} parserState
@ -501,18 +533,7 @@ function initSearch(rawSearchIndex) {
}
const bindingName = parserState.isInBinding;
parserState.isInBinding = null;
return {
name: "never",
id: null,
fullPath: ["never"],
pathWithoutLast: [],
pathLast: "never",
normalizedPathLast: "never",
generics: [],
bindings: new Map(),
typeFilter: "primitive",
bindingName,
};
return makePrimitiveElement("never", { bindingName });
}
const quadcolon = /::\s*::/.exec(path);
if (path.startsWith("::")) {
@ -558,7 +579,10 @@ function initSearch(rawSearchIndex) {
// Syntactically, bindings are parsed as generics,
// but the query engine treats them differently.
if (gen.bindingName !== null) {
bindings.set(gen.bindingName.name, [gen, ...gen.bindingName.generics]);
if (gen.name !== null) {
gen.bindingName.generics.unshift(gen);
}
bindings.set(gen.bindingName.name, gen.bindingName.generics);
return false;
}
return true;
@ -658,6 +682,38 @@ function initSearch(rawSearchIndex) {
return end;
}
function getFilteredNextElem(query, parserState, elems, isInGenerics) {
const start = parserState.pos;
if (parserState.userQuery[parserState.pos] === ":" && !isPathStart(parserState)) {
throw ["Expected type filter before ", ":"];
}
getNextElem(query, parserState, elems, isInGenerics);
if (parserState.userQuery[parserState.pos] === ":" && !isPathStart(parserState)) {
if (parserState.typeFilter !== null) {
throw [
"Unexpected ",
":",
" (expected path after type filter ",
parserState.typeFilter + ":",
")",
];
}
if (elems.length === 0) {
throw ["Expected type filter before ", ":"];
} else if (query.literalSearch) {
throw ["Cannot use quotes on type filter"];
}
// The type filter doesn't count as an element since it's a modifier.
const typeFilterElem = elems.pop();
checkExtraTypeFilterCharacters(start, parserState);
parserState.typeFilter = typeFilterElem.name;
parserState.pos += 1;
parserState.totalElems -= 1;
query.literalSearch = false;
getNextElem(query, parserState, elems, isInGenerics);
}
}
/**
* @param {ParsedQuery} query
* @param {ParserState} parserState
@ -671,28 +727,19 @@ function initSearch(rawSearchIndex) {
let start = parserState.pos;
let end;
if ("[(".indexOf(parserState.userQuery[parserState.pos]) !== -1) {
let endChar = ")";
let name = "()";
let friendlyName = "tuple";
let endChar = ")";
let name = "()";
let friendlyName = "tuple";
if (parserState.userQuery[parserState.pos] === "[") {
endChar = "]";
name = "[]";
friendlyName = "slice";
}
if (parserState.userQuery[parserState.pos] === "[") {
endChar = "]";
name = "[]";
friendlyName = "slice";
}
parserState.pos += 1;
const { foundSeparator } = getItemsBefore(query, parserState, generics, endChar);
const typeFilter = parserState.typeFilter;
const isInBinding = parserState.isInBinding;
if (typeFilter !== null && typeFilter !== "primitive") {
throw [
"Invalid search type: primitive ",
name,
" and ",
typeFilter,
" both specified",
];
}
const bindingName = parserState.isInBinding;
parserState.typeFilter = null;
parserState.isInBinding = null;
for (const gen of generics) {
@ -702,23 +749,26 @@ if (parserState.userQuery[parserState.pos] === "[") {
}
if (name === "()" && !foundSeparator && generics.length === 1 && typeFilter === null) {
elems.push(generics[0]);
} else if (name === "()" && generics.length === 1 && generics[0].name === "->") {
// `primitive:(a -> b)` parser to `primitive:"->"<output=b, (a,)>`
// not `primitive:"()"<"->"<output=b, (a,)>>`
generics[0].typeFilter = typeFilter;
elems.push(generics[0]);
} else {
if (typeFilter !== null && typeFilter !== "primitive") {
throw [
"Invalid search type: primitive ",
name,
" and ",
typeFilter,
" both specified",
];
}
parserState.totalElems += 1;
if (isInGenerics) {
parserState.genericsElems += 1;
}
elems.push({
name: name,
id: null,
fullPath: [name],
pathWithoutLast: [],
pathLast: name,
normalizedPathLast: name,
generics,
bindings: new Map(),
typeFilter: "primitive",
bindingName: isInBinding,
});
elems.push(makePrimitiveElement(name, { bindingName, generics }));
}
} else {
const isStringElem = parserState.userQuery[start] === "\"";
@ -738,6 +788,32 @@ if (parserState.userQuery[parserState.pos] === "[") {
}
parserState.pos += 1;
getItemsBefore(query, parserState, generics, ">");
} else if (parserState.pos < parserState.length &&
parserState.userQuery[parserState.pos] === "("
) {
if (start >= end) {
throw ["Found generics without a path"];
}
if (parserState.isInBinding) {
throw ["Unexpected ", "(", " after ", "="];
}
parserState.pos += 1;
const typeFilter = parserState.typeFilter;
parserState.typeFilter = null;
getItemsBefore(query, parserState, generics, ")");
skipWhitespace(parserState);
if (isReturnArrow(parserState)) {
parserState.pos += 2;
skipWhitespace(parserState);
getFilteredNextElem(query, parserState, generics, isInGenerics);
generics[generics.length - 1].bindingName = makePrimitiveElement("output");
} else {
generics.push(makePrimitiveElement(null, {
bindingName: makePrimitiveElement("output"),
typeFilter: null,
}));
}
parserState.typeFilter = typeFilter;
}
if (isStringElem) {
skipWhitespace(parserState);
@ -797,7 +873,6 @@ if (parserState.userQuery[parserState.pos] === "[") {
function getItemsBefore(query, parserState, elems, endChar) {
let foundStopChar = true;
let foundSeparator = false;
let start = parserState.pos;
// If this is a generic, keep the outer item's type filter around.
const oldTypeFilter = parserState.typeFilter;
@ -805,6 +880,19 @@ if (parserState.userQuery[parserState.pos] === "[") {
const oldIsInBinding = parserState.isInBinding;
parserState.isInBinding = null;
// ML-style Higher Order Function notation
//
// a way to search for any closure or fn pointer regardless of
// which closure trait is used
//
// Looks like this:
//
// `option<t>, (t -> u) -> option<u>`
// ^^^^^^
//
// The Rust-style closure notation is implemented in getNextElem
let hofParameters = null;
let extra = "";
if (endChar === ">") {
extra = "<";
@ -825,6 +913,21 @@ if (parserState.userQuery[parserState.pos] === "[") {
throw ["Unexpected ", endChar, " after ", "="];
}
break;
} else if (endChar !== "" && isReturnArrow(parserState)) {
// ML-style HOF notation only works when delimited in something,
// otherwise a function arrow starts the return type of the top
if (parserState.isInBinding) {
throw ["Unexpected ", "->", " after ", "="];
}
hofParameters = [...elems];
elems.length = 0;
parserState.pos += 2;
foundStopChar = true;
foundSeparator = false;
continue;
} else if (c === " ") {
parserState.pos += 1;
continue;
} else if (isSeparatorCharacter(c)) {
parserState.pos += 1;
foundStopChar = true;
@ -832,24 +935,6 @@ if (parserState.userQuery[parserState.pos] === "[") {
continue;
} else if (c === ":" && isPathStart(parserState)) {
throw ["Unexpected ", "::", ": paths cannot start with ", "::"];
} else if (c === ":") {
if (parserState.typeFilter !== null) {
throw ["Unexpected ", ":"];
}
if (elems.length === 0) {
throw ["Expected type filter before ", ":"];
} else if (query.literalSearch) {
throw ["Cannot use quotes on type filter"];
}
// The type filter doesn't count as an element since it's a modifier.
const typeFilterElem = elems.pop();
checkExtraTypeFilterCharacters(start, parserState);
parserState.typeFilter = typeFilterElem.name;
parserState.pos += 1;
parserState.totalElems -= 1;
query.literalSearch = false;
foundStopChar = true;
continue;
} else if (isEndCharacter(c)) {
throw ["Unexpected ", c, " after ", extra];
}
@ -884,8 +969,7 @@ if (parserState.userQuery[parserState.pos] === "[") {
];
}
const posBefore = parserState.pos;
start = parserState.pos;
getNextElem(query, parserState, elems, endChar !== "");
getFilteredNextElem(query, parserState, elems, endChar !== "");
if (endChar !== "" && parserState.pos >= parserState.length) {
throw ["Unclosed ", extra];
}
@ -904,6 +988,27 @@ if (parserState.userQuery[parserState.pos] === "[") {
// in any case.
parserState.pos += 1;
if (hofParameters) {
// Commas in a HOF don't cause wrapping parens to become a tuple.
// If you want a one-tuple with a HOF in it, write `((a -> b),)`.
foundSeparator = false;
// HOFs can't have directly nested bindings.
if ([...elems, ...hofParameters].some(x => x.bindingName) || parserState.isInBinding) {
throw ["Unexpected ", "=", " within ", "->"];
}
// HOFs are represented the same way closures are.
// The arguments are wrapped in a tuple, and the output
// is a binding, even though the compiler doesn't technically
// represent fn pointers that way.
const hofElem = makePrimitiveElement("->", {
generics: hofParameters,
bindings: new Map([["output", [...elems]]]),
typeFilter: null,
});
elems.length = 0;
elems[0] = hofElem;
}
parserState.typeFilter = oldTypeFilter;
parserState.isInBinding = oldIsInBinding;
@ -941,7 +1046,6 @@ if (parserState.userQuery[parserState.pos] === "[") {
*/
function parseInput(query, parserState) {
let foundStopChar = true;
let start = parserState.pos;
while (parserState.pos < parserState.length) {
const c = parserState.userQuery[parserState.pos];
@ -959,29 +1063,6 @@ if (parserState.userQuery[parserState.pos] === "[") {
throw ["Unexpected ", c, " after ", parserState.userQuery[parserState.pos - 1]];
}
throw ["Unexpected ", c];
} else if (c === ":" && !isPathStart(parserState)) {
if (parserState.typeFilter !== null) {
throw [
"Unexpected ",
":",
" (expected path after type filter ",
parserState.typeFilter + ":",
")",
];
} else if (query.elems.length === 0) {
throw ["Expected type filter before ", ":"];
} else if (query.literalSearch) {
throw ["Cannot use quotes on type filter"];
}
// The type filter doesn't count as an element since it's a modifier.
const typeFilterElem = query.elems.pop();
checkExtraTypeFilterCharacters(start, parserState);
parserState.typeFilter = typeFilterElem.name;
parserState.pos += 1;
parserState.totalElems -= 1;
query.literalSearch = false;
foundStopChar = true;
continue;
} else if (c === " ") {
skipWhitespace(parserState);
continue;
@ -1017,8 +1098,7 @@ if (parserState.userQuery[parserState.pos] === "[") {
];
}
const before = query.elems.length;
start = parserState.pos;
getNextElem(query, parserState, query.elems, false);
getFilteredNextElem(query, parserState, query.elems, false);
if (query.elems.length === before) {
// Nothing was added, weird... Let's increase the position to not remain stuck.
parserState.pos += 1;
@ -1258,11 +1338,6 @@ if (parserState.userQuery[parserState.pos] === "[") {
* @returns {[ResultObject]}
*/
function sortResults(results, isType, preferredCrate) {
// if there are no results then return to default and fail
if (results.size === 0) {
return [];
}
const userQuery = parsedQuery.userQuery;
const result_list = [];
for (const result of results.values()) {
@ -1635,6 +1710,12 @@ if (parserState.userQuery[parserState.pos] === "[") {
) {
// () matches primitive:tuple or primitive:unit
// if it matches, then we're fine, and this is an appropriate match candidate
} else if (queryElem.id === typeNameIdOfHof &&
(fnType.id === typeNameIdOfFn || fnType.id === typeNameIdOfFnMut ||
fnType.id === typeNameIdOfFnOnce)
) {
// -> matches fn, fnonce, and fnmut
// if it matches, then we're fine, and this is an appropriate match candidate
} else if (fnType.id !== queryElem.id || queryElem.id === null) {
return false;
}
@ -1829,6 +1910,7 @@ if (parserState.userQuery[parserState.pos] === "[") {
typePassesFilter(elem.typeFilter, row.ty) && elem.generics.length === 0 &&
// special case
elem.id !== typeNameIdOfArrayOrSlice && elem.id !== typeNameIdOfTupleOrUnit
&& elem.id !== typeNameIdOfHof
) {
return row.id === elem.id || checkIfInList(
row.generics,
@ -2991,7 +3073,7 @@ ${item.displayPath}<span class="${type}">${name}</span>\
*/
function buildFunctionTypeFingerprint(type, output, fps) {
let input = type.id;
// All forms of `[]`/`()` get collapsed down to one thing in the bloom filter.
// All forms of `[]`/`()`/`->` get collapsed down to one thing in the bloom filter.
// Differentiating between arrays and slices, if the user asks for it, is
// still done in the matching algorithm.
if (input === typeNameIdOfArray || input === typeNameIdOfSlice) {
@ -3000,6 +3082,10 @@ ${item.displayPath}<span class="${type}">${name}</span>\
if (input === typeNameIdOfTuple || input === typeNameIdOfUnit) {
input = typeNameIdOfTupleOrUnit;
}
if (input === typeNameIdOfFn || input === typeNameIdOfFnMut ||
input === typeNameIdOfFnOnce) {
input = typeNameIdOfHof;
}
// http://burtleburtle.net/bob/hash/integer.html
// ~~ is toInt32. It's used before adding, so
// the number stays in safe integer range.
@ -3090,20 +3176,10 @@ ${item.displayPath}<span class="${type}">${name}</span>\
*/
function buildIndex(rawSearchIndex) {
searchIndex = [];
typeNameIdMap = new Map();
const charA = "A".charCodeAt(0);
let currentIndex = 0;
let id = 0;
// Initialize type map indexes for primitive list types
// that can be searched using `[]` syntax.
typeNameIdOfArray = buildTypeMapIndex("array");
typeNameIdOfSlice = buildTypeMapIndex("slice");
typeNameIdOfTuple = buildTypeMapIndex("tuple");
typeNameIdOfUnit = buildTypeMapIndex("unit");
typeNameIdOfArrayOrSlice = buildTypeMapIndex("[]");
typeNameIdOfTupleOrUnit = buildTypeMapIndex("()");
// Function type fingerprints are 128-bit bloom filters that are used to
// estimate the distance between function and query.
// This loop counts the number of items to allocate a fingerprint for.

View File

@ -1 +1 @@
The loop took around 7s
The loop took around 12s

View File

@ -114,7 +114,7 @@ const PARSED = [
original: "(p -> p",
returned: [],
userQuery: "(p -> p",
error: "Unexpected `-` after `(`",
error: "Unclosed `(`",
},
{
query: "::a::b",
@ -195,7 +195,7 @@ const PARSED = [
original: "a (b:",
returned: [],
userQuery: "a (b:",
error: "Expected `,`, `:` or `->`, found `(`",
error: "Unclosed `(`",
},
{
query: "_:",
@ -330,7 +330,7 @@ const PARSED = [
original: 'a<->',
returned: [],
userQuery: 'a<->',
error: 'Unexpected `-` after `<`',
error: 'Unclosed `<`',
},
{
query: "a<a>:",
@ -357,7 +357,16 @@ const PARSED = [
original: "a,:",
returned: [],
userQuery: "a,:",
error: 'Unexpected `,` in type filter (before `:`)',
error: 'Expected type filter before `:`',
},
{
query: "a!:",
elems: [],
foundElems: 0,
original: "a!:",
returned: [],
userQuery: "a!:",
error: 'Unexpected `!` in type filter (before `:`)',
},
{
query: " a<> :",
@ -366,7 +375,7 @@ const PARSED = [
original: "a<> :",
returned: [],
userQuery: "a<> :",
error: 'Unexpected `<` in type filter (before `:`)',
error: 'Expected `,`, `:` or `->` after `>`, found `:`',
},
{
query: "mod : :",

View File

@ -0,0 +1,712 @@
const PARSED = [
// ML-style HOF
{
query: "(-> F<P>)",
elems: [{
name: "->",
fullPath: ["->"],
pathWithoutLast: [],
pathLast: "->",
generics: [],
bindings: [
[
"output",
[{
name: "f",
fullPath: ["f"],
pathWithoutLast: [],
pathLast: "f",
generics: [
{
name: "p",
fullPath: ["p"],
pathWithoutLast: [],
pathLast: "p",
generics: [],
},
],
typeFilter: -1,
}],
],
],
typeFilter: -1,
}],
foundElems: 1,
original: "(-> F<P>)",
returned: [],
userQuery: "(-> f<p>)",
error: null,
},
{
query: "(-> P)",
elems: [{
name: "->",
fullPath: ["->"],
pathWithoutLast: [],
pathLast: "->",
generics: [],
bindings: [
[
"output",
[{
name: "p",
fullPath: ["p"],
pathWithoutLast: [],
pathLast: "p",
generics: [],
typeFilter: -1,
}],
],
],
typeFilter: -1,
}],
foundElems: 1,
original: "(-> P)",
returned: [],
userQuery: "(-> p)",
error: null,
},
{
query: "(->,a)",
elems: [{
name: "->",
fullPath: ["->"],
pathWithoutLast: [],
pathLast: "->",
generics: [],
bindings: [
[
"output",
[{
name: "a",
fullPath: ["a"],
pathWithoutLast: [],
pathLast: "a",
generics: [],
typeFilter: -1,
}],
],
],
typeFilter: -1,
}],
foundElems: 1,
original: "(->,a)",
returned: [],
userQuery: "(->,a)",
error: null,
},
{
query: "(F<P> ->)",
elems: [{
name: "->",
fullPath: ["->"],
pathWithoutLast: [],
pathLast: "->",
generics: [{
name: "f",
fullPath: ["f"],
pathWithoutLast: [],
pathLast: "f",
generics: [
{
name: "p",
fullPath: ["p"],
pathWithoutLast: [],
pathLast: "p",
generics: [],
},
],
typeFilter: -1,
}],
bindings: [
[
"output",
[],
],
],
typeFilter: -1,
}],
foundElems: 1,
original: "(F<P> ->)",
returned: [],
userQuery: "(f<p> ->)",
error: null,
},
{
query: "(P ->)",
elems: [{
name: "->",
fullPath: ["->"],
pathWithoutLast: [],
pathLast: "->",
generics: [{
name: "p",
fullPath: ["p"],
pathWithoutLast: [],
pathLast: "p",
generics: [],
typeFilter: -1,
}],
bindings: [
[
"output",
[],
],
],
typeFilter: -1,
}],
foundElems: 1,
original: "(P ->)",
returned: [],
userQuery: "(p ->)",
error: null,
},
{
query: "(,a->)",
elems: [{
name: "->",
fullPath: ["->"],
pathWithoutLast: [],
pathLast: "->",
generics: [{
name: "a",
fullPath: ["a"],
pathWithoutLast: [],
pathLast: "a",
generics: [],
typeFilter: -1,
}],
bindings: [
[
"output",
[],
],
],
typeFilter: -1,
}],
foundElems: 1,
original: "(,a->)",
returned: [],
userQuery: "(,a->)",
error: null,
},
{
query: "(aaaaa->a)",
elems: [{
name: "->",
fullPath: ["->"],
pathWithoutLast: [],
pathLast: "->",
generics: [{
name: "aaaaa",
fullPath: ["aaaaa"],
pathWithoutLast: [],
pathLast: "aaaaa",
generics: [],
typeFilter: -1,
}],
bindings: [
[
"output",
[{
name: "a",
fullPath: ["a"],
pathWithoutLast: [],
pathLast: "a",
generics: [],
typeFilter: -1,
}],
],
],
typeFilter: -1,
}],
foundElems: 1,
original: "(aaaaa->a)",
returned: [],
userQuery: "(aaaaa->a)",
error: null,
},
{
query: "(aaaaa, b -> a)",
elems: [{
name: "->",
fullPath: ["->"],
pathWithoutLast: [],
pathLast: "->",
generics: [
{
name: "aaaaa",
fullPath: ["aaaaa"],
pathWithoutLast: [],
pathLast: "aaaaa",
generics: [],
typeFilter: -1,
},
{
name: "b",
fullPath: ["b"],
pathWithoutLast: [],
pathLast: "b",
generics: [],
typeFilter: -1,
},
],
bindings: [
[
"output",
[{
name: "a",
fullPath: ["a"],
pathWithoutLast: [],
pathLast: "a",
generics: [],
typeFilter: -1,
}],
],
],
typeFilter: -1,
}],
foundElems: 1,
original: "(aaaaa, b -> a)",
returned: [],
userQuery: "(aaaaa, b -> a)",
error: null,
},
{
query: "primitive:(aaaaa, b -> a)",
elems: [{
name: "->",
fullPath: ["->"],
pathWithoutLast: [],
pathLast: "->",
generics: [
{
name: "aaaaa",
fullPath: ["aaaaa"],
pathWithoutLast: [],
pathLast: "aaaaa",
generics: [],
typeFilter: -1,
},
{
name: "b",
fullPath: ["b"],
pathWithoutLast: [],
pathLast: "b",
generics: [],
typeFilter: -1,
},
],
bindings: [
[
"output",
[{
name: "a",
fullPath: ["a"],
pathWithoutLast: [],
pathLast: "a",
generics: [],
typeFilter: -1,
}],
],
],
typeFilter: 1,
}],
foundElems: 1,
original: "primitive:(aaaaa, b -> a)",
returned: [],
userQuery: "primitive:(aaaaa, b -> a)",
error: null,
},
{
query: "x, trait:(aaaaa, b -> a)",
elems: [
{
name: "x",
fullPath: ["x"],
pathWithoutLast: [],
pathLast: "x",
generics: [],
typeFilter: -1,
},
{
name: "->",
fullPath: ["->"],
pathWithoutLast: [],
pathLast: "->",
generics: [
{
name: "aaaaa",
fullPath: ["aaaaa"],
pathWithoutLast: [],
pathLast: "aaaaa",
generics: [],
typeFilter: -1,
},
{
name: "b",
fullPath: ["b"],
pathWithoutLast: [],
pathLast: "b",
generics: [],
typeFilter: -1,
},
],
bindings: [
[
"output",
[{
name: "a",
fullPath: ["a"],
pathWithoutLast: [],
pathLast: "a",
generics: [],
typeFilter: -1,
}],
],
],
typeFilter: 10,
}
],
foundElems: 2,
original: "x, trait:(aaaaa, b -> a)",
returned: [],
userQuery: "x, trait:(aaaaa, b -> a)",
error: null,
},
// Rust-style HOF
{
query: "Fn () -> F<P>",
elems: [{
name: "fn",
fullPath: ["fn"],
pathWithoutLast: [],
pathLast: "fn",
generics: [],
bindings: [
[
"output",
[{
name: "f",
fullPath: ["f"],
pathWithoutLast: [],
pathLast: "f",
generics: [
{
name: "p",
fullPath: ["p"],
pathWithoutLast: [],
pathLast: "p",
generics: [],
},
],
typeFilter: -1,
}],
],
],
typeFilter: -1,
}],
foundElems: 1,
original: "Fn () -> F<P>",
returned: [],
userQuery: "fn () -> f<p>",
error: null,
},
{
query: "FnMut() -> P",
elems: [{
name: "fnmut",
fullPath: ["fnmut"],
pathWithoutLast: [],
pathLast: "fnmut",
generics: [],
bindings: [
[
"output",
[{
name: "p",
fullPath: ["p"],
pathWithoutLast: [],
pathLast: "p",
generics: [],
typeFilter: -1,
}],
],
],
typeFilter: -1,
}],
foundElems: 1,
original: "FnMut() -> P",
returned: [],
userQuery: "fnmut() -> p",
error: null,
},
{
query: "(FnMut() -> P)",
elems: [{
name: "fnmut",
fullPath: ["fnmut"],
pathWithoutLast: [],
pathLast: "fnmut",
generics: [],
bindings: [
[
"output",
[{
name: "p",
fullPath: ["p"],
pathWithoutLast: [],
pathLast: "p",
generics: [],
typeFilter: -1,
}],
],
],
typeFilter: -1,
}],
foundElems: 1,
original: "(FnMut() -> P)",
returned: [],
userQuery: "(fnmut() -> p)",
error: null,
},
{
query: "Fn(F<P>)",
elems: [{
name: "fn",
fullPath: ["fn"],
pathWithoutLast: [],
pathLast: "fn",
generics: [{
name: "f",
fullPath: ["f"],
pathWithoutLast: [],
pathLast: "f",
generics: [
{
name: "p",
fullPath: ["p"],
pathWithoutLast: [],
pathLast: "p",
generics: [],
},
],
typeFilter: -1,
}],
bindings: [
[
"output",
[],
],
],
typeFilter: -1,
}],
foundElems: 1,
original: "Fn(F<P>)",
returned: [],
userQuery: "fn(f<p>)",
error: null,
},
{
query: "primitive:fnonce(aaaaa, b) -> a",
elems: [{
name: "fnonce",
fullPath: ["fnonce"],
pathWithoutLast: [],
pathLast: "fnonce",
generics: [
{
name: "aaaaa",
fullPath: ["aaaaa"],
pathWithoutLast: [],
pathLast: "aaaaa",
generics: [],
typeFilter: -1,
},
{
name: "b",
fullPath: ["b"],
pathWithoutLast: [],
pathLast: "b",
generics: [],
typeFilter: -1,
},
],
bindings: [
[
"output",
[{
name: "a",
fullPath: ["a"],
pathWithoutLast: [],
pathLast: "a",
generics: [],
typeFilter: -1,
}],
],
],
typeFilter: 1,
}],
foundElems: 1,
original: "primitive:fnonce(aaaaa, b) -> a",
returned: [],
userQuery: "primitive:fnonce(aaaaa, b) -> a",
error: null,
},
{
query: "primitive:fnonce(aaaaa, keyword:b) -> trait:a",
elems: [{
name: "fnonce",
fullPath: ["fnonce"],
pathWithoutLast: [],
pathLast: "fnonce",
generics: [
{
name: "aaaaa",
fullPath: ["aaaaa"],
pathWithoutLast: [],
pathLast: "aaaaa",
generics: [],
typeFilter: -1,
},
{
name: "b",
fullPath: ["b"],
pathWithoutLast: [],
pathLast: "b",
generics: [],
typeFilter: 0,
},
],
bindings: [
[
"output",
[{
name: "a",
fullPath: ["a"],
pathWithoutLast: [],
pathLast: "a",
generics: [],
typeFilter: 10,
}],
],
],
typeFilter: 1,
}],
foundElems: 1,
original: "primitive:fnonce(aaaaa, keyword:b) -> trait:a",
returned: [],
userQuery: "primitive:fnonce(aaaaa, keyword:b) -> trait:a",
error: null,
},
{
query: "x, trait:fn(aaaaa, b -> a)",
elems: [
{
name: "x",
fullPath: ["x"],
pathWithoutLast: [],
pathLast: "x",
generics: [],
typeFilter: -1,
},
{
name: "fn",
fullPath: ["fn"],
pathWithoutLast: [],
pathLast: "fn",
generics: [
{
name: "->",
fullPath: ["->"],
pathWithoutLast: [],
pathLast: "->",
generics: [
{
name: "aaaaa",
fullPath: ["aaaaa"],
pathWithoutLast: [],
pathLast: "aaaaa",
generics: [],
typeFilter: -1,
},
{
name: "b",
fullPath: ["b"],
pathWithoutLast: [],
pathLast: "b",
generics: [],
typeFilter: -1,
},
],
bindings: [
[
"output",
[{
name: "a",
fullPath: ["a"],
pathWithoutLast: [],
pathLast: "a",
generics: [],
typeFilter: -1,
}],
],
],
typeFilter: -1,
},
],
bindings: [
[
"output",
[],
]
],
typeFilter: 10,
}
],
foundElems: 2,
original: "x, trait:fn(aaaaa, b -> a)",
returned: [],
userQuery: "x, trait:fn(aaaaa, b -> a)",
error: null,
},
{
query: 'a,b(c)',
elems: [
{
name: "a",
fullPath: ["a"],
pathWithoutLast: [],
pathLast: "a",
generics: [],
typeFilter: -1,
},
{
name: "b",
fullPath: ["b"],
pathWithoutLast: [],
pathLast: "b",
generics: [{
name: "c",
fullPath: ["c"],
pathWithoutLast: [],
pathLast: "c",
generics: [],
typeFilter: -1,
}],
bindings: [
[
"output",
[],
]
],
typeFilter: -1,
}
],
foundElems: 2,
original: "a,b(c)",
returned: [],
userQuery: "a,b(c)",
error: null,
},
];

View File

@ -37,15 +37,6 @@ const PARSED = [
userQuery: "a b",
error: null,
},
{
query: 'a,b(c)',
elems: [],
foundElems: 0,
original: "a,b(c)",
returned: [],
userQuery: "a,b(c)",
error: "Expected `,`, `:` or `->`, found `(`",
},
{
query: 'aaa,a',
elems: [

176
tests/rustdoc-js/hof.js Normal file
View File

@ -0,0 +1,176 @@
// exact-check
const EXPECTED = [
// not a HOF query
{
'query': 'u32 -> !',
'others': [],
},
// ML-style higher-order function notation
{
'query': 'bool, (u32 -> !) -> ()',
'others': [
{"path": "hof", "name": "fn_ptr"},
],
},
{
'query': 'u8, (u32 -> !) -> ()',
'others': [
{"path": "hof", "name": "fn_once"},
],
},
{
'query': 'i8, (u32 -> !) -> ()',
'others': [
{"path": "hof", "name": "fn_mut"},
],
},
{
'query': 'char, (u32 -> !) -> ()',
'others': [
{"path": "hof", "name": "fn_"},
],
},
{
'query': '(first<u32> -> !) -> ()',
'others': [
{"path": "hof", "name": "fn_ptr"},
],
},
{
'query': '(second<u32> -> !) -> ()',
'others': [
{"path": "hof", "name": "fn_once"},
],
},
{
'query': '(third<u32> -> !) -> ()',
'others': [
{"path": "hof", "name": "fn_mut"},
],
},
{
'query': '(u32 -> !) -> ()',
'others': [
{"path": "hof", "name": "fn_"},
{"path": "hof", "name": "fn_ptr"},
{"path": "hof", "name": "fn_mut"},
{"path": "hof", "name": "fn_once"},
],
},
{
'query': '(str, str -> i8) -> ()',
'others': [
{"path": "hof", "name": "multiple"},
],
},
{
'query': '(str ->) -> ()',
'others': [
{"path": "hof", "name": "multiple"},
],
},
{
'query': '(-> i8) -> ()',
'others': [
{"path": "hof", "name": "multiple"},
],
},
{
'query': '(str -> str) -> ()',
// params and return are not the same
'others': [],
},
{
'query': '(i8 ->) -> ()',
// params and return are not the same
'others': [],
},
{
'query': '(-> str) -> ()',
// params and return are not the same
'others': [],
},
// Rust-style higher-order function notation
{
'query': 'bool, fn(u32) -> ! -> ()',
'others': [
{"path": "hof", "name": "fn_ptr"},
],
},
{
'query': 'u8, fnonce(u32) -> ! -> ()',
'others': [
{"path": "hof", "name": "fn_once"},
],
},
{
'query': 'u8, fn(u32) -> ! -> ()',
// fnonce != fn
'others': [],
},
{
'query': 'i8, fnmut(u32) -> ! -> ()',
'others': [
{"path": "hof", "name": "fn_mut"},
],
},
{
'query': 'i8, fn(u32) -> ! -> ()',
// fnmut != fn
'others': [],
},
{
'query': 'char, fn(u32) -> ! -> ()',
'others': [
{"path": "hof", "name": "fn_"},
],
},
{
'query': 'char, fnmut(u32) -> ! -> ()',
// fn != fnmut
'others': [],
},
{
'query': 'fn(first<u32>) -> ! -> ()',
'others': [
{"path": "hof", "name": "fn_ptr"},
],
},
{
'query': 'fnonce(second<u32>) -> ! -> ()',
'others': [
{"path": "hof", "name": "fn_once"},
],
},
{
'query': 'fnmut(third<u32>) -> ! -> ()',
'others': [
{"path": "hof", "name": "fn_mut"},
],
},
{
'query': 'fn(u32) -> ! -> ()',
'others': [
// fn matches primitive:fn and trait:Fn
{"path": "hof", "name": "fn_"},
{"path": "hof", "name": "fn_ptr"},
],
},
{
'query': 'trait:fn(u32) -> ! -> ()',
'others': [
// fn matches primitive:fn and trait:Fn
{"path": "hof", "name": "fn_"},
],
},
{
'query': 'primitive:fn(u32) -> ! -> ()',
'others': [
// fn matches primitive:fn and trait:Fn
{"path": "hof", "name": "fn_ptr"},
],
},
];

12
tests/rustdoc-js/hof.rs Normal file
View File

@ -0,0 +1,12 @@
#![feature(never_type)]
pub struct First<T>(T);
pub struct Second<T>(T);
pub struct Third<T>(T);
pub fn fn_ptr(_: fn (First<u32>) -> !, _: bool) {}
pub fn fn_once(_: impl FnOnce (Second<u32>) -> !, _: u8) {}
pub fn fn_mut(_: impl FnMut (Third<u32>) -> !, _: i8) {}
pub fn fn_(_: impl Fn (u32) -> !, _: char) {}
pub fn multiple(_: impl Fn(&'static str, &'static str) -> i8) {}

View File

@ -0,0 +1,147 @@
//@ run-pass
//! Test a few methods to transform StableMIR.
//@ ignore-stage1
//@ ignore-cross-compile
//@ ignore-remote
//@ ignore-windows-gnu mingw has troubles with linking https://github.com/rust-lang/rust/pull/116837
#![feature(rustc_private)]
#![feature(assert_matches)]
#![feature(control_flow_enum)]
#![feature(ascii_char, ascii_char_variants)]
extern crate rustc_hir;
#[macro_use]
extern crate rustc_smir;
extern crate rustc_driver;
extern crate rustc_interface;
extern crate stable_mir;
use rustc_smir::rustc_internal;
use stable_mir::mir::alloc::GlobalAlloc;
use stable_mir::mir::mono::Instance;
use stable_mir::mir::{Body, Constant, Operand, Rvalue, StatementKind, TerminatorKind};
use stable_mir::ty::{Const, ConstantKind};
use stable_mir::{CrateDef, CrateItems, ItemKind};
use std::convert::TryFrom;
use std::io::Write;
use std::ops::ControlFlow;
const CRATE_NAME: &str = "input";
/// This function uses the Stable MIR APIs to transform the MIR.
fn test_transform() -> ControlFlow<()> {
// Find items in the local crate.
let items = stable_mir::all_local_items();
// Test fn_abi
let target_fn = *get_item(&items, (ItemKind::Fn, "dummy")).unwrap();
let instance = Instance::try_from(target_fn).unwrap();
let body = instance.body().unwrap();
check_msg(&body, "oops");
let new_msg = "new panic message";
let new_body = change_panic_msg(body, new_msg);
check_msg(&new_body, new_msg);
ControlFlow::Continue(())
}
/// Check that the body panic message matches the given message.
fn check_msg(body: &Body, expected: &str) {
let msg = body
.blocks
.iter()
.find_map(|bb| match &bb.terminator.kind {
TerminatorKind::Call { args, .. } => {
assert_eq!(args.len(), 1, "Expected panic message, but found {args:?}");
let msg_const = match &args[0] {
Operand::Constant(msg_const) => msg_const,
Operand::Copy(place) | Operand::Move(place) => {
assert!(place.projection.is_empty());
bb.statements
.iter()
.find_map(|stmt| match &stmt.kind {
StatementKind::Assign(
destination,
Rvalue::Use(Operand::Constant(msg_const)),
) if destination == place => Some(msg_const),
_ => None,
})
.unwrap()
}
};
let ConstantKind::Allocated(alloc) = msg_const.literal.kind() else {
unreachable!()
};
assert_eq!(alloc.provenance.ptrs.len(), 1);
let alloc_prov_id = alloc.provenance.ptrs[0].1 .0;
let GlobalAlloc::Memory(val) = GlobalAlloc::from(alloc_prov_id) else {
unreachable!()
};
let bytes = val.raw_bytes().unwrap();
Some(std::str::from_utf8(&bytes).unwrap().to_string())
}
_ => None,
})
.expect("Failed to find panic message");
assert_eq!(&msg, expected);
}
/// Modify body to use a different panic message.
fn change_panic_msg(mut body: Body, new_msg: &str) -> Body {
for bb in &mut body.blocks {
match &mut bb.terminator.kind {
TerminatorKind::Call { args, .. } => {
let new_const = Const::from_str(new_msg);
args[0] = Operand::Constant(Constant {
literal: new_const,
span: bb.terminator.span,
user_ty: None,
});
}
_ => {}
}
}
body
}
fn get_item<'a>(
items: &'a CrateItems,
item: (ItemKind, &str),
) -> Option<&'a stable_mir::CrateItem> {
items.iter().find(|crate_item| (item.0 == crate_item.kind()) && crate_item.name() == item.1)
}
/// This test will generate and analyze a dummy crate using the stable mir.
/// For that, it will first write the dummy crate into a file.
/// Then it will create a `StableMir` using custom arguments and then
/// it will run the compiler.
fn main() {
let path = "transform_input.rs";
generate_input(&path).unwrap();
let args = vec![
"rustc".to_string(),
"--crate-type=lib".to_string(),
"--crate-name".to_string(),
CRATE_NAME.to_string(),
path.to_string(),
];
run!(args, test_transform).unwrap();
}
fn generate_input(path: &str) -> std::io::Result<()> {
let mut file = std::fs::File::create(path)?;
write!(
file,
r#"
#![feature(panic_internals)]
pub fn dummy() {{
core::panicking::panic_str("oops");
}}
"#
)?;
Ok(())
}

View File

@ -1,15 +0,0 @@
error[E0080]: evaluation of `PrintName::<i32>::VOID` failed
--> $DIR/erroneous-const.rs:6:22
|
LL | const VOID: () = [()][2];
| ^^^^^^^ index out of bounds: the length is 1 but the index is 2
note: erroneous constant encountered
--> $DIR/erroneous-const.rs:13:13
|
LL | PrintName::<T>::VOID;
| ^^^^^^^^^^^^^^^^^^^^
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0080`.

View File

@ -1,19 +0,0 @@
//! Make sure we error on erroneous consts even if they are unused.
#![allow(unconditional_panic)]
struct PrintName<T>(T);
impl<T> PrintName<T> {
const VOID: () = [()][2]; //~ERROR evaluation of `PrintName::<i32>::VOID` failed
}
pub static FOO: () = {
if false {
// This bad constant is only used in dead code in a static initializer... and yet we still
// must make sure that the build fails.
PrintName::<i32>::VOID; //~ constant
}
};
fn main() {
FOO
}

View File

@ -1,15 +0,0 @@
error[E0080]: evaluation of `PrintName::<i32>::VOID` failed
--> $DIR/erroneous-const2.rs:6:22
|
LL | const VOID: () = [()][2];
| ^^^^^^^ index out of bounds: the length is 1 but the index is 2
note: erroneous constant encountered
--> $DIR/erroneous-const2.rs:13:9
|
LL | PrintName::<i32>::VOID;
| ^^^^^^^^^^^^^^^^^^^^^^
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0080`.

View File

@ -1,11 +0,0 @@
error[E0080]: evaluation of `PrintName::<i32>::VOID` failed
--> $DIR/unused-broken-const-late.rs:8:22
|
LL | const VOID: () = panic!();
| ^^^^^^^^ the evaluated program panicked at 'explicit panic', $DIR/unused-broken-const-late.rs:8:22
|
= note: this error originates in the macro `$crate::panic::panic_2015` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info)
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0080`.

View File

@ -0,0 +1,17 @@
error[E0080]: evaluation of `Fail::<i32>::C` failed
--> $DIR/collect-in-called-fn.rs:9:19
|
LL | const C: () = panic!();
| ^^^^^^^^ the evaluated program panicked at 'explicit panic', $DIR/collect-in-called-fn.rs:9:19
|
= note: this error originates in the macro `$crate::panic::panic_2015` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info)
note: the above error was encountered while instantiating `fn called::<i32>`
--> $DIR/collect-in-called-fn.rs:23:5
|
LL | called::<i32>();
| ^^^^^^^^^^^^^^^
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0080`.

View File

@ -0,0 +1,17 @@
error[E0080]: evaluation of `Fail::<i32>::C` failed
--> $DIR/collect-in-called-fn.rs:9:19
|
LL | const C: () = panic!();
| ^^^^^^^^ the evaluated program panicked at 'explicit panic', $DIR/collect-in-called-fn.rs:9:19
|
= note: this error originates in the macro `$crate::panic::panic_2015` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info)
note: the above error was encountered while instantiating `fn called::<i32>`
--> $DIR/collect-in-called-fn.rs:23:5
|
LL | called::<i32>();
| ^^^^^^^^^^^^^^^
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0080`.

View File

@ -1,20 +1,24 @@
//@revisions: noopt opt
//@ build-fail
//@ compile-flags: -O
//@[opt] compile-flags: -O
//! Make sure we detect erroneous constants post-monomorphization even when they are unused. This is
//! crucial, people rely on it for soundness. (https://github.com/rust-lang/rust/issues/112090)
struct PrintName<T>(T);
impl<T> PrintName<T> {
const VOID: () = panic!(); //~ERROR evaluation of `PrintName::<i32>::VOID` failed
struct Fail<T>(T);
impl<T> Fail<T> {
const C: () = panic!(); //~ERROR evaluation of `Fail::<i32>::C` failed
}
fn no_codegen<T>() {
#[inline(never)]
fn called<T>() {
// Any function that is called is guaranteed to have all consts that syntactically
// appear in its body evaluated, even if they only appear in dead code.
// This relies on mono-item collection checking `required_consts` in collected functions.
if false {
let _ = PrintName::<T>::VOID;
let _ = Fail::<T>::C;
}
}
pub fn main() {
no_codegen::<i32>();
called::<i32>();
}

View File

@ -0,0 +1,14 @@
error[E0080]: evaluation of `Fail::<i32>::C` failed
--> $DIR/collect-in-dead-drop.rs:12:19
|
LL | const C: () = panic!();
| ^^^^^^^^ the evaluated program panicked at 'explicit panic', $DIR/collect-in-dead-drop.rs:12:19
|
= note: this error originates in the macro `$crate::panic::panic_2015` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info)
note: the above error was encountered while instantiating `fn <Fail<i32> as std::ops::Drop>::drop`
--> $SRC_DIR/core/src/ptr/mod.rs:LL:COL
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0080`.

View File

@ -0,0 +1,33 @@
//@revisions: noopt opt
//@[noopt] build-fail
//@[opt] compile-flags: -O
//FIXME: `opt` revision currently does not stop with an error due to
//<https://github.com/rust-lang/rust/issues/107503>.
//@[opt] build-pass
//! Make sure we detect erroneous constants post-monomorphization even when they are unused. This is
//! crucial, people rely on it for soundness. (https://github.com/rust-lang/rust/issues/112090)
struct Fail<T>(T);
impl<T> Fail<T> {
const C: () = panic!(); //[noopt]~ERROR evaluation of `Fail::<i32>::C` failed
}
// This function is not actually called, but is mentioned implicitly as destructor in dead code in a
// function that is called. Make sure we still find this error.
impl<T> Drop for Fail<T> {
fn drop(&mut self) {
let _ = Fail::<T>::C;
}
}
#[inline(never)]
fn called<T>(x: T) {
if false {
let v = Fail(x);
// Now it gest dropped implicitly, at the end of this scope.
}
}
pub fn main() {
called::<i32>(0);
}

View File

@ -0,0 +1,17 @@
error[E0080]: evaluation of `Fail::<i32>::C` failed
--> $DIR/collect-in-dead-fn.rs:12:19
|
LL | const C: () = panic!();
| ^^^^^^^^ the evaluated program panicked at 'explicit panic', $DIR/collect-in-dead-fn.rs:12:19
|
= note: this error originates in the macro `$crate::panic::panic_2015` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info)
note: the above error was encountered while instantiating `fn not_called::<i32>`
--> $DIR/collect-in-dead-fn.rs:29:9
|
LL | not_called::<T>();
| ^^^^^^^^^^^^^^^^^
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0080`.

View File

@ -0,0 +1,35 @@
//@revisions: noopt opt
//@[noopt] build-fail
//@[opt] compile-flags: -O
//FIXME: `opt` revision currently does not stop with an error due to
//<https://github.com/rust-lang/rust/issues/107503>.
//@[opt] build-pass
//! Make sure we detect erroneous constants post-monomorphization even when they are unused. This is
//! crucial, people rely on it for soundness. (https://github.com/rust-lang/rust/issues/112090)
struct Fail<T>(T);
impl<T> Fail<T> {
const C: () = panic!(); //[noopt]~ERROR evaluation of `Fail::<i32>::C` failed
}
// This function is not actually called, but it is mentioned in dead code in a function that is
// called. Make sure we still find this error.
// This relies on mono-item collection checking `required_consts` in functions that syntactically
// are called in collected functions (even inside dead code).
#[inline(never)]
fn not_called<T>() {
if false {
let _ = Fail::<T>::C;
}
}
#[inline(never)]
fn called<T>() {
if false {
not_called::<T>();
}
}
pub fn main() {
called::<i32>();
}

View File

@ -0,0 +1,32 @@
//@revisions: noopt opt
//@build-pass
//@[opt] compile-flags: -O
//! Make sure we detect erroneous constants post-monomorphization even when they are unused. This is
//! crucial, people rely on it for soundness. (https://github.com/rust-lang/rust/issues/112090)
struct Fail<T>(T);
impl<T> Fail<T> {
const C: () = panic!();
}
// This function is not actually called, but is mentioned implicitly as destructor in dead code in a
// function that is called. Make sure we still find this error.
impl<T> Drop for Fail<T> {
fn drop(&mut self) {
let _ = Fail::<T>::C;
}
}
#[inline(never)]
fn called<T>(x: T) {
if false {
let v = Fail(x);
std::mem::forget(v);
// Now the destructor never gets "mentioned" so this build should *not* fail.
// IOW, this demonstrates that we are using a post-drop-elab notion of "mentioned".
}
}
pub fn main() {
called::<i32>(0);
}

View File

@ -0,0 +1,14 @@
error[E0080]: evaluation of `Fail::<i32>::C` failed
--> $DIR/collect-in-dead-move.rs:12:19
|
LL | const C: () = panic!();
| ^^^^^^^^ the evaluated program panicked at 'explicit panic', $DIR/collect-in-dead-move.rs:12:19
|
= note: this error originates in the macro `$crate::panic::panic_2015` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info)
note: the above error was encountered while instantiating `fn <Fail<i32> as std::ops::Drop>::drop`
--> $SRC_DIR/core/src/ptr/mod.rs:LL:COL
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0080`.

View File

@ -0,0 +1,33 @@
//@revisions: noopt opt
//@[noopt] build-fail
//@[opt] compile-flags: -O
//FIXME: `opt` revision currently does not stop with an error due to
//<https://github.com/rust-lang/rust/issues/107503>.
//@[opt] build-pass
//! Make sure we detect erroneous constants post-monomorphization even when they are unused. This is
//! crucial, people rely on it for soundness. (https://github.com/rust-lang/rust/issues/112090)
struct Fail<T>(T);
impl<T> Fail<T> {
const C: () = panic!(); //[noopt]~ERROR evaluation of `Fail::<i32>::C` failed
}
// This function is not actually called, but is mentioned implicitly as destructor in dead code in a
// function that is called. Make sure we still find this error.
impl<T> Drop for Fail<T> {
fn drop(&mut self) {
let _ = Fail::<T>::C;
}
}
#[inline(never)]
fn called<T>(x: T) {
if false {
let v = Fail(x);
drop(v); // move `v` away (and it then gets dropped there so build still fails)
}
}
pub fn main() {
called::<i32>(0);
}

View File

@ -0,0 +1,17 @@
error[E0080]: evaluation of `Fail::<i32>::C` failed
--> $DIR/collect-in-dead-vtable.rs:12:19
|
LL | const C: () = panic!();
| ^^^^^^^^ the evaluated program panicked at 'explicit panic', $DIR/collect-in-dead-vtable.rs:12:19
|
= note: this error originates in the macro `$crate::panic::panic_2015` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info)
note: the above error was encountered while instantiating `fn <std::vec::Vec<i32> as MyTrait>::not_called`
--> $DIR/collect-in-dead-vtable.rs:35:40
|
LL | let gen_vtable: &dyn MyTrait = &v; // vtable "appears" here
| ^^
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0080`.

View File

@ -0,0 +1,41 @@
//@revisions: noopt opt
//@[noopt] build-fail
//@[opt] compile-flags: -O
//FIXME: `opt` revision currently does not stop with an error due to
//<https://github.com/rust-lang/rust/issues/107503>.
//@[opt] build-pass
//! Make sure we detect erroneous constants post-monomorphization even when they are unused. This is
//! crucial, people rely on it for soundness. (https://github.com/rust-lang/rust/issues/112090)
struct Fail<T>(T);
impl<T> Fail<T> {
const C: () = panic!(); //[noopt]~ERROR evaluation of `Fail::<i32>::C` failed
}
trait MyTrait {
fn not_called(&self);
}
// This function is not actually called, but it is mentioned in a vtable in a function that is
// called. Make sure we still find this error.
// This relies on mono-item collection checking `required_consts` in functions that are referenced
// in vtables that syntactically appear in collected functions (even inside dead code).
impl<T> MyTrait for Vec<T> {
fn not_called(&self) {
if false {
let _ = Fail::<T>::C;
}
}
}
#[inline(never)]
fn called<T>() {
if false {
let v: Vec<T> = Vec::new();
let gen_vtable: &dyn MyTrait = &v; // vtable "appears" here
}
}
pub fn main() {
called::<i32>();
}

View File

@ -0,0 +1,17 @@
error[E0080]: evaluation of `Fail::<i32>::C` failed
--> $DIR/interpret-in-const-called-fn.rs:7:19
|
LL | const C: () = panic!();
| ^^^^^^^^ the evaluated program panicked at 'explicit panic', $DIR/interpret-in-const-called-fn.rs:7:19
|
= note: this error originates in the macro `$crate::panic::panic_2015` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info)
note: erroneous constant encountered
--> $DIR/interpret-in-const-called-fn.rs:16:9
|
LL | Fail::<T>::C;
| ^^^^^^^^^^^^
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0080`.

View File

@ -0,0 +1,17 @@
error[E0080]: evaluation of `Fail::<i32>::C` failed
--> $DIR/interpret-in-const-called-fn.rs:7:19
|
LL | const C: () = panic!();
| ^^^^^^^^ the evaluated program panicked at 'explicit panic', $DIR/interpret-in-const-called-fn.rs:7:19
|
= note: this error originates in the macro `$crate::panic::panic_2015` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info)
note: erroneous constant encountered
--> $DIR/interpret-in-const-called-fn.rs:16:9
|
LL | Fail::<T>::C;
| ^^^^^^^^^^^^
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0080`.

View File

@ -1,16 +1,19 @@
//@revisions: noopt opt
//@[opt] compile-flags: -O
//! Make sure we error on erroneous consts even if they are unused.
#![allow(unconditional_panic)]
struct PrintName<T>(T);
impl<T> PrintName<T> {
const VOID: () = [()][2]; //~ERROR evaluation of `PrintName::<i32>::VOID` failed
struct Fail<T>(T);
impl<T> Fail<T> {
const C: () = panic!(); //~ERROR evaluation of `Fail::<i32>::C` failed
}
#[inline(never)]
const fn no_codegen<T>() {
if false {
// This bad constant is only used in dead code in a no-codegen function... and yet we still
// must make sure that the build fails.
PrintName::<T>::VOID; //~ constant
// This relies on const-eval evaluating all `required_consts` of `const fn`.
Fail::<T>::C; //~ constant
}
}

View File

@ -0,0 +1,27 @@
error[E0080]: evaluation of constant value failed
--> $SRC_DIR/core/src/hint.rs:LL:COL
|
= note: entering unreachable code
|
note: inside `unreachable_unchecked`
--> $SRC_DIR/core/src/hint.rs:LL:COL
note: inside `ub`
--> $DIR/interpret-in-promoted.rs:6:5
|
LL | std::hint::unreachable_unchecked();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: inside `FOO`
--> $DIR/interpret-in-promoted.rs:12:28
|
LL | let _x: &'static () = &ub();
| ^^^^
note: erroneous constant encountered
--> $DIR/interpret-in-promoted.rs:12:27
|
LL | let _x: &'static () = &ub();
| ^^^^^
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0080`.

View File

@ -0,0 +1,27 @@
error[E0080]: evaluation of constant value failed
--> $SRC_DIR/core/src/hint.rs:LL:COL
|
= note: entering unreachable code
|
note: inside `unreachable_unchecked`
--> $SRC_DIR/core/src/hint.rs:LL:COL
note: inside `ub`
--> $DIR/interpret-in-promoted.rs:6:5
|
LL | std::hint::unreachable_unchecked();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: inside `FOO`
--> $DIR/interpret-in-promoted.rs:12:28
|
LL | let _x: &'static () = &ub();
| ^^^^
note: erroneous constant encountered
--> $DIR/interpret-in-promoted.rs:12:27
|
LL | let _x: &'static () = &ub();
| ^^^^^
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0080`.

View File

@ -0,0 +1,15 @@
//@revisions: noopt opt
//@[opt] compile-flags: -O
//! Make sure we error on erroneous consts even if they are unused.
const unsafe fn ub() {
std::hint::unreachable_unchecked();
}
pub const FOO: () = unsafe {
// Make sure that this gets promoted and then fails to evaluate, and we deal with that
// correctly.
let _x: &'static () = &ub(); //~ erroneous constant
};
fn main() {}

View File

@ -0,0 +1,17 @@
error[E0080]: evaluation of `Fail::<i32>::C` failed
--> $DIR/interpret-in-static.rs:7:19
|
LL | const C: () = panic!();
| ^^^^^^^^ the evaluated program panicked at 'explicit panic', $DIR/interpret-in-static.rs:7:19
|
= note: this error originates in the macro `$crate::panic::panic_2015` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info)
note: erroneous constant encountered
--> $DIR/interpret-in-static.rs:15:9
|
LL | Fail::<i32>::C;
| ^^^^^^^^^^^^^^
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0080`.

View File

@ -0,0 +1,17 @@
error[E0080]: evaluation of `Fail::<i32>::C` failed
--> $DIR/interpret-in-static.rs:7:19
|
LL | const C: () = panic!();
| ^^^^^^^^ the evaluated program panicked at 'explicit panic', $DIR/interpret-in-static.rs:7:19
|
= note: this error originates in the macro `$crate::panic::panic_2015` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info)
note: erroneous constant encountered
--> $DIR/interpret-in-static.rs:15:9
|
LL | Fail::<i32>::C;
| ^^^^^^^^^^^^^^
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0080`.

View File

@ -0,0 +1,21 @@
//@revisions: noopt opt
//@[opt] compile-flags: -O
//! Make sure we error on erroneous consts even if they are unused.
struct Fail<T>(T);
impl<T> Fail<T> {
const C: () = panic!(); //~ERROR evaluation of `Fail::<i32>::C` failed
}
pub static FOO: () = {
if false {
// This bad constant is only used in dead code in a static initializer... and yet we still
// must make sure that the build fails.
// This relies on const-eval evaluating all `required_consts` of the `static` MIR body.
Fail::<i32>::C; //~ constant
}
};
fn main() {
FOO
}