diff --git a/crates/paralegal-flow/src/ann/parse.rs b/crates/paralegal-flow/src/ann/parse.rs index d267b8829a..3c6a074ec7 100644 --- a/crates/paralegal-flow/src/ann/parse.rs +++ b/crates/paralegal-flow/src/ann/parse.rs @@ -12,15 +12,15 @@ use super::{ ExceptionAnnotation, MarkerAnnotation, MarkerRefinement, MarkerRefinementKind, VerificationHash, }; use crate::{ - utils, - utils::{write_sep, Print, TinyBitSet}, + utils::{resolve::def_path_res, TinyBitSet}, Symbol, }; use paralegal_spdg::Identifier; -use rustc_ast::{self as ast, token, tokenstream}; +use rustc_ast::{self as ast, token, tokenstream, ExprKind}; use rustc_hir::def_id::DefId; use rustc_middle::ty::TyCtxt; +use rustc_parse::parser as rustc_parser; use token::*; use tokenstream::*; @@ -240,32 +240,30 @@ pub fn tiny_bitset(i: I) -> R { pub(crate) fn otype_ann_match(ann: &ast::AttrArgs, tcx: TyCtxt) -> Result, String> { match ann { ast::AttrArgs::Delimited(dargs) => { - let mut p = nom::multi::separated_list0( - assert_token(TokenKind::Comma), - nom::multi::separated_list0( - assert_token(TokenKind::ModSep), - nom::combinator::map(identifier, |i| i.to_string()), - ), - ); - p(I::from_stream(&dargs.tokens)) - .map_err(|err: nom::Err<_>| format!("parser failed with error {err:?}"))? - .1 - .into_iter() - .map(|strs| { - let segment_vec = strs.iter().map(AsRef::as_ref).collect::>(); - Ok(utils::resolve::def_path_res(tcx, &segment_vec) - .map_err(|err| { - format!( - "Could not resolve {}: {err:?}", - Print(|f| write_sep(f, "::", &segment_vec, |elem, f| f - .write_str(elem))) - ) - })? - .def_id()) - }) - .collect() + let mut parser = + rustc_parser::Parser::new(&tcx.sess.parse_sess, dargs.tokens.clone(), None); + std::iter::from_fn(|| { + if parser.token.kind == TokenKind::Eof { + return None; + } + let ExprKind::Path(qself, path) = &parser.parse_expr().ok()?.kind else { + return Some(Result::Err(format!( + "Expected path expression, got {:?}", + dargs.tokens + ))); + }; + if parser.token.kind != TokenKind::Eof { + parser.expect(&TokenKind::Comma).ok()?; + } + Some( + def_path_res(tcx, qself.as_deref(), &path.segments) + .map_err(|err| format!("Failed resolution: {err:?}",)) + .map(|d| d.def_id()), + ) + }) + .collect() } - _ => Result::Err("Expected delimoted annotation".to_owned()), + _ => Result::Err("Expected delimited annotation".to_owned()), } } diff --git a/crates/paralegal-flow/src/discover.rs b/crates/paralegal-flow/src/discover.rs index a64015166c..72e121de31 100644 --- a/crates/paralegal-flow/src/discover.rs +++ b/crates/paralegal-flow/src/discover.rs @@ -77,7 +77,7 @@ impl<'tcx> CollectingVisitor<'tcx> { .filter_map(|path| { let def_id = expect_resolve_string_to_def_id(tcx, path, opts.relaxed())?; if !def_id.is_local() { - tcx.sess.span_err(tcx.def_span(def_id), "found an external function as analysis target. Analysis targets are required to be local."); + tcx.sess.span_err(tcx.def_span(def_id), format!("found an external function {def_id:?} as analysis target. Analysis targets are required to be local.")); return None; } Some(FnToAnalyze { diff --git a/crates/paralegal-flow/src/lib.rs b/crates/paralegal-flow/src/lib.rs index 5b97ee5689..9b017d9607 100644 --- a/crates/paralegal-flow/src/lib.rs +++ b/crates/paralegal-flow/src/lib.rs @@ -36,6 +36,7 @@ extern crate rustc_interface; extern crate rustc_macros; extern crate rustc_middle; extern crate rustc_mir_dataflow; +extern crate rustc_parse; extern crate rustc_query_system; extern crate rustc_serialize; extern crate rustc_session; diff --git a/crates/paralegal-flow/src/test_utils.rs b/crates/paralegal-flow/src/test_utils.rs index 9fd8a6e6d7..91ce1b4e57 100644 --- a/crates/paralegal-flow/src/test_utils.rs +++ b/crates/paralegal-flow/src/test_utils.rs @@ -11,6 +11,7 @@ use rustc_interface::interface; use crate::{ ann::dump_markers, desc::{Identifier, ProgramDescription}, + utils::Print, HashSet, EXTRA_RUSTC_ARGS, }; use std::hash::{Hash, Hasher}; @@ -22,6 +23,7 @@ use std::{ use paralegal_spdg::{ traverse::{generic_flows_to, EdgeSelection}, + utils::write_sep, DefInfo, EdgeInfo, Endpoint, Node, TypeId, SPDG, }; @@ -202,7 +204,7 @@ impl InlineTestBuilder { pub fn new(input: impl Into) -> Self { Self { input: input.into(), - ctrl_name: "main".into(), + ctrl_name: "crate::main".into(), } } @@ -240,12 +242,8 @@ impl InlineTestBuilder { } let args = crate::Args::try_from( - TopLevelArgs::parse_from([ - "".into(), - "--analyze".into(), - format!("crate::{}", self.ctrl_name), - ]) - .args, + TopLevelArgs::parse_from(["".into(), "--analyze".into(), self.ctrl_name.to_string()]) + .args, ) .unwrap(); @@ -321,6 +319,7 @@ pub trait HasGraph<'g>: Sized + Copy { } fn ctrl_hashed(self, name: &str) -> Endpoint { + let name = name.strip_prefix("crate::").unwrap_or(name); let candidates = self .graph() .desc @@ -330,7 +329,14 @@ pub trait HasGraph<'g>: Sized + Copy { .map(|(id, _)| *id) .collect::>(); match candidates.as_slice() { - [] => panic!("Could not find controller '{name}'"), + [] => panic!( + "Could not find controller '{name}'. Known controllers are {}", + Print(|fmt| { + write_sep(fmt, ", ", self.graph().desc.controllers.values(), |c, f| { + f.write_str(c.name.as_str()) + }) + }) + ), [ctrl] => *ctrl, more => panic!("Too many matching controllers, found candidates: {more:?}"), } diff --git a/crates/paralegal-flow/src/utils/resolve.rs b/crates/paralegal-flow/src/utils/resolve.rs index da1ad56f01..5b15f46fb8 100644 --- a/crates/paralegal-flow/src/utils/resolve.rs +++ b/crates/paralegal-flow/src/utils/resolve.rs @@ -1,3 +1,5 @@ +use std::hash::Hash; + use ast::Mutability; use hir::{ def::{self, DefKind}, @@ -6,9 +8,11 @@ use hir::{ def_id::LOCAL_CRATE, ImplItemRef, ItemKind, Node, PrimTy, TraitItemRef, }; -use rustc_ast as ast; +use rustc_ast::{self as ast, token::TokenKind, ExprKind, PathSegment, QSelf, Ty, TyKind}; +use rustc_data_structures::stable_hasher::StableHasher; use rustc_hir::{self as hir, def_id::DefId}; -use rustc_middle::ty::{fast_reject::SimplifiedType, FloatTy, IntTy, TyCtxt, UintTy}; +use rustc_middle::ty::{self, fast_reject::SimplifiedType, FloatTy, IntTy, TyCtxt, UintTy}; +use rustc_parse::new_parser_from_source_str; use rustc_span::Symbol; #[derive(Debug, Clone, Copy)] @@ -18,19 +22,22 @@ pub enum Res { } #[derive(Clone, Debug)] -pub enum ResolutionError<'a> { +pub enum ResolutionError { CannotResolvePrimitiveType(Symbol), PathIsEmpty, CouldNotFindChild { item: DefId, - segment: &'a str, + segment: Symbol, search_space: SearchSpace, }, EmptyStarts, UnconvertibleRes(def::Res), - CouldNotResolveCrate(&'a str), + CouldNotResolveCrate(Symbol), + UnsupportedType(Ty), } +pub type Result = std::result::Result; + #[derive(Clone, Debug)] pub enum SearchSpace { InherentImpl, @@ -38,7 +45,7 @@ pub enum SearchSpace { } impl Res { - fn from_def_res<'a>(res: def::Res) -> Result> { + fn from_def_res(res: def::Res) -> Result { match res { def::Res::Def(k, i) => Ok(Res::Def(k, i)), def::Res::PrimTy(t) => Ok(Res::PrimTy(t)), @@ -55,33 +62,36 @@ impl Res { } } -fn find_primitive_impls<'tcx>(tcx: TyCtxt<'tcx>, name: &str) -> impl Iterator + 'tcx { - let ty = match name { - "bool" => SimplifiedType::Bool, - "char" => SimplifiedType::Char, - "str" => SimplifiedType::Str, - "array" => SimplifiedType::Array, - "slice" => SimplifiedType::Slice, - // FIXME: rustdoc documents these two using just `pointer`. - // - // Maybe this is something we should do here too. - "const_ptr" => SimplifiedType::Ptr(Mutability::Not), - "mut_ptr" => SimplifiedType::Ptr(Mutability::Mut), - "isize" => SimplifiedType::Int(IntTy::Isize), - "i8" => SimplifiedType::Int(IntTy::I8), - "i16" => SimplifiedType::Int(IntTy::I16), - "i32" => SimplifiedType::Int(IntTy::I32), - "i64" => SimplifiedType::Int(IntTy::I64), - "i128" => SimplifiedType::Int(IntTy::I128), - "usize" => SimplifiedType::Uint(UintTy::Usize), - "u8" => SimplifiedType::Uint(UintTy::U8), - "u16" => SimplifiedType::Uint(UintTy::U16), - "u32" => SimplifiedType::Uint(UintTy::U32), - "u64" => SimplifiedType::Uint(UintTy::U64), - "u128" => SimplifiedType::Uint(UintTy::U128), - "f32" => SimplifiedType::Float(FloatTy::F32), - "f64" => SimplifiedType::Float(FloatTy::F64), - _ => return [].iter().copied(), +fn as_primitive_ty(name: Symbol) -> Option { + match name.as_str() { + "bool" => Some(SimplifiedType::Bool), + "char" => Some(SimplifiedType::Char), + "str" => Some(SimplifiedType::Str), + "array" => Some(SimplifiedType::Array), + "slice" => Some(SimplifiedType::Slice), + "const_ptr" => Some(SimplifiedType::Ptr(Mutability::Not)), + "mut_ptr" => Some(SimplifiedType::Ptr(Mutability::Mut)), + "isize" => Some(SimplifiedType::Int(IntTy::Isize)), + "i8" => Some(SimplifiedType::Int(IntTy::I8)), + "i16" => Some(SimplifiedType::Int(IntTy::I16)), + "i32" => Some(SimplifiedType::Int(IntTy::I32)), + "i64" => Some(SimplifiedType::Int(IntTy::I64)), + "i128" => Some(SimplifiedType::Int(IntTy::I128)), + "usize" => Some(SimplifiedType::Uint(UintTy::Usize)), + "u8" => Some(SimplifiedType::Uint(UintTy::U8)), + "u16" => Some(SimplifiedType::Uint(UintTy::U16)), + "u32" => Some(SimplifiedType::Uint(UintTy::U32)), + "u64" => Some(SimplifiedType::Uint(UintTy::U64)), + "u128" => Some(SimplifiedType::Uint(UintTy::U128)), + "f32" => Some(SimplifiedType::Float(FloatTy::F32)), + "f64" => Some(SimplifiedType::Float(FloatTy::F64)), + _ => None, + } +} + +fn find_primitive_impls(tcx: TyCtxt<'_>, name: Symbol) -> impl Iterator + '_ { + let Some(ty) = as_primitive_ty(name) else { + return [].iter().copied(); }; tcx.incoherent_impls(ty).iter().copied() @@ -92,7 +102,6 @@ fn find_primitive_impls<'tcx>(tcx: TyCtxt<'tcx>, name: &str) -> impl Iterator Option { - let segment_vec = path.split("::").collect::>(); let report_err = if relaxed { |tcx: TyCtxt<'_>, err: String| { tcx.sess.warn(err); @@ -102,7 +111,25 @@ pub fn expect_resolve_string_to_def_id(tcx: TyCtxt, path: &str, relaxed: bool) - tcx.sess.err(err); } }; - let res = def_path_res(tcx, &segment_vec) + let mut hasher = StableHasher::new(); + path.hash(&mut hasher); + let mut parser = new_parser_from_source_str( + &tcx.sess.parse_sess, + rustc_span::FileName::Anon(hasher.finish()), + path.to_string(), + ); + let qpath = parser.parse_expr().map_err(|mut e| e.emit()).ok()?; + if parser.token.kind != TokenKind::Eof { + report_err(tcx, format!("Tokens left over after parsing path {path}")); + return None; + } + + let ExprKind::Path(slf, rest) = &qpath.kind else { + report_err(tcx, format!("Expected path expression, got {path}")); + return None; + }; + + let res = def_path_res(tcx, slf.as_deref(), &rest.segments) .map_err(|e| report_err(tcx, format!("Could not resolve {path}: {e:?}"))) .ok()?; match res { @@ -115,115 +142,186 @@ pub fn expect_resolve_string_to_def_id(tcx: TyCtxt, path: &str, relaxed: bool) - } } -/// Lifted from `clippy_utils` -pub fn def_path_res<'a>(tcx: TyCtxt, path: &[&'a str]) -> Result> { - fn item_child_by_name<'a>( - tcx: TyCtxt<'_>, - def_id: DefId, - name: &str, - ) -> Option>> { - if let Some(local_id) = def_id.as_local() { - local_item_children_by_name(tcx, local_id, name) - } else { - non_local_item_children_by_name(tcx, def_id, name) - } +fn item_child_by_name(tcx: TyCtxt<'_>, def_id: DefId, name: Symbol) -> Option> { + if let Some(local_id) = def_id.as_local() { + local_item_children_by_name(tcx, local_id, name) + } else { + non_local_item_children_by_name(tcx, def_id, name) } +} - fn non_local_item_children_by_name<'a>( - tcx: TyCtxt<'_>, - def_id: DefId, - name: &str, - ) -> Option>> { - match tcx.def_kind(def_id) { - DefKind::Mod | DefKind::Enum | DefKind::Trait => tcx - .module_children(def_id) - .iter() - .find(|item| item.ident.name.as_str() == name) - .map(|child| Res::from_def_res(child.res.expect_non_local())), - DefKind::Impl { of_trait: false } => tcx - .associated_item_def_ids(def_id) - .iter() - .copied() - .find(|assoc_def_id| tcx.item_name(*assoc_def_id).as_str() == name) - .map(|assoc_def_id| Ok(Res::Def(tcx.def_kind(assoc_def_id), assoc_def_id))), - _ => None, - } +fn non_local_item_children_by_name( + tcx: TyCtxt<'_>, + def_id: DefId, + name: Symbol, +) -> Option> { + match tcx.def_kind(def_id) { + DefKind::Mod | DefKind::Enum | DefKind::Trait => tcx + .module_children(def_id) + .iter() + .find(|item| item.ident.name == name) + .map(|child| Res::from_def_res(child.res.expect_non_local())), + DefKind::Impl { of_trait: false } => tcx + .associated_item_def_ids(def_id) + .iter() + .copied() + .find(|assoc_def_id| tcx.item_name(*assoc_def_id) == name) + .map(|assoc_def_id| Ok(Res::Def(tcx.def_kind(assoc_def_id), assoc_def_id))), + _ => None, } +} - fn local_item_children_by_name<'a>( - tcx: TyCtxt<'_>, - local_id: LocalDefId, - name: &str, - ) -> Option>> { - let hir = tcx.hir(); - - let root_mod; - let item_kind = match hir.find_by_def_id(local_id) { - Some(Node::Crate(r#mod)) => { - root_mod = ItemKind::Mod(r#mod); - &root_mod - } - Some(Node::Item(item)) => &item.kind, - _ => return None, - }; - - let res = |def_id: LocalDefId| Ok(Res::Def(tcx.def_kind(def_id), def_id.to_def_id())); - - match item_kind { - ItemKind::Mod(r#mod) => r#mod - .item_ids - .iter() - .find(|&item_id| hir.item(*item_id).ident.name.as_str() == name) - .map(|&item_id| res(item_id.owner_id.def_id)), - ItemKind::Impl(r#impl) => r#impl - .items - .iter() - .find(|item| item.ident.name.as_str() == name) - .map(|&ImplItemRef { id, .. }| res(id.owner_id.def_id)), - ItemKind::Trait(.., trait_item_refs) => trait_item_refs - .iter() - .find(|item| item.ident.name.as_str() == name) - .map(|&TraitItemRef { id, .. }| res(id.owner_id.def_id)), - _ => None, - } - } +fn local_item_children_by_name( + tcx: TyCtxt<'_>, + local_id: LocalDefId, + name: Symbol, +) -> Option> { + let hir = tcx.hir(); - let (base, path) = match *path { - [primitive] => { - let sym = Symbol::intern(primitive); - return PrimTy::from_name(sym) - .map(Res::PrimTy) - .ok_or(ResolutionError::CannotResolvePrimitiveType(sym)); + let root_mod; + let item_kind = match hir.find_by_def_id(local_id) { + Some(Node::Crate(r#mod)) => { + root_mod = ItemKind::Mod(r#mod); + &root_mod } - [base, ref path @ ..] => (base, path), - [] => return Err(ResolutionError::PathIsEmpty), + Some(Node::Item(item)) => &item.kind, + _ => return None, }; - let local_crate = if tcx.crate_name(LOCAL_CRATE) == Symbol::intern(base) || base == "crate" { - Some(LOCAL_CRATE.as_def_id()) - } else { - None - }; + let res = |def_id: LocalDefId| Ok(Res::Def(tcx.def_kind(def_id), def_id.to_def_id())); - fn find_crates(tcx: TyCtxt<'_>, name: Symbol) -> impl Iterator + '_ { - tcx.crates(()) + match item_kind { + ItemKind::Mod(r#mod) => r#mod + .item_ids .iter() - .copied() - .filter(move |&num| tcx.crate_name(num) == name) - .map(CrateNum::as_def_id) + .find(|&item_id| hir.item(*item_id).ident.name == name) + .map(|&item_id| res(item_id.owner_id.def_id)), + ItemKind::Impl(r#impl) => r#impl + .items + .iter() + .find(|item| item.ident.name == name) + .map(|&ImplItemRef { id, .. }| res(id.owner_id.def_id)), + ItemKind::Trait(.., trait_item_refs) => trait_item_refs + .iter() + .find(|item| item.ident.name == name) + .map(|&TraitItemRef { id, .. }| res(id.owner_id.def_id)), + _ => None, + } +} + +fn find_crates(tcx: TyCtxt<'_>, name: Symbol) -> impl Iterator + '_ { + tcx.crates(()) + .iter() + .copied() + .filter(move |&num| tcx.crate_name(num) == name) + .map(CrateNum::as_def_id) +} + +fn resolve_ty<'tcx>(tcx: TyCtxt<'tcx>, t: &Ty) -> Result> { + match &t.kind { + TyKind::Path(qslf, pth) => { + let adt = def_path_res(tcx, qslf.as_deref(), pth.segments.as_slice())?; + Ok(ty::Ty::new_adt( + tcx, + tcx.adt_def(adt.def_id()), + ty::List::empty(), + )) + } + _ => Err(ResolutionError::UnsupportedType(t.clone())), } +} + +/// Main algorithm lifted from `clippy_utils`. I've made additions so this can +/// handle some qualified paths. +/// +/// ## Caveats +/// +/// Resolution is a rabbit hole that is easy to get lost in. To avoid spending +/// inordinate amounts of time on this, I have eschewed some features and +/// niceties that didn't seem pressing. What follows is a numbered list of such +/// features. Each index in this list is later mentioned in code comments at +/// locations that are likely the best place to get started for implementing the +/// issue in question. +/// +/// 1. All local paths must start with `crate`. If the path has only one segment +/// this function assumes it is a primitive type and tries to resolve it as +/// such. E.g. you need to do `crate::MyStruct::method` and `::clone`. +/// 3. Generics are not supported. If you want to reference a path like +/// ` as std::iter::Extend>::extend`, simply don't add the +/// ``. Note though that you need to ensure that there is only one +/// matching method in this case. If the generics would be needed to +/// disambiguate the instances, one of them is instead returned +/// non-deterministically. +/// 2. It probably cannot resolve a qualified path if the base is a primitive +/// type. E.g. `usize::abs_diff` resolves but `::abs_diff` does not. +pub fn def_path_res(tcx: TyCtxt, qself: Option<&QSelf>, path: &[PathSegment]) -> Result { + let no_generics_supported = |segment: &PathSegment| { + if segment.args.is_some() { + tcx.sess.err(format!( + "Generics are not supported in this position: {segment:?}" + )); + } + }; + let (starts, path): (_, &[PathSegment]) = match qself { + None => match path { + [primitive] => { + /* Start here for issue 1 */ + let sym = primitive.ident.name; + return PrimTy::from_name(sym) + .map(Res::PrimTy) + .ok_or(ResolutionError::CannotResolvePrimitiveType(sym)); + } + [base, ref path @ ..] => { + /* This is relevant for issue 2 */ + no_generics_supported(base); + let base_name = base.ident.name; + let local_crate = + if tcx.crate_name(LOCAL_CRATE) == base_name || base_name.as_str() == "crate" { + Some(LOCAL_CRATE.as_def_id()) + } else { + None + }; + ( + Box::new( + find_primitive_impls(tcx, base_name) + .chain(find_crates(tcx, base_name)) + .chain(local_crate), + ) as Box>, + path, + ) + } + [] => return Err(ResolutionError::PathIsEmpty), + }, + Some(slf) => ( + if slf.position == 0 { + /* this is relevant for 3 */ + let TyKind::Path(qslf, pth) = &slf.ty.kind else { + return Err(ResolutionError::UnsupportedType((*slf.ty).clone())); + }; + let ty = def_path_res(tcx, qslf.as_deref(), &pth.segments)?; + let impls = tcx.inherent_impls(ty.def_id()); + Box::new(impls.iter().copied()) as Box<_> + } else { + let r#trait = def_path_res(tcx, None, &path[..slf.position])?; + let r#type = resolve_ty(tcx, &slf.ty)?; + let mut impls = vec![]; + /* This is relevant for issue 2 */ + tcx.for_each_relevant_impl(r#trait.def_id(), r#type, |i| impls.push(i)); + Box::new(impls.into_iter()) as Box<_> + }, + &path[slf.position..], + ), + }; - let starts = find_primitive_impls(tcx, base) - .chain(find_crates(tcx, Symbol::intern(base))) - .chain(local_crate) - .map(|id| Res::Def(tcx.def_kind(id), id)); let mut last = Err(ResolutionError::EmptyStarts); for first in starts { last = path .iter() - .copied() // for each segment, find the child item - .try_fold(first, |res, segment| { + .try_fold(Res::Def(tcx.def_kind(first), first), |res, segment| { + no_generics_supported(segment); + let segment = segment.ident.name; let def_id = res.def_id(); if let Some(item) = item_child_by_name(tcx, def_id, segment) { item diff --git a/crates/paralegal-flow/tests/external_resolution_tests.rs b/crates/paralegal-flow/tests/external_resolution_tests.rs new file mode 100644 index 0000000000..72e937eeee --- /dev/null +++ b/crates/paralegal-flow/tests/external_resolution_tests.rs @@ -0,0 +1,67 @@ +use paralegal_flow::test_utils::InlineTestBuilder; + +#[test] +fn basic_external_entrypoint_test() { + InlineTestBuilder::new(stringify!( + fn target() {} + )) + .with_entrypoint("crate::target") + .check_ctrl(|_| {}); +} + +#[test] +fn trait_instance_entry_point_test() { + InlineTestBuilder::new(stringify!( + struct MyStruct {} + + impl Clone for MyStruct { + fn clone(&self) -> Self { + MyStruct {} + } + } + )) + .with_entrypoint("::clone") + .run(|graph| { + assert!(graph + .desc + .controllers + .values() + .any(|v| v.name.as_str() == "clone")) + }) + .unwrap() +} + +#[test] +fn qualified_type_without_trait() { + InlineTestBuilder::new(stringify!( + struct MyStruct {} + + impl MyStruct { + fn method(&self) {} + } + )) + .with_entrypoint("::method") + .run(|graph| { + assert!(graph + .desc + .controllers + .values() + .any(|v| v.name.as_str() == "method")) + }) + .unwrap() +} + +#[test] +fn reject_generic_arguments_test() { + InlineTestBuilder::new(stringify!( + struct MyStruct(Vec); + + impl Clone for MyStruct { + fn clone(&self) -> Self { + MyStruct(self.0.clone()) + } + } + )) + .with_entrypoint(" as std::clone::Clone>::clone") + .expect_fail_compile() +}