Require module_id param to resolve to be non-empty

Previously, `resolve` would immediately check that `module_id` was
non-empty and give an error if not. This had two downsides:

- It introduced `Option`s everywhere, even if the calling function knew
it had a valid module, and
- It checked the module on each namespace, which is unnecessary: it only
needed to be checked once.

This makes the caller responsible for checking the module exists, making
the code a lot simpler.
This commit is contained in:
Joshua Nelson 2020-09-13 17:15:40 -04:00
parent 7dc0d335bc
commit 8a13fc494d

View File

@ -217,7 +217,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
let kind = if let Some(intermediate) = self.check_full_res( let kind = if let Some(intermediate) = self.check_full_res(
TypeNS, TypeNS,
&intermediate_path, &intermediate_path,
Some(module_id), module_id,
current_item, current_item,
extra_fragment, extra_fragment,
) { ) {
@ -235,7 +235,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
fn macro_resolve( fn macro_resolve(
&self, &self,
path_str: &'a str, path_str: &'a str,
parent_id: Option<DefId>, module_id: DefId,
) -> Result<Res, ResolutionFailure<'a>> { ) -> Result<Res, ResolutionFailure<'a>> {
let cx = self.cx; let cx = self.cx;
let path = ast::Path::from_ident(Ident::from_str(path_str)); let path = ast::Path::from_ident(Ident::from_str(path_str));
@ -254,20 +254,15 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
if let Some(res) = resolver.all_macros().get(&Symbol::intern(path_str)) { if let Some(res) = resolver.all_macros().get(&Symbol::intern(path_str)) {
return Some(Ok(res.map_id(|_| panic!("unexpected id")))); return Some(Ok(res.map_id(|_| panic!("unexpected id"))));
} }
if let Some(module_id) = parent_id { debug!("resolving {} as a macro in the module {:?}", path_str, module_id);
debug!("resolving {} as a macro in the module {:?}", path_str, module_id); if let Ok((_, res)) =
if let Ok((_, res)) = resolver.resolve_str_path_error(DUMMY_SP, path_str, MacroNS, module_id)
resolver.resolve_str_path_error(DUMMY_SP, path_str, MacroNS, module_id) {
{ // don't resolve builtins like `#[derive]`
// don't resolve builtins like `#[derive]` if let Res::Def(..) = res {
if let Res::Def(..) = res { let res = res.map_id(|_| panic!("unexpected node_id"));
let res = res.map_id(|_| panic!("unexpected node_id")); return Some(Ok(res));
return Some(Ok(res));
}
} }
} else {
debug!("attempting to resolve item without parent module: {}", path_str);
return Some(Err(ResolutionFailure::NoParentItem));
} }
None None
}) })
@ -275,7 +270,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
.unwrap_or_else(|| { .unwrap_or_else(|| {
let mut split = path_str.rsplitn(2, "::"); let mut split = path_str.rsplitn(2, "::");
if let Some((parent, base)) = split.next().and_then(|x| Some((split.next()?, x))) { if let Some((parent, base)) = split.next().and_then(|x| Some((split.next()?, x))) {
if let Some(res) = self.check_full_res(TypeNS, parent, parent_id, &None, &None) { if let Some(res) = self.check_full_res(TypeNS, parent, module_id, &None, &None) {
return Err(if matches!(res, Res::PrimTy(_)) { return Err(if matches!(res, Res::PrimTy(_)) {
ResolutionFailure::NoPrimitiveAssocItem { ResolutionFailure::NoPrimitiveAssocItem {
res, res,
@ -287,12 +282,10 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
}); });
} }
} }
Err(ResolutionFailure::NotInScope { Err(ResolutionFailure::NotInScope { module_id, name: path_str.into() })
module_id: parent_id.expect("already saw `Some` when resolving as a macro"),
name: path_str.into(),
})
}) })
} }
/// Resolves a string as a path within a particular namespace. Also returns an optional /// Resolves a string as a path within a particular namespace. Also returns an optional
/// URL fragment in the case of variants and methods. /// URL fragment in the case of variants and methods.
fn resolve<'path>( fn resolve<'path>(
@ -300,293 +293,271 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
path_str: &'path str, path_str: &'path str,
ns: Namespace, ns: Namespace,
current_item: &Option<String>, current_item: &Option<String>,
parent_id: Option<DefId>, module_id: DefId,
extra_fragment: &Option<String>, extra_fragment: &Option<String>,
) -> Result<(Res, Option<String>), ErrorKind<'path>> { ) -> Result<(Res, Option<String>), ErrorKind<'path>> {
let cx = self.cx; let cx = self.cx;
// In case we're in a module, try to resolve the relative path. let result = cx.enter_resolver(|resolver| {
if let Some(module_id) = parent_id { resolver.resolve_str_path_error(DUMMY_SP, &path_str, ns, module_id)
let result = cx.enter_resolver(|resolver| { });
resolver.resolve_str_path_error(DUMMY_SP, &path_str, ns, module_id) debug!("{} resolved to {:?} in namespace {:?}", path_str, result, ns);
}); let result = match result {
debug!("{} resolved to {:?} in namespace {:?}", path_str, result, ns); Ok((_, Res::Err)) => Err(()),
let result = match result { x => x,
Ok((_, Res::Err)) => Err(()), };
x => x,
if let Ok((_, res)) = result {
let res = res.map_id(|_| panic!("unexpected node_id"));
// In case this is a trait item, skip the
// early return and try looking for the trait.
let value = match res {
Res::Def(DefKind::AssocFn | DefKind::AssocConst, _) => true,
Res::Def(DefKind::AssocTy, _) => false,
Res::Def(DefKind::Variant, _) => {
return handle_variant(cx, res, extra_fragment);
}
// Not a trait item; just return what we found.
Res::PrimTy(..) => {
if extra_fragment.is_some() {
return Err(ErrorKind::AnchorFailure(
AnchorFailure::RustdocAnchorConflict(res),
));
}
return Ok((res, Some(path_str.to_owned())));
}
Res::Def(DefKind::Mod, _) => {
return Ok((res, extra_fragment.clone()));
}
_ => {
return Ok((res, extra_fragment.clone()));
}
}; };
if let Ok((_, res)) = result { if value != (ns == ValueNS) {
let res = res.map_id(|_| panic!("unexpected node_id")); return Err(ResolutionFailure::WrongNamespace(res, ns).into());
// In case this is a trait item, skip the
// early return and try looking for the trait.
let value = match res {
Res::Def(DefKind::AssocFn | DefKind::AssocConst, _) => true,
Res::Def(DefKind::AssocTy, _) => false,
Res::Def(DefKind::Variant, _) => {
return handle_variant(cx, res, extra_fragment);
}
// Not a trait item; just return what we found.
Res::PrimTy(..) => {
if extra_fragment.is_some() {
return Err(ErrorKind::AnchorFailure(
AnchorFailure::RustdocAnchorConflict(res),
));
}
return Ok((res, Some(path_str.to_owned())));
}
Res::Def(DefKind::Mod, _) => {
return Ok((res, extra_fragment.clone()));
}
_ => {
return Ok((res, extra_fragment.clone()));
}
};
if value != (ns == ValueNS) {
return Err(ResolutionFailure::WrongNamespace(res, ns).into());
}
} else if let Some((path, prim)) = is_primitive(path_str, ns) {
if extra_fragment.is_some() {
return Err(ErrorKind::AnchorFailure(AnchorFailure::RustdocAnchorConflict(
prim,
)));
}
return Ok((prim, Some(path.to_owned())));
} }
} else if let Some((path, prim)) = is_primitive(path_str, ns) {
if extra_fragment.is_some() {
return Err(ErrorKind::AnchorFailure(AnchorFailure::RustdocAnchorConflict(prim)));
}
return Ok((prim, Some(path.to_owned())));
}
// Try looking for methods and associated items. // Try looking for methods and associated items.
let mut split = path_str.rsplitn(2, "::"); let mut split = path_str.rsplitn(2, "::");
// this can be an `unwrap()` because we ensure the link is never empty // this can be an `unwrap()` because we ensure the link is never empty
let item_name = Symbol::intern(split.next().unwrap()); let item_name = Symbol::intern(split.next().unwrap());
let path_root = split let path_root = split
.next() .next()
.map(|f| { .map(|f| {
if f == "self" || f == "Self" { if f == "self" || f == "Self" {
if let Some(name) = current_item.as_ref() { if let Some(name) = current_item.as_ref() {
return name.clone(); return name.clone();
}
} }
f.to_owned() }
}) f.to_owned()
// If there's no `::`, it's not an associated item. })
// So we can be sure that `rustc_resolve` was accurate when it said it wasn't resolved. // If there's no `::`, it's not an associated item.
.ok_or_else(|| { // So we can be sure that `rustc_resolve` was accurate when it said it wasn't resolved.
debug!("found no `::`, assumming {} was correctly not in scope", item_name); .ok_or_else(|| {
ResolutionFailure::NotInScope { module_id, name: item_name.to_string().into() } debug!("found no `::`, assumming {} was correctly not in scope", item_name);
})?; ResolutionFailure::NotInScope { module_id, name: item_name.to_string().into() }
})?;
if let Some((path, prim)) = is_primitive(&path_root, TypeNS) { if let Some((path, prim)) = is_primitive(&path_root, TypeNS) {
let impls = primitive_impl(cx, &path) let impls = primitive_impl(cx, &path)
.ok_or_else(|| ResolutionFailure::NoPrimitiveImpl(prim, path_root.into()))?; .ok_or_else(|| ResolutionFailure::NoPrimitiveImpl(prim, path_root.into()))?;
for &impl_ in impls { for &impl_ in impls {
let link = cx let link = cx
.tcx .tcx
.associated_items(impl_) .associated_items(impl_)
.find_by_name_and_namespace( .find_by_name_and_namespace(
cx.tcx,
Ident::with_dummy_span(item_name),
ns,
impl_,
)
.map(|item| match item.kind {
ty::AssocKind::Fn => "method",
ty::AssocKind::Const => "associatedconstant",
ty::AssocKind::Type => "associatedtype",
})
.map(|out| (prim, Some(format!("{}#{}.{}", path, out, item_name))));
if let Some(link) = link {
return Ok(link);
}
}
debug!(
"returning primitive error for {}::{} in {} namespace",
path,
item_name,
ns.descr()
);
return Err(ResolutionFailure::NoPrimitiveAssocItem {
res: prim,
prim_name: path,
assoc_item: item_name,
}
.into());
}
let ty_res = cx
.enter_resolver(|resolver| {
// only types can have associated items
resolver.resolve_str_path_error(DUMMY_SP, &path_root, TypeNS, module_id)
})
.map(|(_, res)| res);
let ty_res = match ty_res {
Err(()) | Ok(Res::Err) => {
return if ns == Namespace::ValueNS {
self.variant_field(path_str, current_item, module_id, extra_fragment)
} else {
// See if it only broke because of the namespace.
let kind = cx.enter_resolver(|resolver| {
// NOTE: this doesn't use `check_full_res` because we explicitly want to ignore `TypeNS` (we already checked it)
for &ns in &[MacroNS, ValueNS] {
match resolver
.resolve_str_path_error(DUMMY_SP, &path_root, ns, module_id)
{
Ok((_, Res::Err)) | Err(()) => {}
Ok((_, res)) => {
let res = res.map_id(|_| panic!("unexpected node_id"));
return ResolutionFailure::CannotHaveAssociatedItems(res, ns);
}
}
}
ResolutionFailure::NotInScope { module_id, name: path_root.into() }
});
Err(kind.into())
};
}
Ok(res) => res,
};
let ty_res = ty_res.map_id(|_| panic!("unexpected node_id"));
let res = match ty_res {
Res::Def(DefKind::Struct | DefKind::Union | DefKind::Enum | DefKind::TyAlias, did) => {
debug!("looking for associated item named {} for item {:?}", item_name, did);
// Checks if item_name belongs to `impl SomeItem`
let assoc_item = cx
.tcx
.inherent_impls(did)
.iter()
.flat_map(|&imp| {
cx.tcx.associated_items(imp).find_by_name_and_namespace(
cx.tcx, cx.tcx,
Ident::with_dummy_span(item_name), Ident::with_dummy_span(item_name),
ns, ns,
impl_, imp,
) )
.map(|item| match item.kind { })
ty::AssocKind::Fn => "method", .map(|item| (item.kind, item.def_id))
ty::AssocKind::Const => "associatedconstant", // There should only ever be one associated item that matches from any inherent impl
ty::AssocKind::Type => "associatedtype", .next()
}) // Check if item_name belongs to `impl SomeTrait for SomeItem`
.map(|out| (prim, Some(format!("{}#{}.{}", path, out, item_name)))); // This gives precedence to `impl SomeItem`:
if let Some(link) = link { // Although having both would be ambiguous, use impl version for compat. sake.
return Ok(link); // To handle that properly resolve() would have to support
} // something like [`ambi_fn`](<SomeStruct as SomeTrait>::ambi_fn)
} .or_else(|| {
debug!( let kind =
"returning primitive error for {}::{} in {} namespace", resolve_associated_trait_item(did, module_id, item_name, ns, &self.cx);
path, debug!("got associated item kind {:?}", kind);
item_name, kind
ns.descr() });
);
return Err(ResolutionFailure::NoPrimitiveAssocItem {
res: prim,
prim_name: path,
assoc_item: item_name,
}
.into());
}
let ty_res = cx if let Some((kind, id)) = assoc_item {
.enter_resolver(|resolver| { let out = match kind {
// only types can have associated items ty::AssocKind::Fn => "method",
resolver.resolve_str_path_error(DUMMY_SP, &path_root, TypeNS, module_id) ty::AssocKind::Const => "associatedconstant",
}) ty::AssocKind::Type => "associatedtype",
.map(|(_, res)| res);
let ty_res = match ty_res {
Err(()) | Ok(Res::Err) => {
return if ns == Namespace::ValueNS {
self.variant_field(path_str, current_item, module_id, extra_fragment)
} else {
// See if it only broke because of the namespace.
let kind = cx.enter_resolver(|resolver| {
// NOTE: this doesn't use `check_full_res` because we explicitly want to ignore `TypeNS` (we already checked it)
for &ns in &[MacroNS, ValueNS] {
match resolver
.resolve_str_path_error(DUMMY_SP, &path_root, ns, module_id)
{
Ok((_, Res::Err)) | Err(()) => {}
Ok((_, res)) => {
let res = res.map_id(|_| panic!("unexpected node_id"));
return ResolutionFailure::CannotHaveAssociatedItems(
res, ns,
);
}
}
}
ResolutionFailure::NotInScope { module_id, name: path_root.into() }
});
Err(kind.into())
}; };
} Some(if extra_fragment.is_some() {
Ok(res) => res, Err(ErrorKind::AnchorFailure(AnchorFailure::RustdocAnchorConflict(ty_res)))
};
let ty_res = ty_res.map_id(|_| panic!("unexpected node_id"));
let res = match ty_res {
Res::Def(
DefKind::Struct | DefKind::Union | DefKind::Enum | DefKind::TyAlias,
did,
) => {
debug!("looking for associated item named {} for item {:?}", item_name, did);
// Checks if item_name belongs to `impl SomeItem`
let assoc_item = cx
.tcx
.inherent_impls(did)
.iter()
.flat_map(|&imp| {
cx.tcx.associated_items(imp).find_by_name_and_namespace(
cx.tcx,
Ident::with_dummy_span(item_name),
ns,
imp,
)
})
.map(|item| (item.kind, item.def_id))
// There should only ever be one associated item that matches from any inherent impl
.next()
// Check if item_name belongs to `impl SomeTrait for SomeItem`
// This gives precedence to `impl SomeItem`:
// Although having both would be ambiguous, use impl version for compat. sake.
// To handle that properly resolve() would have to support
// something like [`ambi_fn`](<SomeStruct as SomeTrait>::ambi_fn)
.or_else(|| {
let kind = resolve_associated_trait_item(
did, module_id, item_name, ns, &self.cx,
);
debug!("got associated item kind {:?}", kind);
kind
});
if let Some((kind, id)) = assoc_item {
let out = match kind {
ty::AssocKind::Fn => "method",
ty::AssocKind::Const => "associatedconstant",
ty::AssocKind::Type => "associatedtype",
};
Some(if extra_fragment.is_some() {
Err(ErrorKind::AnchorFailure(AnchorFailure::RustdocAnchorConflict(
ty_res,
)))
} else {
// HACK(jynelson): `clean` expects the type, not the associated item.
// but the disambiguator logic expects the associated item.
// Store the kind in a side channel so that only the disambiguator logic looks at it.
self.kind_side_channel.set(Some((kind.as_def_kind(), id)));
Ok((ty_res, Some(format!("{}.{}", out, item_name))))
})
} else if ns == Namespace::ValueNS {
debug!("looking for variants or fields named {} for {:?}", item_name, did);
match cx.tcx.type_of(did).kind() {
ty::Adt(def, _) => {
let field = if def.is_enum() {
def.all_fields().find(|item| item.ident.name == item_name)
} else {
def.non_enum_variant()
.fields
.iter()
.find(|item| item.ident.name == item_name)
};
field.map(|item| {
if extra_fragment.is_some() {
let res = Res::Def(
if def.is_enum() {
DefKind::Variant
} else {
DefKind::Field
},
item.did,
);
Err(ErrorKind::AnchorFailure(
AnchorFailure::RustdocAnchorConflict(res),
))
} else {
Ok((
ty_res,
Some(format!(
"{}.{}",
if def.is_enum() {
"variant"
} else {
"structfield"
},
item.ident
)),
))
}
})
}
_ => None,
}
} else { } else {
// We already know this isn't in ValueNS, so no need to check variant_field // HACK(jynelson): `clean` expects the type, not the associated item.
return Err(ResolutionFailure::NoAssocItem(ty_res, item_name).into()); // but the disambiguator logic expects the associated item.
} // Store the kind in a side channel so that only the disambiguator logic looks at it.
} self.kind_side_channel.set(Some((kind.as_def_kind(), id)));
Res::Def(DefKind::Trait, did) => cx Ok((ty_res, Some(format!("{}.{}", out, item_name))))
.tcx })
.associated_items(did) } else if ns == Namespace::ValueNS {
.find_by_name_and_namespace(cx.tcx, Ident::with_dummy_span(item_name), ns, did) debug!("looking for variants or fields named {} for {:?}", item_name, did);
.map(|item| { match cx.tcx.type_of(did).kind() {
let kind = match item.kind { ty::Adt(def, _) => {
ty::AssocKind::Const => "associatedconstant", let field = if def.is_enum() {
ty::AssocKind::Type => "associatedtype", def.all_fields().find(|item| item.ident.name == item_name)
ty::AssocKind::Fn => { } else {
if item.defaultness.has_value() { def.non_enum_variant()
"method" .fields
.iter()
.find(|item| item.ident.name == item_name)
};
field.map(|item| {
if extra_fragment.is_some() {
let res = Res::Def(
if def.is_enum() {
DefKind::Variant
} else {
DefKind::Field
},
item.did,
);
Err(ErrorKind::AnchorFailure(
AnchorFailure::RustdocAnchorConflict(res),
))
} else { } else {
"tymethod" Ok((
ty_res,
Some(format!(
"{}.{}",
if def.is_enum() { "variant" } else { "structfield" },
item.ident
)),
))
} }
} })
};
if extra_fragment.is_some() {
Err(ErrorKind::AnchorFailure(AnchorFailure::RustdocAnchorConflict(
ty_res,
)))
} else {
let res = Res::Def(item.kind.as_def_kind(), item.def_id);
Ok((res, Some(format!("{}.{}", kind, item_name))))
} }
}), _ => None,
_ => None, }
};
res.unwrap_or_else(|| {
if ns == Namespace::ValueNS {
self.variant_field(path_str, current_item, module_id, extra_fragment)
} else { } else {
Err(ResolutionFailure::NoAssocItem(ty_res, item_name).into()) // We already know this isn't in ValueNS, so no need to check variant_field
return Err(ResolutionFailure::NoAssocItem(ty_res, item_name).into());
} }
}) }
} else { Res::Def(DefKind::Trait, did) => cx
debug!("attempting to resolve item without parent module: {}", path_str); .tcx
Err(ResolutionFailure::NoParentItem.into()) .associated_items(did)
} .find_by_name_and_namespace(cx.tcx, Ident::with_dummy_span(item_name), ns, did)
.map(|item| {
let kind = match item.kind {
ty::AssocKind::Const => "associatedconstant",
ty::AssocKind::Type => "associatedtype",
ty::AssocKind::Fn => {
if item.defaultness.has_value() {
"method"
} else {
"tymethod"
}
}
};
if extra_fragment.is_some() {
Err(ErrorKind::AnchorFailure(AnchorFailure::RustdocAnchorConflict(ty_res)))
} else {
let res = Res::Def(item.kind.as_def_kind(), item.def_id);
Ok((res, Some(format!("{}.{}", kind, item_name))))
}
}),
_ => None,
};
res.unwrap_or_else(|| {
if ns == Namespace::ValueNS {
self.variant_field(path_str, current_item, module_id, extra_fragment)
} else {
Err(ResolutionFailure::NoAssocItem(ty_res, item_name).into())
}
})
} }
/// Used for reporting better errors. /// Used for reporting better errors.
@ -599,7 +570,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
&self, &self,
ns: Namespace, ns: Namespace,
path_str: &str, path_str: &str,
base_node: Option<DefId>, module_id: DefId,
current_item: &Option<String>, current_item: &Option<String>,
extra_fragment: &Option<String>, extra_fragment: &Option<String>,
) -> Option<Res> { ) -> Option<Res> {
@ -616,11 +587,11 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
}; };
// cannot be used for macro namespace // cannot be used for macro namespace
let check_full_res = |this: &Self, ns| { let check_full_res = |this: &Self, ns| {
let result = this.resolve(path_str, ns, current_item, base_node, extra_fragment); let result = this.resolve(path_str, ns, current_item, module_id, extra_fragment);
check_full_res_inner(this, result.map(|(res, _)| res)) check_full_res_inner(this, result.map(|(res, _)| res))
}; };
let check_full_res_macro = |this: &Self| { let check_full_res_macro = |this: &Self| {
let result = this.macro_resolve(path_str, base_node); let result = this.macro_resolve(path_str, module_id);
check_full_res_inner(this, result.map_err(ErrorKind::from)) check_full_res_inner(this, result.map_err(ErrorKind::from))
}; };
match ns { match ns {
@ -990,6 +961,23 @@ impl LinkCollector<'_, '_> {
parent_node parent_node
}; };
let module_id = if let Some(id) = base_node {
id
} else {
debug!("attempting to resolve item without parent module: {}", path_str);
let err_kind = ResolutionFailure::NoParentItem.into();
resolution_failure(
self,
&item,
path_str,
disambiguator,
dox,
link_range,
smallvec![err_kind],
);
return;
};
// replace `Self` with suitable item's parent name // replace `Self` with suitable item's parent name
if path_str.starts_with("Self::") { if path_str.starts_with("Self::") {
if let Some(ref name) = parent_name { if let Some(ref name) = parent_name {
@ -1004,7 +992,7 @@ impl LinkCollector<'_, '_> {
dox, dox,
path_str, path_str,
current_item, current_item,
base_node, module_id,
extra_fragment, extra_fragment,
&ori_link, &ori_link,
link_range.clone(), link_range.clone(),
@ -1132,7 +1120,7 @@ impl LinkCollector<'_, '_> {
dox: &str, dox: &str,
path_str: &str, path_str: &str,
current_item: &Option<String>, current_item: &Option<String>,
base_node: Option<DefId>, base_node: DefId,
extra_fragment: Option<String>, extra_fragment: Option<String>,
ori_link: &str, ori_link: &str,
link_range: Option<Range<usize>>, link_range: Option<Range<usize>>,
@ -1580,13 +1568,9 @@ fn resolution_failure(
break; break;
} }
}; };
if let Some(res) = collector.check_full_res( if let Some(res) =
TypeNS, collector.check_full_res(TypeNS, &current, *module_id, &None, &None)
&current, {
Some(*module_id),
&None,
&None,
) {
failure = ResolutionFailure::NoAssocItem(res, Symbol::intern(current)); failure = ResolutionFailure::NoAssocItem(res, Symbol::intern(current));
break; break;
} }