diff --git a/CHANGELOG.md b/CHANGELOG.md index de8da99cdee1..a27cd9155d6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1985,6 +1985,7 @@ Released 2018-09-13 [`non_ascii_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#non_ascii_literal [`nonminimal_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#nonminimal_bool [`nonsensical_open_options`]: https://rust-lang.github.io/rust-clippy/master/index.html#nonsensical_open_options +[`not_exhaustive_enough`]: https://rust-lang.github.io/rust-clippy/master/index.html#not_exhaustive_enough [`not_unsafe_ptr_arg_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#not_unsafe_ptr_arg_deref [`ok_expect`]: https://rust-lang.github.io/rust-clippy/master/index.html#ok_expect [`op_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#op_ref diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 35b057d7b6a4..df5f747e6376 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -278,6 +278,7 @@ mod new_without_default; mod no_effect; mod non_copy_const; mod non_expressive_names; +mod not_exhaustive_enough; mod open_options; mod option_env_unwrap; mod option_if_let_else; @@ -810,6 +811,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &non_expressive_names::JUST_UNDERSCORES_AND_DIGITS, &non_expressive_names::MANY_SINGLE_CHAR_NAMES, &non_expressive_names::SIMILAR_NAMES, + ¬_exhaustive_enough::NOT_EXHAUSTIVE_ENOUGH, &open_options::NONSENSICAL_OPEN_OPTIONS, &option_env_unwrap::OPTION_ENV_UNWRAP, &option_if_let_else::OPTION_IF_LET_ELSE, @@ -1124,6 +1126,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| box path_buf_push_overwrite::PathBufPushOverwrite); store.register_late_pass(|| box integer_division::IntegerDivision); store.register_late_pass(|| box inherent_to_string::InherentToString); + store.register_late_pass(|| box not_exhaustive_enough::NotExhaustiveEnough); let max_trait_bounds = conf.max_trait_bounds; store.register_late_pass(move || box trait_bounds::TraitBounds::new(max_trait_bounds)); store.register_late_pass(|| box comparison_chain::ComparisonChain); @@ -1319,6 +1322,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&needless_continue::NEEDLESS_CONTINUE), LintId::of(&needless_pass_by_value::NEEDLESS_PASS_BY_VALUE), LintId::of(&non_expressive_names::SIMILAR_NAMES), + LintId::of(¬_exhaustive_enough::NOT_EXHAUSTIVE_ENOUGH), LintId::of(&option_if_let_else::OPTION_IF_LET_ELSE), LintId::of(&pass_by_ref_or_value::LARGE_TYPES_PASSED_BY_VALUE), LintId::of(&pass_by_ref_or_value::TRIVIALLY_COPY_PASS_BY_REF), diff --git a/clippy_lints/src/not_exhaustive_enough.rs b/clippy_lints/src/not_exhaustive_enough.rs new file mode 100644 index 000000000000..678af99cc9cd --- /dev/null +++ b/clippy_lints/src/not_exhaustive_enough.rs @@ -0,0 +1,171 @@ +use crate::utils::span_lint_and_sugg; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{Arm, Expr, ExprKind, FieldPat, MatchSource, Pat, PatKind, QPath}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** + /// As suggested in the [non_exhaustive RFC](https://github.com/rust-lang/rfcs/blob/master/text/2008-non-exhaustive.md#unresolved-questions), + /// when using non-exhaustive enums and structs in patterns, + /// this lint warns the user for missing variants or fields despite having a wildcard arm or a rest pattern. + /// + /// **Why is this bad?** + /// When new fields/variants are added by the upstream crate they might go unnoticed. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// #[non_exhaustive] + /// # enum E {First,Second,Third} + /// # let e = E::First; + /// // Bad + /// match e { + /// E::First => {} + /// E::Second => {} + /// _ => {} + /// } + /// // Good + /// match e { + /// E::First => {} + /// E::Second => {} + /// E::Third => {} + /// } + /// ``` + pub NOT_EXHAUSTIVE_ENOUGH, + pedantic, + "missing variants or fields in a pattern despite having a wildcard arm or a rest pattern" +} + +declare_lint_pass!(NotExhaustiveEnough => [NOT_EXHAUSTIVE_ENOUGH]); + +impl<'tcx> LateLintPass<'tcx> for NotExhaustiveEnough { + fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) { + if_chain! { + if let PatKind::Struct(_, ref field_pats, ref rest_pat) = &pat.kind; + if let ty::Adt(adt_def, _) = cx.typeck_results().pat_ty(pat).kind(); + if is_struct_not_exhaustive(adt_def); + if *rest_pat; + if !field_pats.is_empty(); + if let Some(variant) = get_variant(adt_def); + if let field_defs = &variant.fields; + then + { + check_struct_pat(cx, pat, field_pats, field_defs); + } + } + } + + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if_chain! { + if let ExprKind::Match(e, ref arms, MatchSource::Normal) = expr.kind; + if !arms.is_empty(); + if let ExprKind::Path(..) = e.kind; + if let ty::Adt(adt_def, _) = cx.typeck_results().expr_ty(e).kind(); + if adt_def.is_enum(); + if is_enum_not_exhaustive(adt_def); + then + { + check_path_pat(cx, arms, e); + } + } + } +} + +fn check_path_pat<'tcx>(cx: &LateContext<'tcx>, arms: &[Arm<'_>], e: &'tcx Expr<'_>) { + let missing_variants = get_missing_variants(cx, arms, e); + span_lint_and_sugg( + cx, + NOT_EXHAUSTIVE_ENOUGH, + e.span, + "enum match is not exhaustive enough", + "try adding missing variants", + missing_variants.join(" , "), + Applicability::MaybeIncorrect, + ); +} + +fn check_struct_pat<'tcx>( + cx: &LateContext<'tcx>, + pat: &'tcx Pat<'_>, + field_pats: &[FieldPat<'_>], + field_defs: &[ty::FieldDef], +) { + let missing_fields = get_missing_fields(field_pats, field_defs); + if !missing_fields.is_empty() { + let suggestions: Vec = missing_fields.iter().map(|v| v.to_owned() + ": _").collect(); + span_lint_and_sugg( + cx, + NOT_EXHAUSTIVE_ENOUGH, + pat.span, + "struct match is not exhaustive enough", + "try adding missing fields", + suggestions.join(" , "), + Applicability::MaybeIncorrect, + ); + } +} + +fn get_missing_variants<'tcx>(cx: &LateContext<'tcx>, arms: &[Arm<'_>], e: &'tcx Expr<'_>) -> Vec { + let ty = cx.typeck_results().expr_ty(e); + let mut missing_variants = vec![]; + if let ty::Adt(def, _) = ty.kind() { + for variant in &def.variants { + missing_variants.push(variant); + } + } + for arm in arms { + if let PatKind::Path(ref path) = arm.pat.kind { + if let QPath::Resolved(_, p) = path { + missing_variants.retain(|e| e.ctor_def_id != Some(p.res.def_id())); + } + } else if let PatKind::TupleStruct(QPath::Resolved(_, p), ref patterns, ..) = arm.pat.kind { + let is_pattern_exhaustive = |pat: &&Pat<'_>| matches!(pat.kind, PatKind::Wild | PatKind::Binding(.., None)); + if patterns.iter().all(is_pattern_exhaustive) { + missing_variants.retain(|e| e.ctor_def_id != Some(p.res.def_id())); + } + } + } + let missing_variants = missing_variants.iter().map(|v| cx.tcx.def_path_str(v.def_id)).collect(); + missing_variants +} + +fn get_missing_fields(field_pats: &[FieldPat<'_>], field_defs: &[ty::FieldDef]) -> Vec { + let mut missing_fields = vec![]; + let mut field_match = false; + + for field_def in field_defs { + for field_pat in field_pats { + if field_def.ident == field_pat.ident { + field_match = true; + break; + } + } + if !&field_match && field_def.vis.is_visible_locally() { + missing_fields.push(field_def.ident.name.to_ident_string()) + } + field_match = false; + } + missing_fields +} + +fn is_enum_not_exhaustive(adt_def: &ty::AdtDef) -> bool { + adt_def.is_variant_list_non_exhaustive() +} + +fn is_struct_not_exhaustive(adt_def: &ty::AdtDef) -> bool { + if let Some(variant) = adt_def.variants.iter().next() { + return variant.is_field_list_non_exhaustive(); + } + false +} + +fn get_variant(adt_def: &ty::AdtDef) -> Option<&ty::VariantDef> { + if let Some(variant) = adt_def.variants.iter().next() { + return Some(variant); + } + None +} diff --git a/tests/ui/auxiliary/not_exhaustive_enough_helper.rs b/tests/ui/auxiliary/not_exhaustive_enough_helper.rs new file mode 100644 index 000000000000..6ef6f355c6a5 --- /dev/null +++ b/tests/ui/auxiliary/not_exhaustive_enough_helper.rs @@ -0,0 +1,18 @@ +#[non_exhaustive] +pub enum AnotherCrateEnum { + AFirst, + ASecond, + AThird, +} + +#[derive(Default)] +#[non_exhaustive] +pub struct AnotherCrateStruct { + pub a1: i32, + pub b1: i32, + pub c1: i32, +} + +#[derive(Default)] +#[non_exhaustive] +pub struct TPrivateField(pub i32, pub i32, i32); diff --git a/tests/ui/not_exhaustive_enough.rs b/tests/ui/not_exhaustive_enough.rs new file mode 100644 index 000000000000..e76ae47ee507 --- /dev/null +++ b/tests/ui/not_exhaustive_enough.rs @@ -0,0 +1,191 @@ +// aux-build:not_exhaustive_enough_helper.rs + +#![warn(clippy::not_exhaustive_enough)] +#![allow(clippy::many_single_char_names)] +#![allow(clippy::never_loop)] +#![allow(clippy::single_match)] + +extern crate not_exhaustive_enough_helper; +use not_exhaustive_enough_helper::{AnotherCrateEnum, AnotherCrateStruct, TPrivateField}; + +#[non_exhaustive] +pub enum DefaultEnum { + First, + Second, + Third, +} + +#[non_exhaustive] +pub enum DataEnum { + First(String), + Second(u32, u32), + Third(String), +} + +pub enum StructVariantEnum1 { + #[non_exhaustive] + V { a: i32, b: i32 }, +} + +pub enum StructVariantEnum2 { + #[non_exhaustive] + V { + a: i32, + b: i32, + }, + A { + c: u32, + }, +} + +#[derive(Default)] +#[non_exhaustive] +pub struct DefaultStruct { + pub a: i32, + pub b: i32, + pub c: i32, +} + +#[derive(Default)] +#[non_exhaustive] +pub struct DefaultTuple(pub i32, pub i32, pub i32); + +fn main() { + //////// Enum + + let default_enum = DefaultEnum::First; + + let struct_variant_enum_1 = StructVariantEnum1::V { a: 1, b: 2 }; + + let struct_variant_enum_2 = StructVariantEnum2::V { a: 1, b: 2 }; + + match default_enum { + DefaultEnum::First => {}, + DefaultEnum::Second => {}, + _ => {}, + } + + match struct_variant_enum_1 { + StructVariantEnum1::V { a: _, .. } => {}, + } + + match struct_variant_enum_2 { + StructVariantEnum2::V { a: _, .. } => {}, + _ => {}, + } + + // + + let example = "Example".to_string(); + let data_enum = DataEnum::First(example); + + match data_enum { + DataEnum::First(..) => {}, + DataEnum::Second(..) => {}, + _ => {}, + } + + //////// Struct + + let DefaultStruct { a: _, b: _, .. } = DefaultStruct::default(); + + match DefaultStruct::default() { + DefaultStruct { a: 42, b: 21, .. } => {}, + DefaultStruct { a: _, b: _, .. } => {}, + } + + if let DefaultStruct { a: 42, b: _, .. } = DefaultStruct::default() {} + + let v = vec![DefaultStruct::default()]; + + for DefaultStruct { a: _, b: _, .. } in v {} + + while let DefaultStruct { a: 42, b: _, .. } = DefaultStruct::default() { + break; + } + + pub fn take_s(DefaultStruct { a, b, .. }: DefaultStruct) -> (i32, i32) { + (a, b) + } + + //////// Tuple Struct + + let DefaultTuple { 0: _, 1: _, .. } = DefaultTuple::default(); + + match DefaultTuple::default() { + DefaultTuple { 0: 42, 1: 21, .. } => {}, + DefaultTuple { 0: _, 1: _, .. } => {}, + } + + if let DefaultTuple { 0: 42, 1: _, .. } = DefaultTuple::default() {} + + let default_tuple = vec![DefaultTuple::default()]; + for DefaultTuple { 0: _, 1: _, .. } in default_tuple {} + + while let DefaultTuple { 0: 42, 1: _, .. } = DefaultTuple::default() { + break; + } + + pub fn take_t(DefaultTuple { 0: _, 1: _, .. }: DefaultTuple) -> (i32, i32) { + (0, 1) + } + + //////// Tuple Struct - private field + + let TPrivateField { 0: _, 1: _, .. } = TPrivateField::default(); + + match TPrivateField::default() { + TPrivateField { 0: 42, 1: 21, .. } => {}, + TPrivateField { 0: _, 1: _, .. } => {}, + } + + match TPrivateField::default() { + TPrivateField { 1: 21, .. } => {}, + _ => {}, + } + + if let TPrivateField { 0: 42, 1: _, .. } = TPrivateField::default() {} + + let m = vec![TPrivateField::default()]; + for TPrivateField { 0: _, 1: _, .. } in m {} + + while let TPrivateField { 0: 42, 1: _, .. } = TPrivateField::default() { + break; + } + + pub fn take_w(TPrivateField { 0: _, 1: _, .. }: TPrivateField) -> (i32, i32) { + (0, 1) + } + + // Enum - Another Crate + + let another_crate_enum = AnotherCrateEnum::AFirst; + + match another_crate_enum { + AnotherCrateEnum::AFirst => {}, + _ => {}, + } + + // Struct - Another Crate + + let AnotherCrateStruct { a1: _, b1: _, .. } = AnotherCrateStruct::default(); + + match AnotherCrateStruct::default() { + AnotherCrateStruct { a1: 42, b1: 21, .. } => {}, + AnotherCrateStruct { a1: _, b1: _, .. } => {}, + } + + if let AnotherCrateStruct { a1: 42, b1: _, .. } = AnotherCrateStruct::default() {} + + let a_v = vec![AnotherCrateStruct::default()]; + + for AnotherCrateStruct { a1: _, b1: _, .. } in a_v {} + + while let AnotherCrateStruct { a1: 42, b1: _, .. } = AnotherCrateStruct::default() { + break; + } + + pub fn take_a_s(AnotherCrateStruct { a1, b1, .. }: AnotherCrateStruct) -> (i32, i32) { + (a1, b1) + } +} diff --git a/tests/ui/not_exhaustive_enough.stderr b/tests/ui/not_exhaustive_enough.stderr new file mode 100644 index 000000000000..07411c3a473e --- /dev/null +++ b/tests/ui/not_exhaustive_enough.stderr @@ -0,0 +1,166 @@ +error: enum match is not exhaustive enough + --> $DIR/not_exhaustive_enough.rs:62:11 + | +LL | match default_enum { + | ^^^^^^^^^^^^ help: try adding missing variants: `DefaultEnum::Third` + | + = note: `-D clippy::not-exhaustive-enough` implied by `-D warnings` + +error: struct match is not exhaustive enough + --> $DIR/not_exhaustive_enough.rs:69:9 + | +LL | StructVariantEnum1::V { a: _, .. } => {}, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try adding missing fields: `b: _` + +error: struct match is not exhaustive enough + --> $DIR/not_exhaustive_enough.rs:73:9 + | +LL | StructVariantEnum2::V { a: _, .. } => {}, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try adding missing fields: `b: _` + +error: enum match is not exhaustive enough + --> $DIR/not_exhaustive_enough.rs:82:11 + | +LL | match data_enum { + | ^^^^^^^^^ help: try adding missing variants: `DataEnum::Third` + +error: struct match is not exhaustive enough + --> $DIR/not_exhaustive_enough.rs:90:9 + | +LL | let DefaultStruct { a: _, b: _, .. } = DefaultStruct::default(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try adding missing fields: `c: _` + +error: struct match is not exhaustive enough + --> $DIR/not_exhaustive_enough.rs:93:9 + | +LL | DefaultStruct { a: 42, b: 21, .. } => {}, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try adding missing fields: `c: _` + +error: struct match is not exhaustive enough + --> $DIR/not_exhaustive_enough.rs:94:9 + | +LL | DefaultStruct { a: _, b: _, .. } => {}, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try adding missing fields: `c: _` + +error: struct match is not exhaustive enough + --> $DIR/not_exhaustive_enough.rs:97:12 + | +LL | if let DefaultStruct { a: 42, b: _, .. } = DefaultStruct::default() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try adding missing fields: `c: _` + +error: struct match is not exhaustive enough + --> $DIR/not_exhaustive_enough.rs:101:9 + | +LL | for DefaultStruct { a: _, b: _, .. } in v {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try adding missing fields: `c: _` + +error: struct match is not exhaustive enough + --> $DIR/not_exhaustive_enough.rs:103:15 + | +LL | while let DefaultStruct { a: 42, b: _, .. } = DefaultStruct::default() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try adding missing fields: `c: _` + +error: struct match is not exhaustive enough + --> $DIR/not_exhaustive_enough.rs:107:19 + | +LL | pub fn take_s(DefaultStruct { a, b, .. }: DefaultStruct) -> (i32, i32) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try adding missing fields: `c: _` + +error: struct match is not exhaustive enough + --> $DIR/not_exhaustive_enough.rs:113:9 + | +LL | let DefaultTuple { 0: _, 1: _, .. } = DefaultTuple::default(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try adding missing fields: `2: _` + +error: struct match is not exhaustive enough + --> $DIR/not_exhaustive_enough.rs:116:9 + | +LL | DefaultTuple { 0: 42, 1: 21, .. } => {}, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try adding missing fields: `2: _` + +error: struct match is not exhaustive enough + --> $DIR/not_exhaustive_enough.rs:117:9 + | +LL | DefaultTuple { 0: _, 1: _, .. } => {}, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try adding missing fields: `2: _` + +error: struct match is not exhaustive enough + --> $DIR/not_exhaustive_enough.rs:120:12 + | +LL | if let DefaultTuple { 0: 42, 1: _, .. } = DefaultTuple::default() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try adding missing fields: `2: _` + +error: struct match is not exhaustive enough + --> $DIR/not_exhaustive_enough.rs:123:9 + | +LL | for DefaultTuple { 0: _, 1: _, .. } in default_tuple {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try adding missing fields: `2: _` + +error: struct match is not exhaustive enough + --> $DIR/not_exhaustive_enough.rs:125:15 + | +LL | while let DefaultTuple { 0: 42, 1: _, .. } = DefaultTuple::default() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try adding missing fields: `2: _` + +error: struct match is not exhaustive enough + --> $DIR/not_exhaustive_enough.rs:129:19 + | +LL | pub fn take_t(DefaultTuple { 0: _, 1: _, .. }: DefaultTuple) -> (i32, i32) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try adding missing fields: `2: _` + +error: struct match is not exhaustive enough + --> $DIR/not_exhaustive_enough.rs:143:9 + | +LL | TPrivateField { 1: 21, .. } => {}, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try adding missing fields: `0: _` + +error: enum match is not exhaustive enough + --> $DIR/not_exhaustive_enough.rs:164:11 + | +LL | match another_crate_enum { + | ^^^^^^^^^^^^^^^^^^ help: try adding missing variants: `not_exhaustive_enough_helper::AnotherCrateEnum::ASecond , not_exhaustive_enough_helper::AnotherCrateEnum::AThird` + +error: struct match is not exhaustive enough + --> $DIR/not_exhaustive_enough.rs:171:9 + | +LL | let AnotherCrateStruct { a1: _, b1: _, .. } = AnotherCrateStruct::default(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try adding missing fields: `c1: _` + +error: struct match is not exhaustive enough + --> $DIR/not_exhaustive_enough.rs:174:9 + | +LL | AnotherCrateStruct { a1: 42, b1: 21, .. } => {}, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try adding missing fields: `c1: _` + +error: struct match is not exhaustive enough + --> $DIR/not_exhaustive_enough.rs:175:9 + | +LL | AnotherCrateStruct { a1: _, b1: _, .. } => {}, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try adding missing fields: `c1: _` + +error: struct match is not exhaustive enough + --> $DIR/not_exhaustive_enough.rs:178:12 + | +LL | if let AnotherCrateStruct { a1: 42, b1: _, .. } = AnotherCrateStruct::default() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try adding missing fields: `c1: _` + +error: struct match is not exhaustive enough + --> $DIR/not_exhaustive_enough.rs:182:9 + | +LL | for AnotherCrateStruct { a1: _, b1: _, .. } in a_v {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try adding missing fields: `c1: _` + +error: struct match is not exhaustive enough + --> $DIR/not_exhaustive_enough.rs:184:15 + | +LL | while let AnotherCrateStruct { a1: 42, b1: _, .. } = AnotherCrateStruct::default() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try adding missing fields: `c1: _` + +error: struct match is not exhaustive enough + --> $DIR/not_exhaustive_enough.rs:188:21 + | +LL | pub fn take_a_s(AnotherCrateStruct { a1, b1, .. }: AnotherCrateStruct) -> (i32, i32) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try adding missing fields: `c1: _` + +error: aborting due to 27 previous errors +