mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-22 23:04:33 +00:00
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:
commit
c2fbe404d2
@ -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,
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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"
|
||||
|
@ -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),
|
||||
|
@ -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
|
||||
|
@ -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)]
|
||||
|
@ -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<'_> {}
|
||||
|
@ -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> {
|
||||
|
@ -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]
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -1 +1 @@
|
||||
The loop took around 7s
|
||||
The loop took around 12s
|
||||
|
@ -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 : :",
|
||||
|
712
tests/rustdoc-js-std/parser-hof.js
Normal file
712
tests/rustdoc-js-std/parser-hof.js
Normal 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,
|
||||
},
|
||||
];
|
@ -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
176
tests/rustdoc-js/hof.js
Normal 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
12
tests/rustdoc-js/hof.rs
Normal 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) {}
|
147
tests/ui-fulldeps/stable-mir/check_transform.rs
Normal file
147
tests/ui-fulldeps/stable-mir/check_transform.rs
Normal 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(())
|
||||
}
|
@ -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`.
|
@ -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
|
||||
}
|
@ -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`.
|
@ -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`.
|
@ -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`.
|
@ -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`.
|
@ -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>();
|
||||
}
|
@ -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`.
|
33
tests/ui/consts/required-consts/collect-in-dead-drop.rs
Normal file
33
tests/ui/consts/required-consts/collect-in-dead-drop.rs
Normal 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);
|
||||
}
|
@ -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`.
|
35
tests/ui/consts/required-consts/collect-in-dead-fn.rs
Normal file
35
tests/ui/consts/required-consts/collect-in-dead-fn.rs
Normal 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>();
|
||||
}
|
32
tests/ui/consts/required-consts/collect-in-dead-forget.rs
Normal file
32
tests/ui/consts/required-consts/collect-in-dead-forget.rs
Normal 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);
|
||||
}
|
@ -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`.
|
33
tests/ui/consts/required-consts/collect-in-dead-move.rs
Normal file
33
tests/ui/consts/required-consts/collect-in-dead-move.rs
Normal 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);
|
||||
}
|
@ -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`.
|
41
tests/ui/consts/required-consts/collect-in-dead-vtable.rs
Normal file
41
tests/ui/consts/required-consts/collect-in-dead-vtable.rs
Normal 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>();
|
||||
}
|
@ -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`.
|
@ -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`.
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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`.
|
@ -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`.
|
15
tests/ui/consts/required-consts/interpret-in-promoted.rs
Normal file
15
tests/ui/consts/required-consts/interpret-in-promoted.rs
Normal 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() {}
|
@ -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`.
|
@ -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`.
|
21
tests/ui/consts/required-consts/interpret-in-static.rs
Normal file
21
tests/ui/consts/required-consts/interpret-in-static.rs
Normal 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
|
||||
}
|
Loading…
Reference in New Issue
Block a user