rustdoc-search: simplify rules for generics and type params

This commit is a response to feedback on the displayed type
signatures results, by making generics act stricter.

Generics are tightened by making order significant. This means
`Vec<Allocator>` now matches only with a true vector of allocators,
instead of matching the second type param. It also makes unboxing
within generics stricter, so `Result<A, B>` only matches if `B`
is in the error type and `A` is in the success type. The top level
of the function search is unaffected.

Find the discussion on:

* <https://rust-lang.zulipchat.com/#narrow/stream/393423-t-rustdoc.2Fmeetings/topic/meeting.202024-07-08/near/449965149>
* <https://github.com/rust-lang/rust/pull/124544#issuecomment-2204272265>
* <https://rust-lang.zulipchat.com/#narrow/channel/266220-t-rustdoc/topic/deciding.20on.20semantics.20of.20generics.20in.20rustdoc.20search/near/476841363>
This commit is contained in:
Michael Howell 2024-09-24 18:18:01 -07:00
parent 20a4b4fea1
commit 12dc24f460
40 changed files with 630 additions and 217 deletions

View File

@ -204,6 +204,7 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> {
"meant for internal use only" {
keyword => rustdoc_internals
fake_variadic => rustdoc_internals
search_unbox => rustdoc_internals
}
);
}

View File

@ -234,6 +234,9 @@ passes_doc_masked_only_extern_crate =
passes_doc_rust_logo =
the `#[doc(rust_logo)]` attribute is used for Rust branding
passes_doc_search_unbox_invalid =
`#[doc(search_unbox)]` should be used on generic structs and enums
passes_doc_test_literal = `#![doc(test(...)]` does not take a literal
passes_doc_test_takes_list =

View File

