mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-25 08:13:41 +00:00
Implement RTN in resolve_bound_vars and HIR ty lowering
This commit is contained in:
parent
19881b5a5a
commit
51b51bb570
@ -487,11 +487,12 @@ enum ParamMode {
|
|||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
enum AllowReturnTypeNotation {
|
enum AllowReturnTypeNotation {
|
||||||
|
/// Only in types, since RTN is denied later during HIR lowering.
|
||||||
Yes,
|
Yes,
|
||||||
|
/// All other positions (path expr, method, use tree).
|
||||||
No,
|
No,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
|
||||||
enum GenericArgsMode {
|
enum GenericArgsMode {
|
||||||
ParenSugar,
|
ParenSugar,
|
||||||
ReturnTypeNotation,
|
ReturnTypeNotation,
|
||||||
@ -1239,7 +1240,6 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
|
|||||||
qself,
|
qself,
|
||||||
path,
|
path,
|
||||||
param_mode,
|
param_mode,
|
||||||
// We deny these after the fact in HIR->middle type lowering.
|
|
||||||
AllowReturnTypeNotation::Yes,
|
AllowReturnTypeNotation::Yes,
|
||||||
itctx,
|
itctx,
|
||||||
None,
|
None,
|
||||||
|
@ -16,10 +16,9 @@ use super::errors::{
|
|||||||
GenericTypeWithParentheses, UseAngleBrackets,
|
GenericTypeWithParentheses, UseAngleBrackets,
|
||||||
};
|
};
|
||||||
use super::{
|
use super::{
|
||||||
AllowReturnTypeNotation, GenericArgsCtor, GenericArgsMode, ImplTraitContext, LifetimeRes,
|
AllowReturnTypeNotation, GenericArgsCtor, GenericArgsMode, ImplTraitContext, ImplTraitPosition,
|
||||||
LoweringContext, ParamMode, ResolverAstLoweringExt,
|
LifetimeRes, LoweringContext, ParamMode, ResolverAstLoweringExt,
|
||||||
};
|
};
|
||||||
use crate::ImplTraitPosition;
|
|
||||||
|
|
||||||
impl<'a, 'hir> LoweringContext<'a, 'hir> {
|
impl<'a, 'hir> LoweringContext<'a, 'hir> {
|
||||||
#[instrument(level = "trace", skip(self))]
|
#[instrument(level = "trace", skip(self))]
|
||||||
|
@ -240,7 +240,8 @@ fn gather_explicit_predicates_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Gen
|
|||||||
for predicate in hir_generics.predicates {
|
for predicate in hir_generics.predicates {
|
||||||
match predicate {
|
match predicate {
|
||||||
hir::WherePredicate::BoundPredicate(bound_pred) => {
|
hir::WherePredicate::BoundPredicate(bound_pred) => {
|
||||||
let ty = icx.lower_ty(bound_pred.bounded_ty);
|
let ty = icx.lowerer().lower_ty_maybe_return_type_notation(bound_pred.bounded_ty);
|
||||||
|
|
||||||
let bound_vars = tcx.late_bound_vars(bound_pred.hir_id);
|
let bound_vars = tcx.late_bound_vars(bound_pred.hir_id);
|
||||||
// Keep the type around in a dummy predicate, in case of no bounds.
|
// Keep the type around in a dummy predicate, in case of no bounds.
|
||||||
// That way, `where Ty:` is not a complete noop (see #53696) and `Ty`
|
// That way, `where Ty:` is not a complete noop (see #53696) and `Ty`
|
||||||
@ -770,6 +771,10 @@ impl<'tcx> ItemCtxt<'tcx> {
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Subtle: If we're collecting `SelfAndAssociatedTypeBounds`, then we
|
||||||
|
// want to only consider predicates with `Self: ...`, but we don't want
|
||||||
|
// `OnlySelfBounds(true)` since we want to collect the nested associated
|
||||||
|
// type bound as well.
|
||||||
let (only_self_bounds, assoc_name) = match filter {
|
let (only_self_bounds, assoc_name) = match filter {
|
||||||
PredicateFilter::All | PredicateFilter::SelfAndAssociatedTypeBounds => {
|
PredicateFilter::All | PredicateFilter::SelfAndAssociatedTypeBounds => {
|
||||||
(OnlySelfBounds(false), None)
|
(OnlySelfBounds(false), None)
|
||||||
@ -780,14 +785,10 @@ impl<'tcx> ItemCtxt<'tcx> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Subtle: If we're collecting `SelfAndAssociatedTypeBounds`, then we
|
|
||||||
// want to only consider predicates with `Self: ...`, but we don't want
|
|
||||||
// `OnlySelfBounds(true)` since we want to collect the nested associated
|
|
||||||
// type bound as well.
|
|
||||||
let bound_ty = if predicate.is_param_bound(param_def_id.to_def_id()) {
|
let bound_ty = if predicate.is_param_bound(param_def_id.to_def_id()) {
|
||||||
ty
|
ty
|
||||||
} else if matches!(filter, PredicateFilter::All) {
|
} else if matches!(filter, PredicateFilter::All) {
|
||||||
self.lower_ty(predicate.bounded_ty)
|
self.lowerer().lower_ty_maybe_return_type_notation(predicate.bounded_ty)
|
||||||
} else {
|
} else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
@ -889,7 +889,12 @@ impl<'a, 'tcx> Visitor<'tcx> for BoundVarContext<'a, 'tcx> {
|
|||||||
(pair, r)
|
(pair, r)
|
||||||
})
|
})
|
||||||
.unzip();
|
.unzip();
|
||||||
|
|
||||||
self.record_late_bound_vars(hir_id, binders);
|
self.record_late_bound_vars(hir_id, binders);
|
||||||
|
|
||||||
|
// If this is an RTN type in the self type, then append those to the binder.
|
||||||
|
self.try_append_return_type_notation_params(hir_id, bounded_ty);
|
||||||
|
|
||||||
// Even if there are no lifetimes defined here, we still wrap it in a binder
|
// Even if there are no lifetimes defined here, we still wrap it in a binder
|
||||||
// scope. If there happens to be a nested poly trait ref (an error), that
|
// scope. If there happens to be a nested poly trait ref (an error), that
|
||||||
// will be `Concatenating` anyways, so we don't have to worry about the depth
|
// will be `Concatenating` anyways, so we don't have to worry about the depth
|
||||||
@ -1839,6 +1844,110 @@ impl<'a, 'tcx> BoundVarContext<'a, 'tcx> {
|
|||||||
let old_value = self.map.defs.swap_remove(&lifetime_ref.hir_id);
|
let old_value = self.map.defs.swap_remove(&lifetime_ref.hir_id);
|
||||||
assert_eq!(old_value, Some(bad_def));
|
assert_eq!(old_value, Some(bad_def));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
fn try_append_return_type_notation_params(
|
||||||
|
&mut self,
|
||||||
|
hir_id: HirId,
|
||||||
|
hir_ty: &'tcx hir::Ty<'tcx>,
|
||||||
|
) {
|
||||||
|
let hir::TyKind::Path(qpath) = hir_ty.kind else {
|
||||||
|
// TODO:
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let (mut bound_vars, item_def_id, item_segment) = match qpath {
|
||||||
|
// TODO:
|
||||||
|
hir::QPath::Resolved(_, path)
|
||||||
|
if let [.., item_segment] = &path.segments[..]
|
||||||
|
&& item_segment.args.is_some_and(|args| {
|
||||||
|
matches!(
|
||||||
|
args.parenthesized,
|
||||||
|
hir::GenericArgsParentheses::ReturnTypeNotation
|
||||||
|
)
|
||||||
|
}) =>
|
||||||
|
{
|
||||||
|
let Res::Def(DefKind::AssocFn, item_def_id) = path.res else {
|
||||||
|
bug!();
|
||||||
|
};
|
||||||
|
(vec![], item_def_id, item_segment)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
hir::QPath::TypeRelative(qself, item_segment)
|
||||||
|
if item_segment.args.is_some_and(|args| {
|
||||||
|
matches!(args.parenthesized, hir::GenericArgsParentheses::ReturnTypeNotation)
|
||||||
|
}) =>
|
||||||
|
{
|
||||||
|
let hir::TyKind::Path(hir::QPath::Resolved(None, path)) = qself.kind else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
match path.res {
|
||||||
|
Res::Def(DefKind::TyParam, _) | Res::SelfTyParam { trait_: _ } => {
|
||||||
|
let Some(generics) = self.tcx.hir_owner_node(hir_id.owner).generics()
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let one_bound = generics.predicates.iter().find_map(|predicate| {
|
||||||
|
let hir::WherePredicate::BoundPredicate(predicate) = predicate else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let hir::TyKind::Path(hir::QPath::Resolved(None, bounded_path)) =
|
||||||
|
predicate.bounded_ty.kind
|
||||||
|
else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
if bounded_path.res != path.res {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
predicate.bounds.iter().find_map(|bound| {
|
||||||
|
let hir::GenericBound::Trait(trait_, _) = bound else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
BoundVarContext::supertrait_hrtb_vars(
|
||||||
|
self.tcx,
|
||||||
|
trait_.trait_ref.trait_def_id()?,
|
||||||
|
item_segment.ident,
|
||||||
|
ty::AssocKind::Fn,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
let Some((bound_vars, assoc_item)) = one_bound else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
(bound_vars, assoc_item.def_id, item_segment)
|
||||||
|
}
|
||||||
|
Res::SelfTyAlias { is_trait_impl: true, .. } => todo!(),
|
||||||
|
_ => return,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
bound_vars.extend(self.tcx.generics_of(item_def_id).own_params.iter().map(|param| {
|
||||||
|
match param.kind {
|
||||||
|
ty::GenericParamDefKind::Lifetime => ty::BoundVariableKind::Region(
|
||||||
|
ty::BoundRegionKind::BrNamed(param.def_id, param.name),
|
||||||
|
),
|
||||||
|
ty::GenericParamDefKind::Type { .. } => {
|
||||||
|
ty::BoundVariableKind::Ty(ty::BoundTyKind::Param(param.def_id, param.name))
|
||||||
|
}
|
||||||
|
ty::GenericParamDefKind::Const { .. } => ty::BoundVariableKind::Const,
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
bound_vars.extend(self.tcx.fn_sig(item_def_id).instantiate_identity().bound_vars());
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
let existing_bound_vars = self.map.late_bound_vars.get_mut(&hir_id).unwrap();
|
||||||
|
let existing_bound_vars_saved = existing_bound_vars.clone();
|
||||||
|
existing_bound_vars.extend(bound_vars);
|
||||||
|
// TODO: subtle
|
||||||
|
self.record_late_bound_vars(item_segment.hir_id, existing_bound_vars_saved);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Detects late-bound lifetimes and inserts them into
|
/// Detects late-bound lifetimes and inserts them into
|
||||||
|
@ -6,6 +6,7 @@ use rustc_errors::struct_span_code_err;
|
|||||||
use rustc_hir as hir;
|
use rustc_hir as hir;
|
||||||
use rustc_hir::def::{DefKind, Res};
|
use rustc_hir::def::{DefKind, Res};
|
||||||
use rustc_hir::def_id::{DefId, LocalDefId};
|
use rustc_hir::def_id::{DefId, LocalDefId};
|
||||||
|
use rustc_hir::HirId;
|
||||||
use rustc_middle::bug;
|
use rustc_middle::bug;
|
||||||
use rustc_middle::ty::{self as ty, IsSuggestable, Ty, TyCtxt};
|
use rustc_middle::ty::{self as ty, IsSuggestable, Ty, TyCtxt};
|
||||||
use rustc_span::symbol::Ident;
|
use rustc_span::symbol::Ident;
|
||||||
@ -15,6 +16,7 @@ use rustc_type_ir::visit::{TypeSuperVisitable, TypeVisitable, TypeVisitableExt,
|
|||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use tracing::{debug, instrument};
|
use tracing::{debug, instrument};
|
||||||
|
|
||||||
|
use super::errors::GenericsArgsErrExtend;
|
||||||
use crate::bounds::Bounds;
|
use crate::bounds::Bounds;
|
||||||
use crate::errors;
|
use crate::errors;
|
||||||
use crate::hir_ty_lowering::{
|
use crate::hir_ty_lowering::{
|
||||||
@ -332,74 +334,11 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
|
|||||||
.or_insert(constraint.span);
|
.or_insert(constraint.span);
|
||||||
|
|
||||||
let projection_term = if let ty::AssocKind::Fn = assoc_kind {
|
let projection_term = if let ty::AssocKind::Fn = assoc_kind {
|
||||||
let mut emitted_bad_param_err = None;
|
|
||||||
// If we have an method return type bound, then we need to instantiate
|
|
||||||
// the method's early bound params with suitable late-bound params.
|
|
||||||
let mut num_bound_vars = candidate.bound_vars().len();
|
|
||||||
let args =
|
|
||||||
candidate.skip_binder().args.extend_to(tcx, assoc_item.def_id, |param, _| {
|
|
||||||
let arg = match param.kind {
|
|
||||||
ty::GenericParamDefKind::Lifetime => ty::Region::new_bound(
|
|
||||||
tcx,
|
|
||||||
ty::INNERMOST,
|
|
||||||
ty::BoundRegion {
|
|
||||||
var: ty::BoundVar::from_usize(num_bound_vars),
|
|
||||||
kind: ty::BoundRegionKind::BrNamed(param.def_id, param.name),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.into(),
|
|
||||||
ty::GenericParamDefKind::Type { .. } => {
|
|
||||||
let guar = *emitted_bad_param_err.get_or_insert_with(|| {
|
|
||||||
self.dcx().emit_err(
|
|
||||||
crate::errors::ReturnTypeNotationIllegalParam::Type {
|
|
||||||
span: path_span,
|
|
||||||
param_span: tcx.def_span(param.def_id),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
});
|
|
||||||
Ty::new_error(tcx, guar).into()
|
|
||||||
}
|
|
||||||
ty::GenericParamDefKind::Const { .. } => {
|
|
||||||
let guar = *emitted_bad_param_err.get_or_insert_with(|| {
|
|
||||||
self.dcx().emit_err(
|
|
||||||
crate::errors::ReturnTypeNotationIllegalParam::Const {
|
|
||||||
span: path_span,
|
|
||||||
param_span: tcx.def_span(param.def_id),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
});
|
|
||||||
ty::Const::new_error(tcx, guar).into()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
num_bound_vars += 1;
|
|
||||||
arg
|
|
||||||
});
|
|
||||||
|
|
||||||
// Next, we need to check that the return-type notation is being used on
|
|
||||||
// an RPITIT (return-position impl trait in trait) or AFIT (async fn in trait).
|
|
||||||
let output = tcx.fn_sig(assoc_item.def_id).skip_binder().output();
|
|
||||||
let output = if let ty::Alias(ty::Projection, alias_ty) = *output.skip_binder().kind()
|
|
||||||
&& tcx.is_impl_trait_in_trait(alias_ty.def_id)
|
|
||||||
{
|
|
||||||
alias_ty.into()
|
|
||||||
} else {
|
|
||||||
return Err(self.dcx().emit_err(crate::errors::ReturnTypeNotationOnNonRpitit {
|
|
||||||
span: constraint.span,
|
|
||||||
ty: tcx.liberate_late_bound_regions(assoc_item.def_id, output),
|
|
||||||
fn_span: tcx.hir().span_if_local(assoc_item.def_id),
|
|
||||||
note: (),
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
// Finally, move the fn return type's bound vars over to account for the early bound
|
|
||||||
// params (and trait ref's late bound params). This logic is very similar to
|
|
||||||
// `rustc_middle::ty::predicate::Clause::instantiate_supertrait`
|
|
||||||
// and it's no coincidence why.
|
|
||||||
let shifted_output = tcx.shift_bound_var_indices(num_bound_vars, output);
|
|
||||||
let instantiation_output = ty::EarlyBinder::bind(shifted_output).instantiate(tcx, args);
|
|
||||||
|
|
||||||
let bound_vars = tcx.late_bound_vars(constraint.hir_id);
|
let bound_vars = tcx.late_bound_vars(constraint.hir_id);
|
||||||
ty::Binder::bind_with_vars(instantiation_output, bound_vars)
|
ty::Binder::bind_with_vars(
|
||||||
|
self.lower_return_type_notation_ty(candidate, assoc_item.def_id, path_span)?.into(),
|
||||||
|
bound_vars,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
// Create the generic arguments for the associated type or constant by joining the
|
// Create the generic arguments for the associated type or constant by joining the
|
||||||
// parent arguments (the arguments of the trait) and the own arguments (the ones of
|
// parent arguments (the arguments of the trait) and the own arguments (the ones of
|
||||||
@ -525,6 +464,219 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
pub fn lower_ty_maybe_return_type_notation(&self, hir_ty: &hir::Ty<'tcx>) -> Ty<'tcx> {
|
||||||
|
let hir::TyKind::Path(qpath) = hir_ty.kind else {
|
||||||
|
return self.lower_ty(hir_ty);
|
||||||
|
};
|
||||||
|
|
||||||
|
let tcx = self.tcx();
|
||||||
|
match qpath {
|
||||||
|
hir::QPath::Resolved(opt_self_ty, path)
|
||||||
|
if let [mod_segments @ .., trait_segment, item_segment] = &path.segments[..]
|
||||||
|
&& item_segment.args.is_some_and(|args| {
|
||||||
|
matches!(
|
||||||
|
args.parenthesized,
|
||||||
|
hir::GenericArgsParentheses::ReturnTypeNotation
|
||||||
|
)
|
||||||
|
}) =>
|
||||||
|
{
|
||||||
|
let _ =
|
||||||
|
self.prohibit_generic_args(mod_segments.iter(), GenericsArgsErrExtend::None);
|
||||||
|
|
||||||
|
let Res::Def(DefKind::AssocFn, item_def_id) = path.res else {
|
||||||
|
bug!();
|
||||||
|
};
|
||||||
|
let trait_def_id = tcx.parent(item_def_id);
|
||||||
|
|
||||||
|
let Some(self_ty) = opt_self_ty else {
|
||||||
|
return self.error_missing_qpath_self_ty(
|
||||||
|
trait_def_id,
|
||||||
|
hir_ty.span,
|
||||||
|
item_segment,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
let self_ty = self.lower_ty(self_ty);
|
||||||
|
|
||||||
|
let trait_ref = self.lower_mono_trait_ref(
|
||||||
|
hir_ty.span,
|
||||||
|
trait_def_id,
|
||||||
|
self_ty,
|
||||||
|
trait_segment,
|
||||||
|
false,
|
||||||
|
ty::BoundConstness::NotConst,
|
||||||
|
);
|
||||||
|
|
||||||
|
let candidate =
|
||||||
|
ty::Binder::bind_with_vars(trait_ref, tcx.late_bound_vars(item_segment.hir_id));
|
||||||
|
|
||||||
|
match self.lower_return_type_notation_ty(candidate, item_def_id, hir_ty.span) {
|
||||||
|
Ok(ty) => Ty::new_alias(tcx, ty::Projection, ty),
|
||||||
|
Err(guar) => Ty::new_error(tcx, guar),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hir::QPath::TypeRelative(qself, item_segment)
|
||||||
|
if item_segment.args.is_some_and(|args| {
|
||||||
|
matches!(args.parenthesized, hir::GenericArgsParentheses::ReturnTypeNotation)
|
||||||
|
}) =>
|
||||||
|
{
|
||||||
|
match self
|
||||||
|
.resolve_type_relative_return_type_notation(
|
||||||
|
qself,
|
||||||
|
item_segment,
|
||||||
|
hir_ty.hir_id,
|
||||||
|
hir_ty.span,
|
||||||
|
)
|
||||||
|
.and_then(|(candidate, item_def_id)| {
|
||||||
|
self.lower_return_type_notation_ty(candidate, item_def_id, hir_ty.span)
|
||||||
|
}) {
|
||||||
|
Ok(ty) => Ty::new_alias(tcx, ty::Projection, ty),
|
||||||
|
Err(guar) => Ty::new_error(tcx, guar),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => self.lower_ty(hir_ty),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
fn resolve_type_relative_return_type_notation(
|
||||||
|
&self,
|
||||||
|
qself: &'tcx hir::Ty<'tcx>,
|
||||||
|
item_segment: &'tcx hir::PathSegment<'tcx>,
|
||||||
|
qpath_hir_id: HirId,
|
||||||
|
span: Span,
|
||||||
|
) -> Result<(ty::PolyTraitRef<'tcx>, DefId), ErrorGuaranteed> {
|
||||||
|
let tcx = self.tcx();
|
||||||
|
let qself_ty = self.lower_ty(qself);
|
||||||
|
let assoc_ident = item_segment.ident;
|
||||||
|
let qself_res = if let hir::TyKind::Path(hir::QPath::Resolved(_, path)) = &qself.kind {
|
||||||
|
path.res
|
||||||
|
} else {
|
||||||
|
Res::Err
|
||||||
|
};
|
||||||
|
|
||||||
|
let bound = match (qself_ty.kind(), qself_res) {
|
||||||
|
(_, Res::SelfTyAlias { alias_to: impl_def_id, is_trait_impl: true, .. }) => {
|
||||||
|
// `Self` in an impl of a trait -- we have a concrete self type and a
|
||||||
|
// trait reference.
|
||||||
|
let Some(trait_ref) = tcx.impl_trait_ref(impl_def_id) else {
|
||||||
|
// A cycle error occurred, most likely.
|
||||||
|
self.dcx().span_bug(span, "expected cycle error");
|
||||||
|
};
|
||||||
|
|
||||||
|
self.probe_single_bound_for_assoc_item(
|
||||||
|
|| {
|
||||||
|
traits::supertraits(
|
||||||
|
tcx,
|
||||||
|
ty::Binder::dummy(trait_ref.instantiate_identity()),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
AssocItemQSelf::SelfTyAlias,
|
||||||
|
ty::AssocKind::Fn,
|
||||||
|
assoc_ident,
|
||||||
|
span,
|
||||||
|
None,
|
||||||
|
)?
|
||||||
|
}
|
||||||
|
(
|
||||||
|
&ty::Param(_),
|
||||||
|
Res::SelfTyParam { trait_: param_did } | Res::Def(DefKind::TyParam, param_did),
|
||||||
|
) => self.probe_single_ty_param_bound_for_assoc_item(
|
||||||
|
param_did.expect_local(),
|
||||||
|
qself.span,
|
||||||
|
ty::AssocKind::Fn,
|
||||||
|
assoc_ident,
|
||||||
|
span,
|
||||||
|
)?,
|
||||||
|
_ => todo!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Don't let `T::method` resolve to some `for<'a> <T as Tr<'a>>::method`.
|
||||||
|
// This is the same restrictions as associated types; even though we could
|
||||||
|
// support it, it just makes things a lot more difficult to support in
|
||||||
|
// `resolve_bound_vars`.
|
||||||
|
if bound.has_bound_vars() {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
|
||||||
|
let trait_def_id = bound.def_id();
|
||||||
|
let assoc_ty = self
|
||||||
|
.probe_assoc_item(assoc_ident, ty::AssocKind::Fn, qpath_hir_id, span, trait_def_id)
|
||||||
|
.expect("failed to find associated type");
|
||||||
|
|
||||||
|
Ok((bound, assoc_ty.def_id))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
fn lower_return_type_notation_ty(
|
||||||
|
&self,
|
||||||
|
candidate: ty::PolyTraitRef<'tcx>,
|
||||||
|
item_def_id: DefId,
|
||||||
|
path_span: Span,
|
||||||
|
) -> Result<ty::AliasTy<'tcx>, ErrorGuaranteed> {
|
||||||
|
let tcx = self.tcx();
|
||||||
|
let mut emitted_bad_param_err = None;
|
||||||
|
// If we have an method return type bound, then we need to instantiate
|
||||||
|
// the method's early bound params with suitable late-bound params.
|
||||||
|
let mut num_bound_vars = candidate.bound_vars().len();
|
||||||
|
let args = candidate.skip_binder().args.extend_to(tcx, item_def_id, |param, _| {
|
||||||
|
let arg = match param.kind {
|
||||||
|
ty::GenericParamDefKind::Lifetime => ty::Region::new_bound(
|
||||||
|
tcx,
|
||||||
|
ty::INNERMOST,
|
||||||
|
ty::BoundRegion {
|
||||||
|
var: ty::BoundVar::from_usize(num_bound_vars),
|
||||||
|
kind: ty::BoundRegionKind::BrNamed(param.def_id, param.name),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
ty::GenericParamDefKind::Type { .. } => {
|
||||||
|
let guar = *emitted_bad_param_err.get_or_insert_with(|| {
|
||||||
|
self.dcx().emit_err(crate::errors::ReturnTypeNotationIllegalParam::Type {
|
||||||
|
span: path_span,
|
||||||
|
param_span: tcx.def_span(param.def_id),
|
||||||
|
})
|
||||||
|
});
|
||||||
|
Ty::new_error(tcx, guar).into()
|
||||||
|
}
|
||||||
|
ty::GenericParamDefKind::Const { .. } => {
|
||||||
|
let guar = *emitted_bad_param_err.get_or_insert_with(|| {
|
||||||
|
self.dcx().emit_err(crate::errors::ReturnTypeNotationIllegalParam::Const {
|
||||||
|
span: path_span,
|
||||||
|
param_span: tcx.def_span(param.def_id),
|
||||||
|
})
|
||||||
|
});
|
||||||
|
ty::Const::new_error(tcx, guar).into()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
num_bound_vars += 1;
|
||||||
|
arg
|
||||||
|
});
|
||||||
|
|
||||||
|
// Next, we need to check that the return-type notation is being used on
|
||||||
|
// an RPITIT (return-position impl trait in trait) or AFIT (async fn in trait).
|
||||||
|
let output = tcx.fn_sig(item_def_id).skip_binder().output();
|
||||||
|
let output = if let ty::Alias(ty::Projection, alias_ty) = *output.skip_binder().kind()
|
||||||
|
&& tcx.is_impl_trait_in_trait(alias_ty.def_id)
|
||||||
|
{
|
||||||
|
alias_ty
|
||||||
|
} else {
|
||||||
|
return Err(self.dcx().emit_err(crate::errors::ReturnTypeNotationOnNonRpitit {
|
||||||
|
span: path_span,
|
||||||
|
ty: tcx.liberate_late_bound_regions(item_def_id, output),
|
||||||
|
fn_span: tcx.hir().span_if_local(item_def_id),
|
||||||
|
note: (),
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Finally, move the fn return type's bound vars over to account for the early bound
|
||||||
|
// params (and trait ref's late bound params). This logic is very similar to
|
||||||
|
// `rustc_middle::ty::predicate::Clause::instantiate_supertrait`
|
||||||
|
// and it's no coincidence why.
|
||||||
|
let shifted_output = tcx.shift_bound_var_indices(num_bound_vars, output);
|
||||||
|
Ok(ty::EarlyBinder::bind(shifted_output).instantiate(tcx, args))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Detect and reject early-bound & escaping late-bound generic params in the type of assoc const bindings.
|
/// Detect and reject early-bound & escaping late-bound generic params in the type of assoc const bindings.
|
||||||
|
@ -820,10 +820,11 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
|
|||||||
///
|
///
|
||||||
/// `ty_param_def_id` is the `LocalDefId` of the type parameter.
|
/// `ty_param_def_id` is the `LocalDefId` of the type parameter.
|
||||||
#[instrument(level = "debug", skip_all, ret)]
|
#[instrument(level = "debug", skip_all, ret)]
|
||||||
fn probe_single_ty_param_bound_for_assoc_ty(
|
fn probe_single_ty_param_bound_for_assoc_item(
|
||||||
&self,
|
&self,
|
||||||
ty_param_def_id: LocalDefId,
|
ty_param_def_id: LocalDefId,
|
||||||
ty_param_span: Span,
|
ty_param_span: Span,
|
||||||
|
kind: ty::AssocKind,
|
||||||
assoc_name: Ident,
|
assoc_name: Ident,
|
||||||
span: Span,
|
span: Span,
|
||||||
) -> Result<ty::PolyTraitRef<'tcx>, ErrorGuaranteed> {
|
) -> Result<ty::PolyTraitRef<'tcx>, ErrorGuaranteed> {
|
||||||
@ -841,7 +842,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
|
|||||||
traits::transitive_bounds_that_define_assoc_item(tcx, trait_refs, assoc_name)
|
traits::transitive_bounds_that_define_assoc_item(tcx, trait_refs, assoc_name)
|
||||||
},
|
},
|
||||||
AssocItemQSelf::TyParam(ty_param_def_id, ty_param_span),
|
AssocItemQSelf::TyParam(ty_param_def_id, ty_param_span),
|
||||||
ty::AssocKind::Type,
|
kind,
|
||||||
assoc_name,
|
assoc_name,
|
||||||
span,
|
span,
|
||||||
None,
|
None,
|
||||||
@ -1081,9 +1082,10 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
|
|||||||
(
|
(
|
||||||
&ty::Param(_),
|
&ty::Param(_),
|
||||||
Res::SelfTyParam { trait_: param_did } | Res::Def(DefKind::TyParam, param_did),
|
Res::SelfTyParam { trait_: param_did } | Res::Def(DefKind::TyParam, param_did),
|
||||||
) => self.probe_single_ty_param_bound_for_assoc_ty(
|
) => self.probe_single_ty_param_bound_for_assoc_item(
|
||||||
param_did.expect_local(),
|
param_did.expect_local(),
|
||||||
qself.span,
|
qself.span,
|
||||||
|
ty::AssocKind::Type,
|
||||||
assoc_ident,
|
assoc_ident,
|
||||||
span,
|
span,
|
||||||
)?,
|
)?,
|
||||||
@ -1545,48 +1547,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
|
|||||||
debug!(?trait_def_id);
|
debug!(?trait_def_id);
|
||||||
|
|
||||||
let Some(self_ty) = opt_self_ty else {
|
let Some(self_ty) = opt_self_ty else {
|
||||||
let path_str = tcx.def_path_str(trait_def_id);
|
return self.error_missing_qpath_self_ty(trait_def_id, span, item_segment);
|
||||||
|
|
||||||
let def_id = self.item_def_id();
|
|
||||||
debug!(item_def_id = ?def_id);
|
|
||||||
|
|
||||||
// FIXME: document why/how this is different from `tcx.local_parent(def_id)`
|
|
||||||
let parent_def_id =
|
|
||||||
tcx.hir().get_parent_item(tcx.local_def_id_to_hir_id(def_id)).to_def_id();
|
|
||||||
debug!(?parent_def_id);
|
|
||||||
|
|
||||||
// If the trait in segment is the same as the trait defining the item,
|
|
||||||
// use the `<Self as ..>` syntax in the error.
|
|
||||||
let is_part_of_self_trait_constraints = def_id.to_def_id() == trait_def_id;
|
|
||||||
let is_part_of_fn_in_self_trait = parent_def_id == trait_def_id;
|
|
||||||
|
|
||||||
let type_names = if is_part_of_self_trait_constraints || is_part_of_fn_in_self_trait {
|
|
||||||
vec!["Self".to_string()]
|
|
||||||
} else {
|
|
||||||
// Find all the types that have an `impl` for the trait.
|
|
||||||
tcx.all_impls(trait_def_id)
|
|
||||||
.filter_map(|impl_def_id| tcx.impl_trait_header(impl_def_id))
|
|
||||||
.filter(|header| {
|
|
||||||
// Consider only accessible traits
|
|
||||||
tcx.visibility(trait_def_id).is_accessible_from(self.item_def_id(), tcx)
|
|
||||||
&& header.polarity != ty::ImplPolarity::Negative
|
|
||||||
})
|
|
||||||
.map(|header| header.trait_ref.instantiate_identity().self_ty())
|
|
||||||
// We don't care about blanket impls.
|
|
||||||
.filter(|self_ty| !self_ty.has_non_region_param())
|
|
||||||
.map(|self_ty| tcx.erase_regions(self_ty).to_string())
|
|
||||||
.collect()
|
|
||||||
};
|
|
||||||
// FIXME: also look at `tcx.generics_of(self.item_def_id()).params` any that
|
|
||||||
// references the trait. Relevant for the first case in
|
|
||||||
// `src/test/ui/associated-types/associated-types-in-ambiguous-context.rs`
|
|
||||||
let reported = self.report_ambiguous_assoc_ty(
|
|
||||||
span,
|
|
||||||
&type_names,
|
|
||||||
&[path_str],
|
|
||||||
item_segment.ident.name,
|
|
||||||
);
|
|
||||||
return Ty::new_error(tcx, reported);
|
|
||||||
};
|
};
|
||||||
debug!(?self_ty);
|
debug!(?self_ty);
|
||||||
|
|
||||||
@ -1600,6 +1561,53 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
|
|||||||
Ty::new_projection_from_args(tcx, item_def_id, item_args)
|
Ty::new_projection_from_args(tcx, item_def_id, item_args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn error_missing_qpath_self_ty(
|
||||||
|
&self,
|
||||||
|
trait_def_id: DefId,
|
||||||
|
span: Span,
|
||||||
|
item_segment: &hir::PathSegment<'tcx>,
|
||||||
|
) -> Ty<'tcx> {
|
||||||
|
let tcx = self.tcx();
|
||||||
|
let path_str = tcx.def_path_str(trait_def_id);
|
||||||
|
|
||||||
|
let def_id = self.item_def_id();
|
||||||
|
debug!(item_def_id = ?def_id);
|
||||||
|
|
||||||
|
// FIXME: document why/how this is different from `tcx.local_parent(def_id)`
|
||||||
|
let parent_def_id =
|
||||||
|
tcx.hir().get_parent_item(tcx.local_def_id_to_hir_id(def_id)).to_def_id();
|
||||||
|
debug!(?parent_def_id);
|
||||||
|
|
||||||
|
// If the trait in segment is the same as the trait defining the item,
|
||||||
|
// use the `<Self as ..>` syntax in the error.
|
||||||
|
let is_part_of_self_trait_constraints = def_id.to_def_id() == trait_def_id;
|
||||||
|
let is_part_of_fn_in_self_trait = parent_def_id == trait_def_id;
|
||||||
|
|
||||||
|
let type_names = if is_part_of_self_trait_constraints || is_part_of_fn_in_self_trait {
|
||||||
|
vec!["Self".to_string()]
|
||||||
|
} else {
|
||||||
|
// Find all the types that have an `impl` for the trait.
|
||||||
|
tcx.all_impls(trait_def_id)
|
||||||
|
.filter_map(|impl_def_id| tcx.impl_trait_header(impl_def_id))
|
||||||
|
.filter(|header| {
|
||||||
|
// Consider only accessible traits
|
||||||
|
tcx.visibility(trait_def_id).is_accessible_from(self.item_def_id(), tcx)
|
||||||
|
&& header.polarity != ty::ImplPolarity::Negative
|
||||||
|
})
|
||||||
|
.map(|header| header.trait_ref.instantiate_identity().self_ty())
|
||||||
|
// We don't care about blanket impls.
|
||||||
|
.filter(|self_ty| !self_ty.has_non_region_param())
|
||||||
|
.map(|self_ty| tcx.erase_regions(self_ty).to_string())
|
||||||
|
.collect()
|
||||||
|
};
|
||||||
|
// FIXME: also look at `tcx.generics_of(self.item_def_id()).params` any that
|
||||||
|
// references the trait. Relevant for the first case in
|
||||||
|
// `src/test/ui/associated-types/associated-types-in-ambiguous-context.rs`
|
||||||
|
let reported =
|
||||||
|
self.report_ambiguous_assoc_ty(span, &type_names, &[path_str], item_segment.ident.name);
|
||||||
|
Ty::new_error(tcx, reported)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn prohibit_generic_args<'a>(
|
pub fn prohibit_generic_args<'a>(
|
||||||
&self,
|
&self,
|
||||||
segments: impl Iterator<Item = &'a hir::PathSegment<'a>> + Clone,
|
segments: impl Iterator<Item = &'a hir::PathSegment<'a>> + Clone,
|
||||||
@ -1910,19 +1918,6 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
|
|||||||
ty::BoundConstness::NotConst,
|
ty::BoundConstness::NotConst,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// Deny any qpath types that were successfully lowered in AST lowering.
|
|
||||||
Res::Def(DefKind::AssocFn, _)
|
|
||||||
if let [.., _trait_segment, item_segment] = &path.segments[..]
|
|
||||||
&& item_segment.args.is_some_and(|args| {
|
|
||||||
matches!(
|
|
||||||
args.parenthesized,
|
|
||||||
hir::GenericArgsParentheses::ReturnTypeNotation
|
|
||||||
)
|
|
||||||
}) =>
|
|
||||||
{
|
|
||||||
let guar = self.dcx().emit_err(BadReturnTypeNotation { span: path.span });
|
|
||||||
Ty::new_error(tcx, guar)
|
|
||||||
}
|
|
||||||
Res::PrimTy(prim_ty) => {
|
Res::PrimTy(prim_ty) => {
|
||||||
assert_eq!(opt_self_ty, None);
|
assert_eq!(opt_self_ty, None);
|
||||||
let _ = self.prohibit_generic_args(
|
let _ = self.prohibit_generic_args(
|
||||||
@ -1943,7 +1938,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
|
|||||||
.tcx()
|
.tcx()
|
||||||
.dcx()
|
.dcx()
|
||||||
.span_delayed_bug(path.span, "path with `Res::Err` but no error emitted");
|
.span_delayed_bug(path.span, "path with `Res::Err` but no error emitted");
|
||||||
Ty::new_error(self.tcx(), e)
|
Ty::new_error(tcx, e)
|
||||||
}
|
}
|
||||||
Res::Def(..) => {
|
Res::Def(..) => {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -2098,6 +2093,15 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
|
|||||||
ref i => bug!("`impl Trait` pointed to non-opaque type?? {:#?}", i),
|
ref i => bug!("`impl Trait` pointed to non-opaque type?? {:#?}", i),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// TODO:
|
||||||
|
hir::TyKind::Path(hir::QPath::TypeRelative(_, segment))
|
||||||
|
if segment.args.is_some_and(|args| {
|
||||||
|
matches!(args.parenthesized, hir::GenericArgsParentheses::ReturnTypeNotation)
|
||||||
|
}) =>
|
||||||
|
{
|
||||||
|
let guar = self.dcx().emit_err(BadReturnTypeNotation { span: hir_ty.span });
|
||||||
|
Ty::new_error(tcx, guar)
|
||||||
|
}
|
||||||
hir::TyKind::Path(hir::QPath::TypeRelative(qself, segment)) => {
|
hir::TyKind::Path(hir::QPath::TypeRelative(qself, segment)) => {
|
||||||
debug!(?qself, ?segment);
|
debug!(?qself, ?segment);
|
||||||
let ty = self.lower_ty(qself);
|
let ty = self.lower_ty(qself);
|
||||||
|
@ -790,6 +790,7 @@ impl<'ra: 'ast, 'ast, 'tcx> Visitor<'ast> for LateResolutionVisitor<'_, 'ast, 'r
|
|||||||
TyKind::Path(qself, path) => {
|
TyKind::Path(qself, path) => {
|
||||||
self.diag_metadata.current_type_path = Some(ty);
|
self.diag_metadata.current_type_path = Some(ty);
|
||||||
|
|
||||||
|
// TODO:
|
||||||
let source = if let Some(seg) = path.segments.last()
|
let source = if let Some(seg) = path.segments.last()
|
||||||
&& let Some(args) = &seg.args
|
&& let Some(args) = &seg.args
|
||||||
&& matches!(**args, GenericArgs::ParenthesizedElided(..))
|
&& matches!(**args, GenericArgs::ParenthesizedElided(..))
|
||||||
|
@ -10,14 +10,12 @@ trait Tr {
|
|||||||
fn foo<T: Tr>()
|
fn foo<T: Tr>()
|
||||||
where
|
where
|
||||||
T::method(..): Send,
|
T::method(..): Send,
|
||||||
//~^ ERROR expected type, found function
|
|
||||||
<T as Tr>::method(..): Send,
|
<T as Tr>::method(..): Send,
|
||||||
//~^ ERROR return type notation not allowed in this position yet
|
|
||||||
{
|
{
|
||||||
let _ = T::CONST::(..);
|
let _ = T::CONST::(..);
|
||||||
//~^ ERROR return type notation not allowed in this position yet
|
//~^ ERROR return type notation not allowed in this position yet
|
||||||
let _: T::method(..);
|
let _: T::method(..);
|
||||||
//~^ ERROR expected type, found function
|
//~^ ERROR return type notation not allowed in this position yet
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {}
|
fn main() {}
|
||||||
|
@ -8,40 +8,16 @@ LL | #![feature(return_type_notation)]
|
|||||||
= note: `#[warn(incomplete_features)]` on by default
|
= note: `#[warn(incomplete_features)]` on by default
|
||||||
|
|
||||||
error: return type notation not allowed in this position yet
|
error: return type notation not allowed in this position yet
|
||||||
--> $DIR/bare-path.rs:17:23
|
--> $DIR/bare-path.rs:15:23
|
||||||
|
|
|
|
||||||
LL | let _ = T::CONST::(..);
|
LL | let _ = T::CONST::(..);
|
||||||
| ^^^^
|
| ^^^^
|
||||||
|
|
||||||
error: expected type, found function
|
|
||||||
--> $DIR/bare-path.rs:12:8
|
|
||||||
|
|
|
||||||
LL | T::method(..): Send,
|
|
||||||
| ^^^^^^ unexpected function
|
|
||||||
|
|
|
||||||
note: the associated function is defined here
|
|
||||||
--> $DIR/bare-path.rs:7:5
|
|
||||||
|
|
|
||||||
LL | fn method() -> impl Sized;
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
error: return type notation not allowed in this position yet
|
error: return type notation not allowed in this position yet
|
||||||
--> $DIR/bare-path.rs:14:5
|
--> $DIR/bare-path.rs:17:12
|
||||||
|
|
|
||||||
LL | <T as Tr>::method(..): Send,
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
error: expected type, found function
|
|
||||||
--> $DIR/bare-path.rs:19:15
|
|
||||||
|
|
|
|
||||||
LL | let _: T::method(..);
|
LL | let _: T::method(..);
|
||||||
| ^^^^^^ unexpected function
|
| ^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
note: the associated function is defined here
|
|
||||||
--> $DIR/bare-path.rs:7:5
|
|
||||||
|
|
|
||||||
LL | fn method() -> impl Sized;
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
error: aborting due to 4 previous errors; 1 warning emitted
|
error: aborting due to 2 previous errors; 1 warning emitted
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ pub fn foo(num: i32) -> i32 {
|
|||||||
let foo: i32::from_be(num);
|
let foo: i32::from_be(num);
|
||||||
//~^ ERROR expected type, found local variable `num`
|
//~^ ERROR expected type, found local variable `num`
|
||||||
//~| ERROR argument types not allowed with return type notation
|
//~| ERROR argument types not allowed with return type notation
|
||||||
//~| ERROR ambiguous associated type
|
//~| ERROR return type notation not allowed in this position yet
|
||||||
foo
|
foo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,18 +16,12 @@ LL | let foo: i32::from_be(num);
|
|||||||
= help: add `#![feature(return_type_notation)]` to the crate attributes to enable
|
= help: add `#![feature(return_type_notation)]` to the crate attributes to enable
|
||||||
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
|
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
|
||||||
|
|
||||||
error[E0223]: ambiguous associated type
|
error: return type notation not allowed in this position yet
|
||||||
--> $DIR/let-binding-init-expr-as-ty.rs:2:14
|
--> $DIR/let-binding-init-expr-as-ty.rs:2:14
|
||||||
|
|
|
|
||||||
LL | let foo: i32::from_be(num);
|
LL | let foo: i32::from_be(num);
|
||||||
| ^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
help: if there were a trait named `Example` with associated type `from_be` implemented for `i32`, you could use the fully-qualified path
|
|
||||||
|
|
|
||||||
LL | let foo: <i32 as Example>::from_be;
|
|
||||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
error: aborting due to 3 previous errors
|
error: aborting due to 3 previous errors
|
||||||
|
|
||||||
Some errors have detailed explanations: E0223, E0573.
|
For more information about this error, try `rustc --explain E0573`.
|
||||||
For more information about an error, try `rustc --explain E0223`.
|
|
||||||
|
Loading…
Reference in New Issue
Block a user