//! "Collection" is the process of determining the type and other external
//! details of each item in Rust. Collection is specifically concerned
-//! with *interprocedural* things -- for example, for a function
+//! with *inter-procedural* things -- for example, for a function
//! definition, collection will figure out the type and signature of the
//! function, but it will not visit the *body* of the function in any way,
//! nor examine type annotations on local variables (that's the job of
//! At present, however, we do run collection across all items in the
//! crate as a kind of pass. This should eventually be factored away.
-use crate::astconv::{AstConv, Bounds};
+use crate::astconv::{AstConv, Bounds, SizedByDefault};
use crate::constrained_generic_params as cgp;
use crate::check::intrinsic::intrisic_operation_unsafety;
use crate::lint;
-use crate::middle::lang_items::SizedTraitLangItem;
use crate::middle::resolve_lifetime as rl;
use crate::middle::weak_lang_items;
use rustc::mir::mono::Linkage;
use rustc::ty::util::Discr;
use rustc::ty::util::IntTypeExt;
use rustc::ty::subst::UnpackedKind;
-use rustc::ty::{self, AdtKind, ToPolyTraitRef, Ty, TyCtxt};
+use rustc::ty::{self, AdtKind, DefIdTree, ToPolyTraitRef, Ty, TyCtxt};
use rustc::ty::{ReprOptions, ToPredicate};
use rustc::util::captures::Captures;
use rustc::util::nodemap::FxHashMap;
}
fn set_tainted_by_errors(&self) {
- // no obvious place to track this, just let it go
+ // no obvious place to track this, so just let it go
}
fn record_ty(&self, _hir_id: hir::HirId, _ty: Ty<'tcx>, _span: Span) {
}
}
- // Desugared from `impl Trait` -> visited by the function's return type
+ // Desugared from `impl Trait`, so visited by the function's return type.
hir::ItemKind::Existential(hir::ExistTy {
impl_trait_fn: Some(_),
..
// Convert the bounds that follow the colon, e.g., `Bar + Zed` in `trait Foo: Bar + Zed`.
let self_param_ty = tcx.mk_self_type();
- let superbounds1 = compute_bounds(&icx, self_param_ty, bounds, SizedByDefault::No, item.span);
+ let superbounds1 = AstConv::compute_bounds(&icx, self_param_ty, bounds, SizedByDefault::No,
+ item.span);
let superbounds1 = superbounds1.predicates(tcx, self_param_ty);
let is_marker = tcx.has_attr(def_id, sym::marker);
let def_path_hash = tcx.def_path_hash(def_id);
let def = ty::TraitDef::new(def_id, unsafety, paren_sugar, is_auto, is_marker, def_path_hash);
- tcx.alloc_trait_def(def)
+ tcx.arena.alloc(def)
}
fn has_late_bound_regions<'a, 'tcx>(
.map(|param| (param.def_id, param.index))
.collect();
- tcx.alloc_generics(ty::Generics {
+ tcx.arena.alloc(ty::Generics {
parent: parent_def_id,
parent_count,
params,
impl_trait_fn: None,
..
}) => find_existential_constraints(tcx, def_id),
- // existential types desugared from impl Trait
+ // Existential types desugared from `impl Trait`.
ItemKind::Existential(hir::ExistTy {
impl_trait_fn: Some(owner),
..
Node::Ty(&hir::Ty { node: hir::TyKind::Path(_), .. }) |
Node::Expr(&hir::Expr { node: ExprKind::Struct(..), .. }) |
- Node::Expr(&hir::Expr { node: ExprKind::Path(_), .. }) => {
+ Node::Expr(&hir::Expr { node: ExprKind::Path(_), .. }) |
+ Node::TraitRef(..) => {
let path = match parent_node {
- Node::Ty(&hir::Ty { node: hir::TyKind::Path(ref path), .. }) |
- Node::Expr(&hir::Expr { node: ExprKind::Path(ref path), .. }) => {
- path
+ Node::Ty(&hir::Ty {
+ node: hir::TyKind::Path(QPath::Resolved(_, ref path)),
+ ..
+ })
+ | Node::Expr(&hir::Expr {
+ node: ExprKind::Path(QPath::Resolved(_, ref path)),
+ ..
+ }) => {
+ Some(&**path)
}
Node::Expr(&hir::Expr { node: ExprKind::Struct(ref path, ..), .. }) => {
- &*path
+ if let QPath::Resolved(_, ref path) = **path {
+ Some(&**path)
+ } else {
+ None
+ }
}
- _ => unreachable!(),
+ Node::TraitRef(&hir::TraitRef { ref path, .. }) => Some(path),
+ _ => None,
};
- match path {
- QPath::Resolved(_, ref path) => {
- let mut arg_index = 0;
- let mut found_const = false;
- for seg in &path.segments {
- if let Some(generic_args) = &seg.args {
- let args = &generic_args.args;
- for arg in args {
- if let GenericArg::Const(ct) = arg {
- if ct.value.hir_id == hir_id {
- found_const = true;
- break;
- }
- arg_index += 1;
- }
- }
- }
- }
- // Sanity check to make sure everything is as expected.
- if !found_const {
+ if let Some(path) = path {
+ let arg_index = path.segments.iter()
+ .filter_map(|seg| seg.args.as_ref())
+ .map(|generic_args| generic_args.args.as_ref())
+ .find_map(|args| {
+ args.iter()
+ .filter(|arg| arg.is_const())
+ .enumerate()
+ .filter(|(_, arg)| arg.id() == hir_id)
+ .map(|(index, _)| index)
+ .next()
+ })
+ .or_else(|| {
if !fail {
- return None;
- }
- bug!("no arg matching AnonConst in path")
- }
- match path.res {
- // We've encountered an `AnonConst` in some path, so we need to
- // figure out which generic parameter it corresponds to and return
- // the relevant type.
- Res::Def(DefKind::Struct, def_id)
- | Res::Def(DefKind::Union, def_id)
- | Res::Def(DefKind::Enum, def_id)
- | Res::Def(DefKind::Fn, def_id) => {
- let generics = tcx.generics_of(def_id);
- let mut param_index = 0;
- for param in &generics.params {
- if let ty::GenericParamDefKind::Const = param.kind {
- if param_index == arg_index {
- return Some(tcx.type_of(param.def_id));
- }
- param_index += 1;
- }
- }
- // This is no generic parameter associated with the arg. This is
- // probably from an extra arg where one is not needed.
- return Some(tcx.types.err);
- }
- Res::Err => tcx.types.err,
- x => {
- if !fail {
- return None;
- }
- tcx.sess.delay_span_bug(
- DUMMY_SP,
- &format!(
- "unexpected const parent path def {:?}", x
- ),
- );
- tcx.types.err
+ None
+ } else {
+ bug!("no arg matching AnonConst in path")
}
+ })?;
+
+ // We've encountered an `AnonConst` in some path, so we need to
+ // figure out which generic parameter it corresponds to and return
+ // the relevant type.
+ let generics = match path.res {
+ Res::Def(DefKind::Ctor(..), def_id) => {
+ tcx.generics_of(tcx.parent(def_id).unwrap())
}
- }
- x => {
- if !fail {
- return None;
+ Res::Def(_, def_id) => tcx.generics_of(def_id),
+ Res::Err => return Some(tcx.types.err),
+ _ if !fail => return None,
+ res => {
+ tcx.sess.delay_span_bug(
+ DUMMY_SP,
+ &format!(
+ "unexpected const parent path def {:?}",
+ res,
+ ),
+ );
+ return Some(tcx.types.err);
}
- tcx.sess.delay_span_bug(
- DUMMY_SP,
- &format!(
- "unexpected const parent path {:?}", x
- ),
- );
- tcx.types.err
+ };
+
+ generics.params.iter()
+ .filter(|param| {
+ if let ty::GenericParamDefKind::Const = param.kind {
+ true
+ } else {
+ false
+ }
+ })
+ .nth(arg_index)
+ .map(|param| tcx.type_of(param.def_id))
+ // This is no generic parameter associated with the arg. This is
+ // probably from an extra arg where one is not needed.
+ .unwrap_or(tcx.types.err)
+ } else {
+ if !fail {
+ return None;
}
+ tcx.sess.delay_span_bug(
+ DUMMY_SP,
+ &format!(
+ "unexpected const parent path {:?}",
+ parent_node,
+ ),
+ );
+ return Some(tcx.types.err);
}
}
) -> Ty<'tcx> {
use rustc::hir::{ImplItem, Item, TraitItem};
+ debug!("find_existential_constraints({:?})", def_id);
+
struct ConstraintLocator<'a, 'tcx: 'a> {
tcx: TyCtxt<'a, 'tcx, 'tcx>,
def_id: DefId,
- // First found type span, actual type, mapping from the existential type's generic
- // parameters to the concrete type's generic parameters
+ // (first found type span, actual type, mapping from the existential type's generic
+ // parameters to the concrete type's generic parameters)
//
// The mapping is an index for each use site of a generic parameter in the concrete type
//
impl<'a, 'tcx> ConstraintLocator<'a, 'tcx> {
fn check(&mut self, def_id: DefId) {
- trace!("checking {:?}", def_id);
- // don't try to check items that cannot possibly constrain the type
+ // Don't try to check items that cannot possibly constrain the type.
if !self.tcx.has_typeck_tables(def_id) {
- trace!("no typeck tables for {:?}", def_id);
+ debug!(
+ "find_existential_constraints: no constraint for `{:?}` at `{:?}`: no tables",
+ self.def_id,
+ def_id,
+ );
return;
}
let ty = self
.concrete_existential_types
.get(&self.def_id);
if let Some(ty::ResolvedOpaqueTy { concrete_type, substs }) = ty {
- // FIXME(oli-obk): trace the actual span from inference to improve errors
+ debug!(
+ "find_existential_constraints: found constraint for `{:?}` at `{:?}`: {:?}",
+ self.def_id,
+ def_id,
+ ty,
+ );
+
+ // FIXME(oli-obk): trace the actual span from inference to improve errors.
let span = self.tcx.def_span(def_id);
// used to quickly look up the position of a generic parameter
let mut index_map: FxHashMap<ty::ParamTy, usize> = FxHashMap::default();
- // skip binder is ok, since we only use this to find generic parameters and their
- // positions.
+ // Skipping binder is ok, since we only use this to find generic parameters and
+ // their positions.
for (idx, subst) in substs.iter().enumerate() {
if let UnpackedKind::Type(ty) = subst.unpack() {
if let ty::Param(p) = ty.sty {
if index_map.insert(p, idx).is_some() {
- // there was already an entry for `p`, meaning a generic parameter
- // was used twice
+ // There was already an entry for `p`, meaning a generic parameter
+ // was used twice.
self.tcx.sess.span_err(
span,
- &format!("defining existential type use restricts existential \
- type by using the generic parameter `{}` twice", p.name),
+ &format!(
+ "defining existential type use restricts existential \
+ type by using the generic parameter `{}` twice",
+ p.name
+ ),
);
return;
}
}
}
}
- // compute the index within the existential type for each generic parameter used in
- // the concrete type
+ // Compute the index within the existential type for each generic parameter used in
+ // the concrete type.
let indices = concrete_type
.subst(self.tcx, substs)
.walk()
let mut ty = concrete_type.walk().fuse();
let mut p_ty = prev_ty.walk().fuse();
let iter_eq = (&mut ty).zip(&mut p_ty).all(|(t, p)| match (&t.sty, &p.sty) {
- // type parameters are equal to any other type parameter for the purpose of
+ // Type parameters are equal to any other type parameter for the purpose of
// concrete type equality, as it is possible to obtain the same type just
// by passing matching parameters to a function.
(ty::Param(_), ty::Param(_)) => true,
_ => t == p,
});
if !iter_eq || ty.next().is_some() || p_ty.next().is_some() {
- // found different concrete types for the existential type
+ debug!("find_existential_constraints: span={:?}", span);
+ // Found different concrete types for the existential type.
let mut err = self.tcx.sess.struct_span_err(
span,
"concrete type differs from previous defining existential type use",
err.span_note(prev_span, "previous use here");
err.emit();
} else if indices != *prev_indices {
- // found "same" concrete types, but the generic parameter order differs
+ // Found "same" concrete types, but the generic parameter order differs.
let mut err = self.tcx.sess.struct_span_err(
span,
"concrete type's generic parameters differ from previous defining use",
} else {
self.found = Some((span, concrete_type, indices));
}
+ } else {
+ debug!(
+ "find_existential_constraints: no constraint for `{:?}` at `{:?}`",
+ self.def_id,
+ def_id,
+ );
}
}
}
}
fn visit_item(&mut self, it: &'tcx Item) {
let def_id = self.tcx.hir().local_def_id_from_hir_id(it.hir_id);
- // the existential type itself or its children are not within its reveal scope
+ // The existential type itself or its children are not within its reveal scope.
if def_id != self.def_id {
self.check(def_id);
intravisit::walk_item(self, it);
}
fn visit_impl_item(&mut self, it: &'tcx ImplItem) {
let def_id = self.tcx.hir().local_def_id_from_hir_id(it.hir_id);
- // the existential type itself or its children are not within its reveal scope
+ // The existential type itself or its children are not within its reveal scope.
if def_id != self.def_id {
self.check(def_id);
intravisit::walk_impl_item(self, it);
}
}
+ let hir_id = tcx.hir().as_local_hir_id(def_id).unwrap();
+ let scope = tcx.hir()
+ .get_defining_scope(hir_id)
+ .expect("could not get defining scope");
let mut locator = ConstraintLocator {
def_id,
tcx,
found: None,
};
- let hir_id = tcx.hir().as_local_hir_id(def_id).unwrap();
- let parent = tcx.hir().get_parent_item(hir_id);
- trace!("parent_id: {:?}", parent);
+ debug!("find_existential_constraints: scope={:?}", scope);
- if parent == hir::CRATE_HIR_ID {
+ if scope == hir::CRATE_HIR_ID {
intravisit::walk_crate(&mut locator, tcx.hir().krate());
} else {
- trace!("parent: {:?}", tcx.hir().get_by_hir_id(parent));
- match tcx.hir().get_by_hir_id(parent) {
+ debug!("find_existential_constraints: scope={:?}", tcx.hir().get_by_hir_id(scope));
+ match tcx.hir().get_by_hir_id(scope) {
Node::Item(ref it) => intravisit::walk_item(&mut locator, it),
Node::ImplItem(ref it) => intravisit::walk_impl_item(&mut locator, it),
Node::TraitItem(ref it) => intravisit::walk_trait_item(&mut locator, it),
other => bug!(
- "{:?} is not a valid parent of an existential type item",
+ "{:?} is not a valid scope for an existential type item",
other
),
}
}
}
-// Is it marked with ?Sized
-fn is_unsized<'gcx: 'tcx, 'tcx>(
- astconv: &dyn AstConv<'gcx, 'tcx>,
- ast_bounds: &[hir::GenericBound],
- span: Span,
-) -> bool {
- let tcx = astconv.tcx();
-
- // Try to find an unbound in bounds.
- let mut unbound = None;
- for ab in ast_bounds {
- if let &hir::GenericBound::Trait(ref ptr, hir::TraitBoundModifier::Maybe) = ab {
- if unbound.is_none() {
- unbound = Some(ptr.trait_ref.clone());
- } else {
- span_err!(
- tcx.sess,
- span,
- E0203,
- "type parameter has more than one relaxed default \
- bound, only one is supported"
- );
- }
- }
- }
-
- let kind_id = tcx.lang_items().require(SizedTraitLangItem);
- match unbound {
- Some(ref tpb) => {
- // FIXME(#8559) currently requires the unbound to be built-in.
- if let Ok(kind_id) = kind_id {
- if tpb.path.res != Res::Def(DefKind::Trait, kind_id) {
- tcx.sess.span_warn(
- span,
- "default bound relaxed for a type parameter, but \
- this does nothing because the given bound is not \
- a default. Only `?Sized` is supported",
- );
- }
- }
- }
- _ if kind_id.is_ok() => {
- return false;
- }
- // No lang item for Sized, so we can't add it as a bound.
- None => {}
- }
-
- true
-}
-
/// Returns the early-bound lifetimes declared in this generics
/// listing. For anything other than fns/methods, this is just all
/// the lifetimes that are declared. For fns or methods, we have to
let substs = InternalSubsts::identity_for_item(tcx, def_id);
let opaque_ty = tcx.mk_opaque(def_id, substs);
- // Collect the bounds, i.e., the `A+B+'c` in `impl A+B+'c`.
- let bounds = compute_bounds(
+ // Collect the bounds, i.e., the `A + B + 'c` in `impl A + B + 'c`.
+ let bounds = AstConv::compute_bounds(
&icx,
opaque_ty,
bounds,
let substs = InternalSubsts::identity_for_item(tcx, def_id);
let opaque_ty = tcx.mk_opaque(def_id, substs);
- // Collect the bounds, i.e., the `A+B+'c` in `impl A+B+'c`.
- let bounds = compute_bounds(
+ // Collect the bounds, i.e., the `A + B + 'c` in `impl A + B + 'c`.
+ let bounds = AstConv::compute_bounds(
&icx,
opaque_ty,
bounds,
tcx.def_span(def_id),
);
+ let bounds_predicates = bounds.predicates(tcx, opaque_ty);
if impl_trait_fn.is_some() {
- // impl Trait
+ // opaque types
return tcx.arena.alloc(ty::GenericPredicates {
parent: None,
- predicates: bounds.predicates(tcx, opaque_ty),
+ predicates: bounds_predicates,
});
} else {
// named existential types
- predicates.extend(bounds.predicates(tcx, opaque_ty));
+ predicates.extend(bounds_predicates);
generics
}
}
}
// Collect the predicates that were written inline by the user on each
- // type parameter (e.g., `<T:Foo>`).
+ // type parameter (e.g., `<T: Foo>`).
for param in &ast_generics.params {
if let GenericParamKind::Type { .. } = param.kind {
let name = param.name.ident().as_interned_str();
index += 1;
let sized = SizedByDefault::Yes;
- let bounds = compute_bounds(&icx, param_ty, ¶m.bounds, sized, param.span);
+ let bounds = AstConv::compute_bounds(&icx, param_ty, ¶m.bounds, sized, param.span);
predicates.extend(bounds.predicates(tcx, param_ty));
}
}
- // Add in the bounds that appear in the where-clause
+ // Add in the bounds that appear in the where-clause.
let where_clause = &ast_generics.where_clause;
for predicate in &where_clause.predicates {
match predicate {
for bound in bound_pred.bounds.iter() {
match bound {
&hir::GenericBound::Trait(ref poly_trait_ref, _) => {
- let mut projections = Vec::new();
+ let mut bounds = Bounds::default();
let (trait_ref, _) = AstConv::instantiate_poly_trait_ref(
&icx,
poly_trait_ref,
ty,
- &mut projections,
+ &mut bounds,
);
- predicates.extend(
- iter::once((trait_ref.to_predicate(), poly_trait_ref.span)).chain(
- projections.iter().map(|&(p, span)| (p.to_predicate(), span)
- )));
+ predicates.push((trait_ref.to_predicate(), poly_trait_ref.span));
+ predicates.extend(bounds.predicates(tcx, ty));
}
&hir::GenericBound::Outlives(ref lifetime) => {
let trait_item = tcx.hir().trait_item(trait_item_ref.id);
let bounds = match trait_item.node {
hir::TraitItemKind::Type(ref bounds, _) => bounds,
- _ => return vec![].into_iter()
+ _ => return Vec::new().into_iter()
};
let assoc_ty =
tcx.mk_projection(tcx.hir().local_def_id_from_hir_id(trait_item.hir_id),
self_trait_ref.substs);
- let bounds = compute_bounds(
+ let bounds = AstConv::compute_bounds(
&ItemCtxt::new(tcx, def_id),
assoc_ty,
bounds,
result
}
-pub enum SizedByDefault {
- Yes,
- No,
-}
-
-/// Translate the AST's notion of ty param bounds (which are an enum consisting of a newtyped `Ty`
-/// or a region) to ty's notion of ty param bounds, which can either be user-defined traits, or the
-/// built-in trait `Send`.
-pub fn compute_bounds<'gcx: 'tcx, 'tcx>(
- astconv: &dyn AstConv<'gcx, 'tcx>,
- param_ty: Ty<'tcx>,
- ast_bounds: &[hir::GenericBound],
- sized_by_default: SizedByDefault,
- span: Span,
-) -> Bounds<'tcx> {
- let mut region_bounds = Vec::new();
- let mut trait_bounds = Vec::new();
-
- for ast_bound in ast_bounds {
- match *ast_bound {
- hir::GenericBound::Trait(ref b, hir::TraitBoundModifier::None) => trait_bounds.push(b),
- hir::GenericBound::Trait(_, hir::TraitBoundModifier::Maybe) => {}
- hir::GenericBound::Outlives(ref l) => region_bounds.push(l),
- }
- }
-
- let mut projection_bounds = Vec::new();
-
- let mut trait_bounds: Vec<_> = trait_bounds.iter().map(|&bound| {
- let (poly_trait_ref, _) = astconv.instantiate_poly_trait_ref(
- bound,
- param_ty,
- &mut projection_bounds,
- );
- (poly_trait_ref, bound.span)
- }).collect();
-
- let region_bounds = region_bounds
- .into_iter()
- .map(|r| (astconv.ast_region_to_region(r, None), r.span))
- .collect();
-
- trait_bounds.sort_by_key(|(t, _)| t.def_id());
-
- let implicitly_sized = if let SizedByDefault::Yes = sized_by_default {
- if !is_unsized(astconv, ast_bounds, span) {
- Some(span)
- } else {
- None
- }
- } else {
- None
- };
-
- Bounds {
- region_bounds,
- implicitly_sized,
- trait_bounds,
- projection_bounds,
- }
-}
-
/// Converts a specific `GenericBound` from the AST into a set of
/// predicates that apply to the self type. A vector is returned
/// because this can be anywhere from zero predicates (`T: ?Sized` adds no
) -> Vec<(ty::Predicate<'tcx>, Span)> {
match *bound {
hir::GenericBound::Trait(ref tr, hir::TraitBoundModifier::None) => {
- let mut projections = Vec::new();
- let (pred, _) = astconv.instantiate_poly_trait_ref(tr, param_ty, &mut projections);
- iter::once((pred.to_predicate(), tr.span)).chain(
- projections
- .into_iter()
- .map(|(p, span)| (p.to_predicate(), span))
- ).collect()
+ let mut bounds = Bounds::default();
+ let (pred, _) = astconv.instantiate_poly_trait_ref(tr, param_ty, &mut bounds);
+ iter::once((pred.to_predicate(), tr.span))
+ .chain(bounds.predicates(astconv.tcx(), param_ty))
+ .collect()
}
hir::GenericBound::Outlives(ref lifetime) => {
let region = astconv.ast_region_to_region(lifetime, None);
};
let fty = AstConv::ty_of_fn(&ItemCtxt::new(tcx, def_id), unsafety, abi, decl);
- // feature gate SIMD types in FFI, since I (huonw) am not sure the
- // ABIs are handled at all correctly.
+ // Feature gate SIMD types in FFI, since I am not sure that the
+ // ABIs are handled at all correctly. -huonw
if abi != abi::Abi::RustIntrinsic
&& abi != abi::Abi::PlatformIntrinsic
&& !tcx.features().simd_ffi
};
let rust_features = tcx.features();
for item in list {
- // Only `enable = ...` is accepted in the meta item list
+ // Only `enable = ...` is accepted in the meta-item list.
if !item.check_name(sym::enable) {
bad_item(item.span());
continue;
}
- // Must be of the form `enable = "..."` ( a string)
+ // Must be of the form `enable = "..."` (a string).
let value = match item.value_str() {
Some(value) => value,
None => {
}
};
- // We allow comma separation to enable multiple features
+ // We allow comma separation to enable multiple features.
target_features.extend(value.as_str().split(',').filter_map(|feature| {
- // Only allow whitelisted features per platform
+ // Only allow whitelisted features per platform.
let feature_gate = match whitelist.get(feature) {
Some(g) => g,
None => {
}
};
- // Only allow features whose feature gates have been enabled
+ // Only allow features whose feature gates have been enabled.
let allowed = match feature_gate.as_ref().map(|s| *s) {
Some(sym::arm_target_feature) => rust_features.arm_target_feature,
Some(sym::aarch64_target_feature) => rust_features.aarch64_target_feature,
for attr in attrs.iter() {
if attr.check_name(sym::cold) {
codegen_fn_attrs.flags |= CodegenFnAttrFlags::COLD;
- } else if attr.check_name(sym::allocator) {
+ } else if attr.check_name(sym::rustc_allocator) {
codegen_fn_attrs.flags |= CodegenFnAttrFlags::ALLOCATOR;
} else if attr.check_name(sym::unwind) {
codegen_fn_attrs.flags |= CodegenFnAttrFlags::UNWIND;
if tcx.is_foreign_item(id) {
codegen_fn_attrs.flags |= CodegenFnAttrFlags::FFI_RETURNS_TWICE;
} else {
- // `#[ffi_returns_twice]` is only allowed `extern fn`s
+ // `#[ffi_returns_twice]` is only allowed `extern fn`s.
struct_span_err!(
tcx.sess,
attr.span,