@ -16,8 +16,8 @@ use rustc_feature::{AttributeDuplicates, AttributeType, BUILTIN_ATTRIBUTE_MAP, B
use rustc_hir::def_id::LocalModDefId;
use rustc_hir::intravisit::{self, Visitor};
use rustc_hir::{
self as hir, self, CRATE_HIR_ID, CRATE_OWNER_ID, FnSig, ForeignItem, HirId, Item, ItemKind,
MethodKind, Safety, Target, TraitItem,
self as hir, self, AssocItemKind, CRATE_HIR_ID, CRATE_OWNER_ID, FnSig, ForeignItem, HirId,
Item, ItemKind, MethodKind, Safety, Target, TraitItem,
};
use rustc_macros::LintDiagnostic;
use rustc_middle::hir::nested_filter;
@ -940,6 +940,23 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
}
}
fn check_doc_search_unbox(&self, meta: &MetaItemInner, hir_id: HirId) {
let hir::Node::Item(item) = self.tcx.hir_node(hir_id) else {
self.dcx().emit_err(errors::DocSearchUnboxInvalid { span: meta.span() });
return;
};
match item.kind {
ItemKind::Enum(_, generics) | ItemKind::Struct(_, generics)
if generics.params.len() != 0 => {}
ItemKind::Trait(_, _, generics, _, items)
if generics.params.len() != 0
|| items.iter().any(|item| matches!(item.kind, AssocItemKind::Type)) => {}
_ => {
self.dcx().emit_err(errors::DocSearchUnboxInvalid { span: meta.span() });
}
}
}
/// Checks `#[doc(inline)]`/`#[doc(no_inline)]` attributes.
///
/// A doc inlining attribute is invalid if it is applied to a non-`use` item, or
@ -1152,6 +1169,12 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
}
}
sym::search_unbox => {
if self.check_attr_not_crate_level(meta, hir_id, "fake_variadic") {
self.check_doc_search_unbox(meta, hir_id);
}
}
sym::test => {
if self.check_attr_crate_level(attr, meta, hir_id) {
self.check_test_attr(meta, hir_id);

View File

@ -244,6 +244,13 @@ pub(crate) struct DocKeywordOnlyImpl {
pub span: Span,
}
#[derive(Diagnostic)]
#[diag(passes_doc_search_unbox_invalid)]
pub(crate) struct DocSearchUnboxInvalid {
#[primary_span]
pub span: Span,
}
#[derive(Diagnostic)]
#[diag(passes_doc_inline_conflict)]
#[help]

View File

@ -1762,6 +1762,7 @@ symbols! {
saturating_add,
saturating_div,
saturating_sub,
search_unbox,
select_unpredictable,
self_in_typedefs,
self_struct_ctor,

View File

@ -225,6 +225,7 @@ pub use thin::ThinBox;
#[fundamental]
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_insignificant_dtor]
#[cfg_attr(not(bootstrap), doc(search_unbox))]
// The declaration of the `Box` struct must be kept in sync with the
// compiler or ICEs will happen.
pub struct Box<

View File

@ -307,6 +307,7 @@ fn rc_inner_layout_for_value_layout(layout: Layout) -> Layout {
/// `value.get_mut()`. This avoids conflicts with methods of the inner type `T`.
///
/// [get_mut]: Rc::get_mut
#[cfg_attr(not(bootstrap), doc(search_unbox))]
#[cfg_attr(not(test), rustc_diagnostic_item = "Rc")]
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_insignificant_dtor]

View File

@ -235,6 +235,7 @@ macro_rules! acquire {
/// counting in general.
///
/// [rc_examples]: crate::rc#examples
#[cfg_attr(not(bootstrap), doc(search_unbox))]
#[cfg_attr(not(test), rustc_diagnostic_item = "Arc")]
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_insignificant_dtor]

View File

@ -25,6 +25,7 @@ use crate::task::{Context, Poll};
/// [`async`]: ../../std/keyword.async.html
/// [`Waker`]: crate::task::Waker
#[doc(notable_trait)]
#[cfg_attr(not(bootstrap), doc(search_unbox))]
#[must_use = "futures do nothing unless you `.await` or poll them"]
#[stable(feature = "futures_api", since = "1.36.0")]
#[lang = "future_trait"]

View File

@ -563,6 +563,7 @@ use crate::pin::Pin;
use crate::{cmp, convert, hint, mem, slice};
/// The `Option` type. See [the module level documentation](self) for more.
#[cfg_attr(not(bootstrap), doc(search_unbox))]
#[derive(Copy, Eq, Debug, Hash)]
#[rustc_diagnostic_item = "Option"]
#[lang = "Option"]

View File

@ -520,6 +520,7 @@ use crate::{convert, fmt, hint};
/// `Result` is a type that represents either success ([`Ok`]) or failure ([`Err`]).
///
/// See the [module documentation](self) for details.
#[cfg_attr(not(bootstrap), doc(search_unbox))]
#[derive(Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)]
#[must_use = "this `Result` may be an `Err` variant, which should be handled"]
#[rustc_diagnostic_item = "Result"]

View File

@ -130,29 +130,31 @@ pub trait MyTrait {
/// This function can be found using the following search queries:
///
/// MyTrait<First=u8, Second=u32> -> bool
/// MyTrait<u32, First=u8> -> bool
/// MyTrait<Second=u32> -> bool
/// MyTrait<u32, u8> -> bool
///
/// The following queries, however, will *not* match it:
///
/// MyTrait<First=u32> -> bool
/// MyTrait<u32, u32> -> bool
/// MyTrait<u32, First=u8> -> bool
/// MyTrait<u32, u8> -> bool
pub fn my_fn(x: impl MyTrait<First=u8, Second=u32>) -> bool { true }
```
Generics and function parameters are order-agnostic, but sensitive to nesting
Function parameters are order-agnostic, but sensitive to nesting
and number of matches. For example, a function with the signature
`fn read_all(&mut self: impl Read) -> Result<Vec<u8>, Error>`
will match these queries:
* `&mut Read -> Result<Vec<u8>, Error>`
* `Read -> Result<Vec<u8>, Error>`
* `Read -> Result<Error, Vec>`
* `Read -> Result<Vec<u8>>`
* `Read -> u8`
But it *does not* match `Result<Vec, u8>` or `Result<u8<Vec>>`.
But it *does not* match `Result<Vec, u8>` or `Result<u8<Vec>>`,
because those are nested incorrectly, and it does not match
`Result<Error, Vec<u8>>` or `Result<Error>`, because those are
in the wrong order.
To search for a function that accepts a function as a parameter,
like `Iterator::all`, wrap the nested signature in parenthesis,

View File

@ -13,8 +13,8 @@ use serde::ser::{Serialize, SerializeSeq, SerializeStruct, Serializer};
use thin_vec::ThinVec;
use tracing::instrument;
use crate::clean;
use crate::clean::types::{Function, Generics, ItemId, Type, WherePredicate};
use crate::clean::{self, utils};
use crate::formats::cache::{Cache, OrphanImplItem};
use crate::formats::item_type::ItemType;
use crate::html::format::join_with_double_colon;
@ -66,7 +66,7 @@ pub(crate) fn build_index<'tcx>(
let mut associated_types = FxHashMap::default();
// item type, display path, re-exported internal path
let mut crate_paths: Vec<(ItemType, Vec<Symbol>, Option<Vec<Symbol>>)> = vec![];
let mut crate_paths: Vec<(ItemType, Vec<Symbol>, Option<Vec<Symbol>>, bool)> = vec![];
// Attach all orphan items to the type's definition if the type
// has since been learned.
@ -132,10 +132,11 @@ pub(crate) fn build_index<'tcx>(
map: &mut FxHashMap<F, isize>,
itemid: F,
lastpathid: &mut isize,
crate_paths: &mut Vec<(ItemType, Vec<Symbol>, Option<Vec<Symbol>>)>,
crate_paths: &mut Vec<(ItemType, Vec<Symbol>, Option<Vec<Symbol>>, bool)>,
item_type: ItemType,
path: &[Symbol],
exact_path: Option<&[Symbol]>,
search_unbox: bool,
) -> RenderTypeId {
match map.entry(itemid) {
Entry::Occupied(entry) => RenderTypeId::Index(*entry.get()),
@ -147,6 +148,7 @@ pub(crate) fn build_index<'tcx>(
item_type,
path.to_vec(),
exact_path.map(|path| path.to_vec()),
search_unbox,
));
RenderTypeId::Index(pathid)
}
@ -160,9 +162,21 @@ pub(crate) fn build_index<'tcx>(
primitives: &mut FxHashMap<Symbol, isize>,
associated_types: &mut FxHashMap<Symbol, isize>,
lastpathid: &mut isize,
crate_paths: &mut Vec<(ItemType, Vec<Symbol>, Option<Vec<Symbol>>)>,
crate_paths: &mut Vec<(ItemType, Vec<Symbol>, Option<Vec<Symbol>>, bool)>,
tcx: TyCtxt<'_>,
) -> Option<RenderTypeId> {
use crate::clean::PrimitiveType;
let Cache { ref paths, ref external_paths, ref exact_paths, .. } = *cache;
let search_unbox = match id {
RenderTypeId::Mut => false,
RenderTypeId::DefId(defid) => utils::has_doc_flag(tcx, defid, sym::search_unbox),
RenderTypeId::Primitive(PrimitiveType::Reference | PrimitiveType::Tuple) => true,
RenderTypeId::Primitive(..) => false,
RenderTypeId::AssociatedType(..) => false,
// this bool is only used by `insert_into_map`, so it doesn't matter what we set here
// because Index means we've already inserted into the map
RenderTypeId::Index(_) => false,
};
match id {
RenderTypeId::Mut => Some(insert_into_map(
primitives,
@ -172,6 +186,7 @@ pub(crate) fn build_index<'tcx>(
ItemType::Keyword,
&[kw::Mut],
None,
search_unbox,
)),
RenderTypeId::DefId(defid) => {
if let Some(&(ref fqp, item_type)) =
@ -195,6 +210,7 @@ pub(crate) fn build_index<'tcx>(
item_type,
fqp,
exact_fqp.map(|x| &x[..]).filter(|exact_fqp| exact_fqp != fqp),
search_unbox,
))
} else {
None
@ -210,6 +226,7 @@ pub(crate) fn build_index<'tcx>(
ItemType::Primitive,
&[sym],
None,
search_unbox,
))
}
RenderTypeId::Index(_) => Some(id),
@ -221,6 +238,7 @@ pub(crate) fn build_index<'tcx>(
ItemType::AssocType,
&[sym],
None,
search_unbox,
)),
}
}
@ -232,7 +250,8 @@ pub(crate) fn build_index<'tcx>(
primitives: &mut FxHashMap<Symbol, isize>,
associated_types: &mut FxHashMap<Symbol, isize>,
lastpathid: &mut isize,
crate_paths: &mut Vec<(ItemType, Vec<Symbol>, Option<Vec<Symbol>>)>,
crate_paths: &mut Vec<(ItemType, Vec<Symbol>, Option<Vec<Symbol>>, bool)>,
tcx: TyCtxt<'_>,
) {
if let Some(generics) = &mut ty.generics {
for item in generics {
@ -244,6 +263,7 @@ pub(crate) fn build_index<'tcx>(
associated_types,
lastpathid,
crate_paths,
tcx,
);
}
}
@ -257,6 +277,7 @@ pub(crate) fn build_index<'tcx>(
associated_types,
lastpathid,
crate_paths,
tcx,
);
let Some(converted_associated_type) = converted_associated_type else {
return false;
@ -271,6 +292,7 @@ pub(crate) fn build_index<'tcx>(
associated_types,
lastpathid,
crate_paths,
tcx,
);
}
true
@ -288,6 +310,7 @@ pub(crate) fn build_index<'tcx>(
associated_types,
lastpathid,
crate_paths,
tcx,
);
}
if let Some(search_type) = &mut item.search_type {
@ -300,6 +323,7 @@ pub(crate) fn build_index<'tcx>(
&mut associated_types,
&mut lastpathid,
&mut crate_paths,
tcx,
);
}
for item in &mut search_type.output {
@ -311,6 +335,7 @@ pub(crate) fn build_index<'tcx>(
&mut associated_types,
&mut lastpathid,
&mut crate_paths,
tcx,
);
}
for constraint in &mut search_type.where_clause {
@ -323,6 +348,7 @@ pub(crate) fn build_index<'tcx>(
&mut associated_types,
&mut lastpathid,
&mut crate_paths,
tcx,
);
}
}
@ -350,7 +376,12 @@ pub(crate) fn build_index<'tcx>(
.filter(|exact_fqp| {
exact_fqp.last() == Some(&item.name) && *exact_fqp != fqp
});
crate_paths.push((short, fqp.clone(), exact_fqp.cloned()));
crate_paths.push((
short,
fqp.clone(),
exact_fqp.cloned(),
utils::has_doc_flag(tcx, defid, sym::search_unbox),
));
Some(pathid)
} else {
None
@ -431,7 +462,7 @@ pub(crate) fn build_index<'tcx>(
struct CrateData<'a> {
items: Vec<&'a IndexItem>,
paths: Vec<(ItemType, Vec<Symbol>, Option<Vec<Symbol>>)>,
paths: Vec<(ItemType, Vec<Symbol>, Option<Vec<Symbol>>, bool)>,
// The String is alias name and the vec is the list of the elements with this alias.
//
// To be noted: the `usize` elements are indexes to `items`.
@ -450,6 +481,7 @@ pub(crate) fn build_index<'tcx>(
name: Symbol,
path: Option<usize>,
exact_path: Option<usize>,
search_unbox: bool,
}
impl Serialize for Paths {
@ -467,6 +499,15 @@ pub(crate) fn build_index<'tcx>(
assert!(self.path.is_some());
seq.serialize_element(path)?;
}
if self.search_unbox {
if self.path.is_none() {
seq.serialize_element(&None::<u8>)?;
}
if self.exact_path.is_none() {
seq.serialize_element(&None::<u8>)?;
}
seq.serialize_element(&1)?;
}
seq.end()
}
}
@ -489,9 +530,15 @@ pub(crate) fn build_index<'tcx>(
mod_paths.insert(&item.path, index);
}
let mut paths = Vec::with_capacity(self.paths.len());
for (ty, path, exact) in &self.paths {
for &(ty, ref path, ref exact, search_unbox) in &self.paths {
if path.len() < 2 {
paths.push(Paths { ty: *ty, name: path[0], path: None, exact_path: None });
paths.push(Paths {
ty,
name: path[0],
path: None,
exact_path: None,
search_unbox,
});
continue;
}
let full_path = join_with_double_colon(&path[..path.len() - 1]);
@ -517,10 +564,11 @@ pub(crate) fn build_index<'tcx>(
});
if let Some(index) = mod_paths.get(&full_path) {
paths.push(Paths {
ty: *ty,
ty,
name: *path.last().unwrap(),
path: Some(*index),
exact_path,
search_unbox,
});
continue;
}
@ -532,10 +580,11 @@ pub(crate) fn build_index<'tcx>(
match extra_paths.entry(full_path.clone()) {
Entry::Occupied(entry) => {
paths.push(Paths {
ty: *ty,
ty,
name: *path.last().unwrap(),
path: Some(*entry.get()),
exact_path,
search_unbox,
});
}
Entry::Vacant(entry) => {
@ -544,10 +593,11 @@ pub(crate) fn build_index<'tcx>(
revert_extra_paths.insert(index, full_path);
}
paths.push(Paths {
ty: *ty,
ty,
name: *path.last().unwrap(),
path: Some(index),
exact_path,
search_unbox,
});
}
}

View File

@ -1327,6 +1327,7 @@ class DocSearch {
exactPath: null,
generics,
bindings,
unboxFlag: true,
};
} else if (pathIndex === 0) {
// `0` is used as a sentinel because it's fewer bytes than `null`
@ -1338,6 +1339,7 @@ class DocSearch {
exactPath: null,
generics,
bindings,
unboxFlag: true,
};
} else {
const item = lowercasePaths[pathIndex - 1];
@ -1353,6 +1355,7 @@ class DocSearch {
exactPath: item.exactPath,
generics,
bindings,
unboxFlag: item.unboxFlag,
};
}
const cr = this.TYPES_POOL.get(result.id);
@ -1390,6 +1393,7 @@ class DocSearch {
if (cr.ty === result.ty && cr.path === result.path
&& cr.bindings === result.bindings && cr.generics === result.generics
&& cr.ty === result.ty && cr.name === result.name
&& cr.unboxFlag === result.unboxFlag
) {
return cr;
}
@ -1681,14 +1685,17 @@ class DocSearch {
const ty = elem[0];
const name = elem[1];
let path = null;
if (elem.length > 2) {
if (elem.length > 2 && elem[2] !== null) {
path = itemPaths.has(elem[2]) ? itemPaths.get(elem[2]) : lastPath;
lastPath = path;
}
const exactPath = elem.length > 3 ? itemPaths.get(elem[3]) : path;
const exactPath = elem.length > 3 && elem[3] !== null ?
itemPaths.get(elem[3]) :
path;
const unboxFlag = elem.length > 4 && !!elem[4];
lowercasePaths.push({ ty, name: name.toLowerCase(), path, exactPath });
paths[i] = { ty, name, path, exactPath };
lowercasePaths.push({ ty, name: name.toLowerCase(), path, exactPath, unboxFlag });
paths[i] = { ty, name, path, exactPath, unboxFlag };
}
// convert `item*` into an object form, and construct word indices.
@ -2376,17 +2383,20 @@ class DocSearch {
*/
const writeFn = (fnType, result) => {
if (fnType.id < 0) {
const queryId = mgens && mgens.has(fnType.id) ? mgens.get(fnType.id) : null;
if (fnParamNames[-1 - fnType.id] === "") {
for (const nested of fnType.generics) {
writeFn(nested, result);
}
return;
} else if (queryId < 0) {
mappedNames.set(
fnParamNames[-1 - fnType.id],
queryParamNames[-1 - queryId],
);
} else if (mgens) {
for (const [queryId, fnId] of mgens) {
if (fnId === fnType.id) {
mappedNames.set(
queryParamNames[-1 - queryId],
fnParamNames[-1 - fnType.id],
);
}
}
}
pushText({
name: fnParamNames[-1 - fnType.id],
@ -2436,15 +2446,15 @@ class DocSearch {
}
pushText({ name, highlighted: false }, result);
pushText({
name: values.length > 1 ? "=(" : "=",
name: values.length !== 1 ? "=(" : "=",
highlighted: false,
}, result);
onEachBtwn(
values,
values || [],
value => writeFn(value, result),
() => pushText({ name: " + ", highlighted: false }, result),
);
if (values.length > 1) {
if (values.length !== 1) {
pushText({ name: ")", highlighted: false }, result);
}
},
@ -2616,8 +2626,8 @@ class DocSearch {
* @param {Array<QueryElement>} queryElems - The elements from the parsed query.
* @param {[FunctionType]} whereClause - Trait bounds for generic items.
* @param {Map<number,number>|null} mgensIn
* - Map functions generics to query generics (never modified).
* @param {null|Map<number,number> -> bool} solutionCb - Called for each `mgens` solution.
* - Map query generics to function generics (never modified).
* @param {Map<number,number> -> bool} solutionCb - Called for each `mgens` solution.
* @param {number} unboxingDepth
* - Limit checks that Ty matches Vec<Ty>,
* but not Vec<ParamEnvAnd<WithInfcx<ConstTy<Interner<Ty=Ty>>>>>
@ -2640,7 +2650,7 @@ class DocSearch {
*/
const mgens = mgensIn === null ? null : new Map(mgensIn);
if (queryElems.length === 0) {
return (!solutionCb || solutionCb(mgens)) ? fnTypesIn : null;
return solutionCb(mgens) ? fnTypesIn : null;
}
if (!fnTypesIn || fnTypesIn.length === 0) {
return null;
@ -2657,12 +2667,12 @@ class DocSearch {
continue;
}
if (fnType.id < 0 && queryElem.id < 0) {
if (mgens && mgens.has(fnType.id) &&
mgens.get(fnType.id) !== queryElem.id) {
if (mgens && mgens.has(queryElem.id) &&
mgens.get(queryElem.id) !== fnType.id) {
continue;
}
const mgensScratch = new Map(mgens);
mgensScratch.set(fnType.id, queryElem.id);
mgensScratch.set(queryElem.id, fnType.id);
if (!solutionCb || solutionCb(mgensScratch)) {
const highlighted = [...fnTypesIn];
highlighted[i] = Object.assign({
@ -2672,13 +2682,13 @@ class DocSearch {
});
return highlighted;
}
} else if (!solutionCb || solutionCb(mgens ? new Map(mgens) : null)) {
} else if (solutionCb(mgens ? new Map(mgens) : null)) {
// unifyFunctionTypeIsMatchCandidate already checks that ids match
const highlighted = [...fnTypesIn];
highlighted[i] = Object.assign({
highlighted: true,
}, fnType, {
generics: unifyFunctionTypes(
generics: unifyGenericTypes(
fnType.generics,
queryElem.generics,
whereClause,
@ -2701,17 +2711,11 @@ class DocSearch {
continue;
}
if (fnType.id < 0) {
if (mgens && mgens.has(fnType.id) &&
mgens.get(fnType.id) !== 0) {
continue;
}
const mgensScratch = new Map(mgens);
mgensScratch.set(fnType.id, 0);
const highlightedGenerics = unifyFunctionTypes(
whereClause[(-fnType.id) - 1],
queryElems,
whereClause,
mgensScratch,
mgens,
solutionCb,
unboxingDepth + 1,
);
@ -2782,11 +2786,11 @@ class DocSearch {
let mgensScratch;
if (fnType.id < 0) {
mgensScratch = new Map(mgens);
if (mgensScratch.has(fnType.id)
&& mgensScratch.get(fnType.id) !== queryElem.id) {
if (mgensScratch.has(queryElem.id)
&& mgensScratch.get(queryElem.id) !== fnType.id) {
continue;
}
mgensScratch.set(fnType.id, queryElem.id);
mgensScratch.set(queryElem.id, fnType.id);
} else {
mgensScratch = mgens;
}
@ -2809,7 +2813,7 @@ class DocSearch {
mgensScratch => {
if (fnType.generics.length === 0 && queryElem.generics.length === 0
&& fnType.bindings.size === 0 && queryElem.bindings.size === 0) {
return !solutionCb || solutionCb(mgensScratch);
return solutionCb(mgensScratch);
}
const solution = unifyFunctionTypeCheckBindings(
fnType,
@ -2823,7 +2827,7 @@ class DocSearch {
}
const simplifiedGenerics = solution.simplifiedGenerics;
for (const simplifiedMgens of solution.mgens) {
unifiedGenerics = unifyFunctionTypes(
unifiedGenerics = unifyGenericTypes(
simplifiedGenerics,
queryElem.generics,
whereClause,
@ -2831,7 +2835,7 @@ class DocSearch {
solutionCb,
unboxingDepth,
);
if (unifiedGenerics) {
if (unifiedGenerics !== null) {
unifiedGenericsMgens = simplifiedMgens;
return true;
}
@ -2875,16 +2879,6 @@ class DocSearch {
)) {
continue;
}
let mgensScratch;
if (fnType.id < 0) {
mgensScratch = new Map(mgens);
if (mgensScratch.has(fnType.id) && mgensScratch.get(fnType.id) !== 0) {
continue;
}
mgensScratch.set(fnType.id, 0);
} else {
mgensScratch = mgens;
}
const generics = fnType.id < 0 ?
whereClause[(-fnType.id) - 1] :
fnType.generics;
@ -2895,7 +2889,7 @@ class DocSearch {
fnTypes.toSpliced(i, 1, ...bindings, ...generics),
queryElems,
whereClause,
mgensScratch,
mgens,
solutionCb,
unboxingDepth + 1,
);
@ -2919,6 +2913,199 @@ class DocSearch {
}
return null;
}
/**
* This function compares two lists of generics.
*
* This function behaves very similarly to `unifyFunctionTypes`, except that it
* doesn't skip or reorder anything. This is intended to match the behavior of
* the ordinary Rust type system, so that `Vec<Allocator>` only matches an actual
* `Vec` of `Allocators` and not the implicit `Allocator` parameter that every
* `Vec` has.
*
* @param {Array<FunctionType>} fnTypesIn - The objects to check.
* @param {Array<QueryElement>} queryElems - The elements from the parsed query.
* @param {[FunctionType]} whereClause - Trait bounds for generic items.
* @param {Map<number,number>|null} mgensIn
* - Map functions generics to query generics (never modified).
* @param {Map<number,number> -> bool} solutionCb - Called for each `mgens` solution.
* @param {number} unboxingDepth
* - Limit checks that Ty matches Vec<Ty>,
* but not Vec<ParamEnvAnd<WithInfcx<ConstTy<Interner<Ty=Ty>>>>>
*
* @return {[FunctionType]|null} - Returns highlighed results if a match, null otherwise.
*/
function unifyGenericTypes(
fnTypesIn,
queryElems,
whereClause,
mgensIn,
solutionCb,
unboxingDepth,
) {
if (unboxingDepth >= UNBOXING_LIMIT) {
return null;
}
/**
* @type Map<integer, integer>|null
*/
const mgens = mgensIn === null ? null : new Map(mgensIn);
if (queryElems.length === 0) {
return solutionCb(mgens) ? fnTypesIn : null;
}
if (!fnTypesIn || fnTypesIn.length === 0) {
return null;
}
const fnType = fnTypesIn[0];
const queryElem = queryElems[0];
if (unifyFunctionTypeIsMatchCandidate(fnType, queryElem, mgens)) {
if (fnType.id < 0 && queryElem.id < 0) {
if (!mgens || !mgens.has(queryElem.id) ||
mgens.get(queryElem.id) === fnType.id
) {
const mgensScratch = new Map(mgens);
mgensScratch.set(queryElem.id, fnType.id);
const fnTypesRemaining = unifyGenericTypes(
fnTypesIn.slice(1),
queryElems.slice(1),
whereClause,
mgensScratch,
solutionCb,
unboxingDepth,
);
if (fnTypesRemaining) {
const highlighted = [fnType, ...fnTypesRemaining];
highlighted[0] = Object.assign({
highlighted: true,
}, fnType, {
generics: whereClause[-1 - fnType.id],
});
return highlighted;
}
}
} else {
let unifiedGenerics;
const fnTypesRemaining = unifyGenericTypes(
fnTypesIn.slice(1),
queryElems.slice(1),
whereClause,
mgens,
mgensScratch => {
const solution = unifyFunctionTypeCheckBindings(
fnType,
queryElem,
whereClause,
mgensScratch,
unboxingDepth,
);
if (!solution) {
return false;
}
const simplifiedGenerics = solution.simplifiedGenerics;
for (const simplifiedMgens of solution.mgens) {
unifiedGenerics = unifyGenericTypes(
simplifiedGenerics,
queryElem.generics,
whereClause,
simplifiedMgens,
solutionCb,
unboxingDepth,
);
if (unifiedGenerics !== null) {
return true;
}
}
},
unboxingDepth,
);
if (fnTypesRemaining) {
const highlighted = [fnType, ...fnTypesRemaining];
highlighted[0] = Object.assign({
highlighted: true,
}, fnType, {
generics: unifiedGenerics || fnType.generics,
});
return highlighted;
}
}
}
if (unifyFunctionTypeIsUnboxCandidate(
fnType,
queryElem,
whereClause,
mgens,
unboxingDepth + 1,
)) {
let highlightedRemaining;
if (fnType.id < 0) {
// Where clause corresponds to `F: A + B`
// ^^^^^
// The order of the constraints doesn't matter, so
// use order-agnostic matching for it.
const highlightedGenerics = unifyFunctionTypes(
whereClause[(-fnType.id) - 1],
[queryElem],
whereClause,
mgens,
mgensScratch => {
const hl = unifyGenericTypes(
fnTypesIn.slice(1),
queryElems.slice(1),
whereClause,
mgensScratch,
solutionCb,
unboxingDepth,
);
if (hl) {
highlightedRemaining = hl;
}
return hl;
},
unboxingDepth + 1,
);
if (highlightedGenerics) {
return [Object.assign({
highlighted: true,
}, fnType, {
generics: highlightedGenerics,
}), ...highlightedRemaining];
}
} else {
const highlightedGenerics = unifyGenericTypes(
[
...Array.from(fnType.bindings.values()).flat(),
...fnType.generics,
],
[queryElem],
whereClause,
mgens,
mgensScratch => {
const hl = unifyGenericTypes(
fnTypesIn.slice(1),
queryElems.slice(1),
whereClause,
mgensScratch,
solutionCb,
unboxingDepth,
);
if (hl) {
highlightedRemaining = hl;
}
return hl;
},
unboxingDepth + 1,
);
if (highlightedGenerics) {
return [Object.assign({}, fnType, {
generics: highlightedGenerics,
bindings: new Map([...fnType.bindings.entries()].map(([k, v]) => {
return [k, highlightedGenerics.splice(0, v.length)];
})),
}), ...highlightedRemaining];
}
}
}
return null;
}
/**
* Check if this function is a match candidate.
*
@ -2930,7 +3117,7 @@ class DocSearch {
*
* @param {FunctionType} fnType
* @param {QueryElement} queryElem
* @param {Map<number,number>|null} mgensIn - Map functions generics to query generics.
* @param {Map<number,number>|null} mgensIn - Map query generics to function generics.
* @returns {boolean}
*/
const unifyFunctionTypeIsMatchCandidate = (fnType, queryElem, mgensIn) => {
@ -2940,22 +3127,13 @@ class DocSearch {
}
// fnType.id < 0 means generic
// queryElem.id < 0 does too
// mgensIn[fnType.id] = queryElem.id
// or, if mgensIn[fnType.id] = 0, then we've matched this generic with a bare trait
// and should make that same decision everywhere it appears
// mgensIn[queryElem.id] = fnType.id
if (fnType.id < 0 && queryElem.id < 0) {
if (mgensIn) {
if (mgensIn.has(fnType.id) && mgensIn.get(fnType.id) !== queryElem.id) {
return false;
}
for (const [fid, qid] of mgensIn.entries()) {
if (fnType.id !== fid && queryElem.id === qid) {
return false;
}
if (fnType.id === fid && queryElem.id !== qid) {
return false;
}
}
if (
mgensIn && mgensIn.has(queryElem.id) &&
mgensIn.get(queryElem.id) !== fnType.id
) {
return false;
}
return true;
} else {
@ -3032,7 +3210,7 @@ class DocSearch {
* @param {FunctionType} fnType
* @param {QueryElement} queryElem
* @param {[FunctionType]} whereClause - Trait bounds for generic items.
* @param {Map<number,number>} mgensIn - Map functions generics to query generics.
* @param {Map<number,number>} mgensIn - Map query generics to function generics.
* Never modified.
* @param {number} unboxingDepth
* @returns {false|{mgens: [Map<number,number>], simplifiedGenerics: [FunctionType]}}
@ -3100,7 +3278,7 @@ class DocSearch {
* @param {FunctionType} fnType
* @param {QueryElement} queryElem
* @param {[FunctionType]} whereClause - Trait bounds for generic items.
* @param {Map<number,number>|null} mgens - Map functions generics to query generics.
* @param {Map<number,number>|null} mgens - Map query generics to function generics.
* @param {number} unboxingDepth
* @returns {boolean}
*/
@ -3114,19 +3292,10 @@ class DocSearch {
if (unboxingDepth >= UNBOXING_LIMIT) {
return false;
}
if (fnType.id < 0 && queryElem.id >= 0) {
if (fnType.id < 0) {
if (!whereClause) {
return false;
}
// mgens[fnType.id] === 0 indicates that we committed to unboxing this generic
// mgens[fnType.id] === null indicates that we haven't decided yet
if (mgens && mgens.has(fnType.id) && mgens.get(fnType.id) !== 0) {
return false;
}
// Where clauses can represent cyclical data.
// `null` prevents it from trying to unbox in an infinite loop
const mgensTmp = new Map(mgens);
mgensTmp.set(fnType.id, null);
// This is only a potential unbox if the search query appears in the where clause
// for example, searching `Read -> usize` should find
// `fn read_all<R: Read>(R) -> Result<usize>`
@ -3135,10 +3304,11 @@ class DocSearch {
whereClause[(-fnType.id) - 1],
queryElem,
whereClause,
mgensTmp,
mgens,
unboxingDepth,
);
} else if (fnType.generics.length > 0 || fnType.bindings.size > 0) {
} else if (fnType.unboxFlag &&
(fnType.generics.length > 0 || fnType.bindings.size > 0)) {
const simplifiedGenerics = [
...fnType.generics,
...Array.from(fnType.bindings.values()).flat(),
@ -3182,7 +3352,7 @@ class DocSearch {
* @param {Row} row
* @param {QueryElement} elem - The element from the parsed query.
* @param {[FunctionType]} whereClause - Trait bounds for generic items.
* @param {Map<number,number>|null} mgens - Map functions generics to query generics.
* @param {Map<number,number>|null} mgens - Map query generics to function generics.
*
* @return {boolean} - Returns true if the type matches, false otherwise.
*/
@ -3200,10 +3370,34 @@ class DocSearch {
) {
return row.id === elem.id && typePassesFilter(elem.typeFilter, row.ty);
} else {
return unifyFunctionTypes([row], [elem], whereClause, mgens, null, unboxingDepth);
return unifyFunctionTypes(
[row],
[elem],
whereClause,
mgens,
() => true,
unboxingDepth,
);
}
};
/**
* Check a query solution for conflicting generics.
*/
const checkTypeMgensForConflict = mgens => {
if (!mgens) {
return true;
}
const fnTypes = new Set();
for (const [_qid, fid] of mgens) {
if (fnTypes.has(fid)) {
return false;
}
fnTypes.add(fid);
}
return true;
};
/**
* Compute an "edit distance" that ignores missing path elements.
* @param {string[]} contains search query path
@ -3523,7 +3717,7 @@ class DocSearch {
parsedQuery.returned,
row.type.where_clause,
mgens,
null,
checkTypeMgensForConflict,
0, // unboxing depth
);
},
@ -3973,7 +4167,7 @@ ${item.displayPath}<span class="${type}">${name}</span>\
displayType.appendChild(line);
addWhereLineFn = () => {};
};
for (const [name, qname] of mappedNames) {
for (const [qname, name] of mappedNames) {
// don't care unless the generic name is different
if (name === qname) {
continue;

View File

@ -11,8 +11,8 @@ assert-count: ("#search-tabs button", 1)
assert-count: (".search-results > a", 1)
assert: "//div[@class='type-signature']/strong[text()='Iterator']"
assert: "//div[@class='type-signature']/strong[text()='(']"
assert: "//div[@class='type-signature']/strong[text()=')']"
assert: "//div[@class='type-signature']/strong[text()='(B']"
assert: "//div[@class='type-signature']/strong[text()='B)']"
assert: "//div[@class='type-signature']/div[@class='where']/strong[text()='FnMut']"
assert: "//div[@class='type-signature']/div[@class='where']/strong[text()='Iterator::Item']"

View File

@ -2,12 +2,15 @@
const EXPECTED = [
{
'query': 'bufread -> result<u8>',
'query': 'bufread -> result<[u8]>',
'others': [
{ 'path': 'std::boxed::Box', 'name': 'fill_buf' },
],
},
{
'query': 'split<bufread> -> option<result<vec<u8>>>',
'others': [
{ 'path': 'std::io::Split', 'name': 'next' },
{ 'path': 'std::boxed::Box', 'name': 'fill_buf' },
{ 'path': 'std::io::Chain', 'name': 'fill_buf' },
{ 'path': 'std::io::Take', 'name': 'fill_buf' },
],
},
];

View File

@ -80,11 +80,6 @@ const EXPECTED = [
'name': 'and',
'displayType': '`Option`<`T`>, `Option`<`U`> -> `Option`<`U`>',
},
{
'path': 'std::option::Option',
'name': 'zip',
'displayType': '`Option`<`T`>, `Option`<`U`> -> `Option`<(T, `U`)>',
},
],
},
{
@ -103,12 +98,12 @@ const EXPECTED = [
],
},
{
'query': 'option<t>, option<u> -> option<t, u>',
'query': 'option<t>, option<u> -> option<(t, u)>',
'others': [
{
'path': 'std::option::Option',
'name': 'zip',
'displayType': '`Option`<`T`>, `Option`<`U`> -> `Option`<(`T`, `U`)>',
'displayType': '`Option`<`T`>, `Option`<`U`> -> `Option`<`(T`, `U)`>',
},
],
},
@ -174,37 +169,23 @@ const EXPECTED = [
'path': 'std::option::Option',
'name': 'map',
'displayType': '`Option`<`T`>, F -> `Option`<U>',
'displayMappedNames': `T = t, U = u`,
'displayMappedNames': `t = T, u = U`,
'displayWhereClause': "F: `FnOnce` (T) -> `U`",
},
{
'path': 'std::option::Option',
'name': 'and_then',
'displayType': '`Option`<`T`>, F -> `Option`<U>',
'displayMappedNames': `T = t, U = u`,
'displayMappedNames': `t = T, u = U`,
'displayWhereClause': "F: `FnOnce` (T) -> Option<`U`>",
},
{
'path': 'std::option::Option',
'name': 'zip_with',
'displayType': 'Option<T>, `Option`<`U`>, F -> `Option`<R>',
'displayMappedNames': `U = t, R = u`,
'displayMappedNames': `t = U, u = R`,
'displayWhereClause': "F: `FnOnce` (T, U) -> `R`",
},
{
'path': 'std::task::Poll',
'name': 'map_ok',
'displayType': 'Poll<`Option`<Result<`T`, E>>>, F -> Poll<`Option`<Result<U, E>>>',
'displayMappedNames': `T = t, U = u`,
'displayWhereClause': "F: `FnOnce` (T) -> `U`",
},
{
'path': 'std::task::Poll',
'name': 'map_err',
'displayType': 'Poll<`Option`<Result<`T`, E>>>, F -> Poll<`Option`<Result<T, U>>>',
'displayMappedNames': `T = t, U = u`,
'displayWhereClause': "F: `FnOnce` (E) -> `U`",
},
],
},
{
@ -214,7 +195,7 @@ const EXPECTED = [
'path': 'std::option::Option',
'name': 'and_then',
'displayType': '`Option`<`T`>, F -> `Option`<U>',
'displayMappedNames': `T = t, U = u`,
'displayMappedNames': `t = T, u = U`,
'displayWhereClause': "F: `FnOnce` (T) -> `Option`<`U`>",
},
],

View File

@ -19,4 +19,16 @@ const EXPECTED = [
{ 'path': 'std::vec::IntoIter', 'name': 'next_chunk' },
],
},
{
'query': 'vec<T, Allocator> -> Box<[T]>',
'others': [
{
'path': 'std::boxed::Box',
'name': 'from',
'displayType': '`Vec`<`T`, `A`> -> `Box`<`[T]`, A>',
'displayMappedNames': `T = T`,
'displayWhereClause': 'A: `Allocator`',
},
],
},
];

View File

@ -6,7 +6,6 @@ const EXPECTED = [
'correction': null,
'others': [
{ 'path': 'assoc_type_backtrack::MyTrait', 'name': 'fold' },
{ 'path': 'assoc_type_backtrack::Cloned', 'name': 'fold' },
],
},
{
@ -14,6 +13,19 @@ const EXPECTED = [
'correction': null,
'others': [
{ 'path': 'assoc_type_backtrack::MyTrait', 'name': 'fold' },
],
},
{
'query': 'cloned<mytrait>, mytrait2 -> T',
'correction': null,
'others': [
{ 'path': 'assoc_type_backtrack::Cloned', 'name': 'fold' },
],
},
{
'query': 'cloned<mytrait<U>>, mytrait2 -> T',
'correction': null,
'others': [
{ 'path': 'assoc_type_backtrack::Cloned', 'name': 'fold' },
],
},
@ -22,7 +34,6 @@ const EXPECTED = [
'correction': null,
'others': [
{ 'path': 'assoc_type_backtrack::MyTrait', 'name': 'fold' },
{ 'path': 'assoc_type_backtrack::Cloned', 'name': 'fold' },
],
},
{
@ -50,14 +61,14 @@ const EXPECTED = [
],
},
{
'query': 'mytrait<U> -> Option<T>',
'query': 'cloned<mytrait<U>> -> Option<T>',
'correction': null,
'others': [
{ 'path': 'assoc_type_backtrack::Cloned', 'name': 'next' },
],
},
{
'query': 'mytrait<Item=U> -> Option<T>',
'query': 'cloned<mytrait<Item=U>> -> Option<T>',
'correction': null,
'others': [
{ 'path': 'assoc_type_backtrack::Cloned', 'name': 'next' },
@ -89,19 +100,21 @@ const EXPECTED = [
],
},
{
'query': 'myintofuture<myfuture<t>> -> myfuture<t>',
'query': 'myintofuture<t, myfuture<t>> -> myfuture<t>',
'correction': null,
'others': [
{ 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future' },
{ 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' },
],
},
// Invalid unboxing of the one-argument case.
// If you unbox one of the myfutures, you need to unbox both of them.
// Unboxings of the one-argument case.
{
'query': 'myintofuture<fut=t> -> myfuture<t>',
'correction': null,
'others': [],
'others': [
{ 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future' },
{ 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' },
],
},
// Unboxings of the two-argument case.
{
@ -119,7 +132,7 @@ const EXPECTED = [
],
},
{
'query': 'myintofuture<myfuture>, myintofuture<myfuture> -> myfuture',
'query': 'myintofuture<t, myfuture>, myintofuture<t, myfuture> -> myfuture',
'correction': null,
'others': [
{ 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' },
@ -132,24 +145,29 @@ const EXPECTED = [
{ 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' },
],
},
// Invalid unboxings of the two-argument case.
// If you unbox one of the myfutures, you need to unbox all of them.
// If you unbox one of the myfutures, you don't need to unbox all of them.
{
'query': 'myintofuture<fut=t>, myintofuture<fut=myfuture<t>> -> myfuture<t>',
'correction': null,
'others': [],
'others': [
{ 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' },
],
},
{
'query': 'myintofuture<fut=myfuture<t>>, myintofuture<fut=t> -> myfuture<t>',
'correction': null,
'others': [],
'others': [
{ 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' },
],
},
{
'query': 'myintofuture<fut=myfuture<t>>, myintofuture<fut=myfuture<t>> -> t',
'correction': null,
'others': [],
'others': [
{ 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' },
],
},
// different generics don't match up either
// different generics must match up
{
'query': 'myintofuture<fut=myfuture<u>>, myintofuture<fut=myfuture<t>> -> myfuture<t>',
'correction': null,

View File

@ -1,3 +1,5 @@
#![feature(rustdoc_internals)]
pub trait MyTrait2<X> {
type Output;
}
@ -31,10 +33,12 @@ where
}
}
#[doc(search_unbox)]
pub trait MyFuture {
type Output;
}
#[doc(search_unbox)]
pub trait MyIntoFuture {
type Output;
type Fut: MyFuture<Output = Self::Output>;

View File

@ -13,7 +13,7 @@ const EXPECTED = [
'path': 'assoc_type_unbound::MyIter',
'name': 'next',
'displayType': '&mut `MyIter` -> `Option`<`MyIter::Item`>',
'displayMappedNames': 'MyIter::Item = T',
'displayMappedNames': 'T = MyIter::Item',
'displayWhereClause': '',
},
],
@ -26,7 +26,7 @@ const EXPECTED = [
'path': 'assoc_type_unbound::MyIter',
'name': 'next',
'displayType': '&mut `MyIter` -> `Option`<`MyIter::Item`>',
'displayMappedNames': 'MyIter::Item = T',
'displayMappedNames': 'T = MyIter::Item',
'displayWhereClause': '',
},
],

View File

@ -1,12 +1,22 @@
pub fn my_fn<X: Iterator<Item = Something>>(_x: X) -> u32 {
#![feature(rustdoc_internals)]
pub fn my_fn<X: other::Iterator<Item = Something>>(_x: X) -> u32 {
3
}
pub struct Something;
pub mod my {
#[doc(search_unbox)]
pub trait Iterator<T> {}
pub fn other_fn<X: Iterator<crate::Something>>(_: X) -> u32 {
3
}
}
pub mod other {
#[doc(search_unbox)]
pub trait Iterator {
type Item;
}
}

View File

@ -14,7 +14,7 @@ const EXPECTED = [
],
},
{
'query': 'Aaaaaaa -> usize',
'query': 'Aaaaaaa -> Result<usize>',
'others': [
{ 'path': 'generics_impl::Aaaaaaa', 'name': 'read' },
],
@ -23,6 +23,11 @@ const EXPECTED = [
'query': 'Read -> u64',
'others': [
{ 'path': 'generics_impl::Ddddddd', 'name': 'eeeeeee' },
],
},
{
'query': 'Ddddddd<Read> -> u64',
'others': [
{ 'path': 'generics_impl::Ddddddd', 'name': 'ggggggg' },
],
},
@ -30,7 +35,6 @@ const EXPECTED = [
'query': 'trait:Read -> u64',
'others': [
{ 'path': 'generics_impl::Ddddddd', 'name': 'eeeeeee' },
{ 'path': 'generics_impl::Ddddddd', 'name': 'ggggggg' },
],
},
{

View File

@ -1,4 +1,4 @@
use std::io::{Read, Result as IoResult};
use std::io::{self, Read};
pub struct Aaaaaaa;
@ -12,7 +12,7 @@ impl Aaaaaaa {
}
impl Read for Aaaaaaa {
fn read(&mut self, out: &mut [u8]) -> IoResult<usize> {
fn read(&mut self, out: &mut [u8]) -> io::Result<usize> {
Ok(out.len())
}
}

View File

@ -0,0 +1,68 @@
// ignore-order
// exact-check
// Make sure that results are order-agnostic, even when there's search items that only differ
// by generics.
const EXPECTED = [
{
'query': 'Wrap',
'in_args': [
{ 'path': 'generics_match_ambiguity', 'name': 'bar' },
{ 'path': 'generics_match_ambiguity', 'name': 'foo' },
],
},
{
'query': 'Wrap<i32>',
'in_args': [
{ 'path': 'generics_match_ambiguity', 'name': 'bar' },
{ 'path': 'generics_match_ambiguity', 'name': 'foo' },
],
},
{
'query': 'Wrap<i32>, Wrap<i32, u32>',
'others': [
{ 'path': 'generics_match_ambiguity', 'name': 'bar' },
{ 'path': 'generics_match_ambiguity', 'name': 'foo' },
],
},
{
'query': 'Wrap<i32, u32>, Wrap<i32>',
'others': [
{ 'path': 'generics_match_ambiguity', 'name': 'bar' },
{ 'path': 'generics_match_ambiguity', 'name': 'foo' },
],
},
{
'query': 'W3<i32>, W3<i32, u32>',
'others': [
{ 'path': 'generics_match_ambiguity', 'name': 'baaa' },
{ 'path': 'generics_match_ambiguity', 'name': 'baab' },
],
},
{
'query': 'W3<i32, u32>, W3<i32>',
'others': [
{ 'path': 'generics_match_ambiguity', 'name': 'baaa' },
{ 'path': 'generics_match_ambiguity', 'name': 'baab' },
],
},
{
// strict generics matching; W2<i32, u32> doesn't match W2<W3<i32, u32>>,
// even though W2<i32> works just fine (ignoring the W3)
'query': 'W2<i32>, W2<i32, u32>',
'others': [],
},
{
'query': 'W2<i32, u32>, W2<i32>',
'others': [],
},
{
'query': 'W2<i32>, W3<i32, u32>',
'others': [],
},
{
'query': 'W2<i32>, W2<i32>',
'others': [],
},
];

View File

@ -0,0 +1,18 @@
#![crate_name = "generics_match_ambiguity"]
pub struct Wrap<T, U = ()>(pub T, pub U);
pub fn foo(a: Wrap<i32>, b: Wrap<i32, u32>) {}
pub fn bar(a: Wrap<i32, u32>, b: Wrap<i32>) {}
pub struct W2<T>(pub T);
pub struct W3<T, U = ()>(pub T, pub U);
pub fn baaa(a: W3<i32>, b: W3<i32, u32>) {}
pub fn baab(a: W3<i32, u32>, b: W3<i32>) {}
pub fn baac(a: W2<W3<i32>>, b: W3<i32, u32>) {}
pub fn baad(a: W2<W3<i32, u32>>, b: W3<i32>) {}
pub fn baae(a: W3<i32>, b: W2<W3<i32, u32>>) {}
pub fn baaf(a: W3<i32, u32>, b: W2<W3<i32>>) {}
pub fn baag(a: W2<W3<i32>>, b: W2<W3<i32, u32>>) {}
pub fn baah(a: W2<W3<i32, u32>>, b: W2<W3<i32>>) {}

View File

@ -60,18 +60,14 @@ const EXPECTED = [
],
},
{
// strict generics matching; W2<i32, u32> doesn't match W2<W3<i32, u32>>,
// even though W2<i32> works just fine (ignoring the W3)
'query': 'W2<i32>, W2<i32, u32>',
'others': [
{ 'path': 'generics_match_ambiguity', 'name': 'baag' },
{ 'path': 'generics_match_ambiguity', 'name': 'baah' },
],
'others': [],
},
{
'query': 'W2<i32, u32>, W2<i32>',
'others': [
{ 'path': 'generics_match_ambiguity', 'name': 'baag' },
{ 'path': 'generics_match_ambiguity', 'name': 'baah' },
],
'others': [],
},
{
'query': 'W2<i32>, W3<i32, u32>',

View File

@ -1,9 +1,14 @@
#![feature(rustdoc_internals)]
#[doc(search_unbox)]
pub struct Wrap<T, U = ()>(pub T, pub U);
pub fn foo(a: Wrap<i32>, b: Wrap<i32, u32>) {}
pub fn bar(a: Wrap<i32, u32>, b: Wrap<i32>) {}
#[doc(search_unbox)]
pub struct W2<T>(pub T);
#[doc(search_unbox)]
pub struct W3<T, U = ()>(pub T, pub U);
pub fn baaa(a: W3<i32>, b: W3<i32, u32>) {}
@ -14,4 +19,3 @@ pub fn baae(a: W3<i32>, b: W2<W3<i32, u32>>) {}
pub fn baaf(a: W3<i32, u32>, b: W2<W3<i32>>) {}
pub fn baag(a: W2<W3<i32>>, b: W2<W3<i32, u32>>) {}
pub fn baah(a: W2<W3<i32, u32>>, b: W2<W3<i32>>) {}
//

View File

@ -18,9 +18,8 @@ const EXPECTED = [
],
},
{
// can't put generics out of order
'query': '-> Out<Second, First>',
'others': [
{ 'path': 'generics_nested', 'name': 'bet' },
],
'others': [],
},
];

View File

@ -11,20 +11,17 @@ const EXPECTED = [
'query': 'Inside<T> -> Out3<T>',
'others': [
{ 'path': 'generics_unbox', 'name': 'beta' },
{ 'path': 'generics_unbox', 'name': 'gamma' },
],
},
{
'query': 'Inside<T> -> Out4<T>',
'others': [
{ 'path': 'generics_unbox', 'name': 'beta' },
{ 'path': 'generics_unbox', 'name': 'gamma' },
],
},
{
'query': 'Inside<T> -> Out3<U, T>',
'others': [
{ 'path': 'generics_unbox', 'name': 'beta' },
{ 'path': 'generics_unbox', 'name': 'gamma' },
],
},
@ -32,7 +29,6 @@ const EXPECTED = [
'query': 'Inside<T> -> Out4<U, T>',
'others': [
{ 'path': 'generics_unbox', 'name': 'beta' },
{ 'path': 'generics_unbox', 'name': 'gamma' },
],
},
];

View File

@ -1,26 +1,34 @@
#![feature(rustdoc_internals)]
#[doc(search_unbox)]
pub struct Out<A, B = ()> {
a: A,
b: B,
}
#[doc(search_unbox)]
pub struct Out1<A, const N: usize> {
a: [A; N],
}
#[doc(search_unbox)]
pub struct Out2<A, const N: usize> {
a: [A; N],
}
#[doc(search_unbox)]
pub struct Out3<A, B> {
a: A,
b: B,
}
#[doc(search_unbox)]
pub struct Out4<A, B> {
a: A,
b: B,
}
#[doc(search_unbox)]
pub struct Inside<T>(T);
pub fn alpha<const N: usize, T>(_: Inside<T>) -> Out<Out1<T, N>, Out2<T, N>> {

View File

@ -30,21 +30,13 @@ const EXPECTED = [
'others': [
{ 'path': 'generics', 'name': 'P' },
],
'returned': [
{ 'path': 'generics', 'name': 'alef' },
],
'in_args': [
{ 'path': 'generics', 'name': 'alpha' },
],
'returned': [],
'in_args': [],
},
{
'query': 'P',
'returned': [
{ 'path': 'generics', 'name': 'alef' },
],
'in_args': [
{ 'path': 'generics', 'name': 'alpha' },
],
'returned': [],
'in_args': [],
},
{
'query': '"ExtraCreditStructMulti"<ExtraCreditInnerMulti, ExtraCreditInnerMulti>',

View File

@ -9,19 +9,19 @@ const EXPECTED = [
// ML-style higher-order function notation
{
'query': 'bool, (u32 -> !) -> ()',
'query': 'bool, (first<u32> -> !) -> ()',
'others': [
{"path": "hof", "name": "fn_ptr"},
],
},
{
'query': 'u8, (u32 -> !) -> ()',
'query': 'u8, (second<u32> -> !) -> ()',
'others': [
{"path": "hof", "name": "fn_once"},
],
},
{
'query': 'i8, (u32 -> !) -> ()',
'query': 'i8, (third<u32> -> !) -> ()',
'others': [
{"path": "hof", "name": "fn_mut"},
],
@ -54,9 +54,6 @@ const EXPECTED = [
'query': '(u32 -> !) -> ()',
'others': [
{"path": "hof", "name": "fn_"},
{"path": "hof", "name": "fn_ptr"},
{"path": "hof", "name": "fn_mut"},
{"path": "hof", "name": "fn_once"},
],
},
{
@ -95,30 +92,30 @@ const EXPECTED = [
// Rust-style higher-order function notation
{
'query': 'bool, fn(u32) -> ! -> ()',
'query': 'bool, fn(first<u32>) -> ! -> ()',
'others': [
{"path": "hof", "name": "fn_ptr"},
],
},
{
'query': 'u8, fnonce(u32) -> ! -> ()',
'query': 'u8, fnonce(second<u32>) -> ! -> ()',
'others': [
{"path": "hof", "name": "fn_once"},
],
},
{
'query': 'u8, fn(u32) -> ! -> ()',
'query': 'u8, fn(second<u32>) -> ! -> ()',
// fnonce != fn
'others': [],
},
{
'query': 'i8, fnmut(u32) -> ! -> ()',
'query': 'i8, fnmut(third<u32>) -> ! -> ()',
'others': [
{"path": "hof", "name": "fn_mut"},
],
},
{
'query': 'i8, fn(u32) -> ! -> ()',
'query': 'i8, fn(third<u32>) -> ! -> ()',
// fnmut != fn
'others': [],
},
@ -152,7 +149,7 @@ const EXPECTED = [
],
},
{
'query': 'fn(u32) -> ! -> ()',
'query': 'fn() -> ! -> ()',
'others': [
// fn matches primitive:fn and trait:Fn
{"path": "hof", "name": "fn_"},
@ -160,14 +157,14 @@ const EXPECTED = [
],
},
{
'query': 'trait:fn(u32) -> ! -> ()',
'query': 'trait:fn() -> ! -> ()',
'others': [
// fn matches primitive:fn and trait:Fn
{"path": "hof", "name": "fn_"},
],
},
{
'query': 'primitive:fn(u32) -> ! -> ()',
'query': 'primitive:fn() -> ! -> ()',
'others': [
// fn matches primitive:fn and trait:Fn
{"path": "hof", "name": "fn_ptr"},

View File

@ -1,9 +1,15 @@
// https://github.com/rust-lang/rust/pull/122247
// exact-check
const EXPECTED = {
'query': 'canonicalvarinfo, intoiterator -> intoiterator',
'others': [
{ 'path': 'looks_like_rustc_interner::Interner', 'name': 'mk_canonical_var_infos' },
],
};
const EXPECTED = [
{
'query': 'canonicalvarinfo, intoiterator -> intoiterator',
'others': [],
},
{
'query': '[canonicalvarinfo], interner<tys=intoiterator> -> intoiterator',
'others': [
{ 'path': 'looks_like_rustc_interner::Interner', 'name': 'mk_canonical_var_infos' },
],
},
];

View File

@ -33,9 +33,8 @@ const EXPECTED = [
},
{
'query': '-> Result<i32, u32, bool>',
'others': [
{ 'path': 'nested_unboxed', 'name': 'something' },
],
// can't put nested generics out of order
'others': [],
},
{
'query': '-> Result<Object<i32>, bool>',
@ -45,9 +44,7 @@ const EXPECTED = [
},
{
'query': '-> Result<Object<u32>, bool>',
'others': [
{ 'path': 'nested_unboxed', 'name': 'something' },
],
'others': [],
},
{
'query': '-> Result<Object<i32>, u32, bool>',

View File

@ -1,3 +1,6 @@
#![feature(rustdoc_internals)]
#[doc(search_unbox)]
pub struct Object<T, U>(T, U);
pub fn something() -> Result<Object<i32, u32>, bool> {

View File

@ -79,9 +79,8 @@ const EXPECTED = [
},
{
'query': 'reference<ring>, reference<ring> -> ()',
'others': [
{ 'path': 'reference::Ring', 'name': 'wear' },
],
// can't leave out the `mut`, because can't reorder like that
'others': [],
},
{
'query': 'reference<mut, ring>, reference<ring> -> ()',
@ -102,9 +101,8 @@ const EXPECTED = [
},
{
'query': 'reference<middle>, reference<middle> -> ()',
'others': [
{ 'path': 'reference', 'name': 'show' },
],
// can't leave out the mut
'others': [],
},
{
'query': 'reference<mut, middle>, reference<mut, middle> -> ()',
@ -203,9 +201,8 @@ const EXPECTED = [
// middle with shorthand
{
'query': '&middle, &middle -> ()',
'others': [
{ 'path': 'reference', 'name': 'show' },
],
// can't leave out the mut
'others': [],
},
{
'query': '&mut middle, &mut middle -> ()',

View File

@ -57,7 +57,7 @@ const EXPECTED = [
'in_args': [],
},
{
'query': '(Q, ())',
'query': '(Q, R<()>)',
'returned': [
{ 'path': 'tuple_unit', 'name': 'nest' },
],
@ -71,7 +71,7 @@ const EXPECTED = [
'in_args': [],
},
{
'query': '(u32)',
'query': 'R<(u32)>',
'returned': [
{ 'path': 'tuple_unit', 'name': 'nest' },
],

View File

@ -7,4 +7,7 @@ trait Mine {}
#[doc(fake_variadic)] //~ ERROR: `#[doc(fake_variadic)]` is meant for internal use only
impl<T> Mine for (T,) {}
#[doc(search_unbox)] //~ ERROR: `#[doc(search_unbox)]` is meant for internal use only
struct Wrap<T> (T);
fn main() {}

View File

@ -18,6 +18,16 @@ LL | #[doc(fake_variadic)]
= help: add `#![feature(rustdoc_internals)]` to the crate attributes to enable
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
error: aborting due to 2 previous errors
error[E0658]: `#[doc(search_unbox)]` is meant for internal use only
--> $DIR/feature-gate-rustdoc_internals.rs:10:1
|
LL | #[doc(search_unbox)]
| ^^^^^^^^^^^^^^^^^^^^
|
= note: see issue #90418 <https://github.com/rust-lang/rust/issues/90418> for more information
= help: add `#![feature(rustdoc_internals)]` to the crate attributes to enable
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
error: aborting due to 3 previous errors
For more information about this error, try `rustc --explain E0658`.