1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
// Code generated by the `darling` derive macro triggers a clippy lint.
// https://github.com/TedDriggs/darling/issues/293
#![allow(clippy::manual_unwrap_or_default)]

use std::collections::HashSet;

use darling::{error::Accumulator, util::Flag, FromAttributes};
use serde_derive_internals::{ast as serde_ast, Ctxt, Derive};
use syn::{
    DeriveInput, ExprPath, GenericArgument, Generics, Ident, PathArguments, PathSegment, Type,
    TypeParam,
};

use super::{
    util::{
        err_serde_failed, get_serde_default_value, try_extract_doc_title_description,
        DarlingResultIterator,
    },
    Data, Field, LazyCustomAttribute, Metadata, Style, Tagging, Variant,
};

const ERR_NO_ENUM_TUPLES: &str = "enum variants cannot be tuples (multiple unnamed fields)";
const ERR_NO_ENUM_VARIANT_DESCRIPTION: &str = "enum variants must have a description i.e. `/// This is a description` or `#[configurable(description = \"This is a description...\")]`";
const ERR_ENUM_UNTAGGED_DUPLICATES: &str = "enum variants must be unique in style/shape when in untagged mode i.e. there cannot be multiple unit variants, or tuple variants with the same fields, etc";
const ERR_NO_UNIT_STRUCTS: &str = "unit structs are not supported by `Configurable`";
const ERR_MISSING_DESC: &str = "all structs/enums must have a description i.e. `/// This is a description` or `#[configurable(description = \"This is a description...\")]`";
const ERR_ASYMMETRIC_SERDE_TYPE_CONVERSION: &str = "any container using `from`/`try_from`/`into` via `#[serde(...)]` must do so symmetrically i.e. the from/into types must match";
const ERR_SERDE_TYPE_CONVERSION_FROM_TRY_FROM: &str = "`#[serde(from)]` and `#[serde(try_from)]` cannot be identical, as it is impossible for an infallible conversion from T to also be fallible";

/// A source data structure annotated with `#[derive(Configurable)]`, parsed into an internal
/// representation.
pub struct Container<'a> {
    original: &'a DeriveInput,
    name: String,
    default_value: Option<ExprPath>,
    data: Data<'a>,
    tagging: Option<Tagging>,
    virtual_newtype: Option<Type>,
    attrs: Attributes,
}

impl<'a> Container<'a> {
    /// Creates a new `Container<'a>` from the raw derive macro input.
    pub fn from_derive_input(input: &'a DeriveInput) -> darling::Result<Container<'a>> {
        // We can't do anything unless `serde` can also handle this container. We specifically only care about
        // deserialization here, because the schema tells us what we can _give_ to Vector.
        let context = Ctxt::new();
        let serde = match serde_ast::Container::from_ast(&context, input, Derive::Deserialize) {
            Some(serde) => {
                // This `serde_derive_internals` helper will panic if `check` isn't _always_ called, so we also have to
                // call it on the success path.
                context
                    .check()
                    .expect("should not have errors if container was parsed successfully");
                Ok(serde)
            }
            None => Err(err_serde_failed(context)),
        }?;

        let mut accumulator = Accumulator::default();

        // Check if we're dealing with a "virtual" newtype.
        //
        // In some cases, types may (de)serialize themselves as another type, which is entirely normal... but
        // they may do this with `serde` helper attributes rather than with a newtype wrapper or manually
        // converting between types.
        //
        // For types doing this, it could be entirely irrelevant to document all of the internal fields, or at
        // least enforce documenting them, because they don't truly represent the actual schema and all that
        // might get used is the documentation on the type having `Configurable` derived.
        //
        // All of that said, we check to see if the `from`, `try_from`, or `into` helper attributes are being
        // used from `serde`, and make sure the transformation is symmetric (it has to
        // deserialize from T and serialize to T, no halfsies) since we can't express a schema that's
        // half-and-half. Assuming it passes this requirement, we track the actual (de)serialized type and use
        // that for our schema generation instead.
        let virtual_newtype = if serde.attrs.type_from().is_some()
            || serde.attrs.type_try_from().is_some()
            || serde.attrs.type_into().is_some()
        {
            // if any of these are set, we start by checking `into`. If it's set, then that's fine, and we
            // continue verifying. Otherwise, it implies that `from`/`try_from` are set, and we only allow
            // symmetric conversions.
            if let Some(into_ty) = serde.attrs.type_into() {
                // Figure out which of `from` and `try_from` are set. Both cannot be set, because either the
                // types are different -- which means asymmetric conversion -- or they're both the same, which
                // would be a logical fallacy since you can't have a fallible conversion from T if you already
                // have an infallible conversion from T.
                //
                // Similar, at least one of them must be set, otherwise that's an asymmetric conversion.
                match (serde.attrs.type_from(), serde.attrs.type_try_from()) {
                    (None, None) => {
                        accumulator.push(
                            darling::Error::custom(ERR_ASYMMETRIC_SERDE_TYPE_CONVERSION)
                                .with_span(&serde.ident),
                        );
                        None
                    }
                    (Some(_), Some(_)) => {
                        accumulator.push(
                            darling::Error::custom(ERR_SERDE_TYPE_CONVERSION_FROM_TRY_FROM)
                                .with_span(&serde.ident),
                        );
                        None
                    }
                    (Some(from_ty), None) | (None, Some(from_ty)) => {
                        if into_ty == from_ty {
                            Some(into_ty.clone())
                        } else {
                            accumulator.push(
                                darling::Error::custom(ERR_ASYMMETRIC_SERDE_TYPE_CONVERSION)
                                    .with_span(&serde.ident),
                            );
                            None
                        }
                    }
                }
            } else {
                accumulator.push(
                    darling::Error::custom(ERR_ASYMMETRIC_SERDE_TYPE_CONVERSION)
                        .with_span(&serde.ident),
                );
                None
            }
        } else {
            None
        };

        // Once we have the `serde` side of things, we need to collect our own specific attributes for the container
        // and map things to our own `Container`.
        Attributes::from_attributes(&input.attrs)
            .and_then(|attrs| attrs.finalize(&input.attrs))
            // We successfully parsed the derive input through both `serde` itself and our own attribute parsing, so
            // build our data container based on whether or not we have a struct, enum, and do any necessary
            // validation, etc.
            .and_then(|attrs| {
                let tagging: Tagging = serde.attrs.tag().into();

                let (data, is_enum) = match serde.data {
                    serde_ast::Data::Enum(variants) => {
                        let variants = variants
                            .iter()
                            // When an item is marked as being skipped -- `#[serde(skip)]` -- we
                            // want to filter out variants that are skipped entirely, because
                            // otherwise they have to meet all the criteria (doc comment, etc)
                            // despite the fact they won't be part of the configuration schema
                            // anyways, and we can't filter it out after the below step because all
                            // we get is the errors until they can be validated completely.
                            .filter(|variant| {
                                !variant.attrs.skip_deserializing()
                                    && !variant.attrs.skip_serializing()
                            })
                            .map(|variant| {
                                Variant::from_ast(
                                    variant,
                                    tagging.clone(),
                                    virtual_newtype.is_some(),
                                )
                            })
                            .collect_darling_results(&mut accumulator);

                        // Check the generated variants for conformance. We do this at a per-variant and per-enum level.
                        // Not all enum variant styles are compatible with the various tagging types that `serde`
                        // supports, and additionally, we have some of our own constraints that we want to enforce.
                        for variant in &variants {
                            // We don't support tuple variants.
                            if variant.style() == Style::Tuple {
                                accumulator.push(
                                    darling::Error::custom(ERR_NO_ENUM_TUPLES).with_span(variant),
                                );
                            }

                            // All variants must have a description, except for untagged enums.
                            //
                            // This allows untagged enums used for "(de)serialize as A, B, or C"
                            // purposes to avoid needless titles/descriptions when their fields will
                            // implicitly provide that.
                            if variant.description().is_none() && tagging != Tagging::None {
                                accumulator.push(
                                    darling::Error::custom(ERR_NO_ENUM_VARIANT_DESCRIPTION)
                                        .with_span(variant),
                                );
                            }
                        }

                        // If we're in untagged mode, there can be no duplicate variants.
                        if tagging == Tagging::None {
                            for (i, variant) in variants.iter().enumerate() {
                                for (k, other_variant) in variants.iter().enumerate() {
                                    if variant == other_variant && i != k {
                                        accumulator.push(
                                            darling::Error::custom(ERR_ENUM_UNTAGGED_DUPLICATES)
                                                .with_span(variant),
                                        );
                                    }
                                }
                            }
                        }

                        (Data::Enum(variants), true)
                    }
                    serde_ast::Data::Struct(style, fields) => match style {
                        serde_ast::Style::Struct
                        | serde_ast::Style::Tuple
                        | serde_ast::Style::Newtype => {
                            let is_newtype_wrapper_field =
                                matches!(style, serde_ast::Style::Newtype);
                            let fields = fields
                                .iter()
                                .map(|field| {
                                    Field::from_ast(
                                        field,
                                        virtual_newtype.is_some(),
                                        is_newtype_wrapper_field,
                                    )
                                })
                                .collect_darling_results(&mut accumulator);

                            (Data::Struct(style.into(), fields), false)
                        }
                        serde_ast::Style::Unit => {
                            // This is a little ugly but we can't drop the accumulator without finishing it, otherwise
                            // it will panic to let us know we didn't assert whether there were errors or not... so add
                            // our error and just return a dummy value.
                            accumulator
                                .push(darling::Error::custom(ERR_NO_UNIT_STRUCTS).with_span(input));
                            (Data::Struct(Style::Unit, Vec::new()), false)
                        }
                    },
                };

                // All containers must have a description: no ifs, ands, or buts.
                //
                // The compile-time errors are a bit too inscrutable otherwise, and inscrutable errors are not very
                // helpful when using procedural macros.
                if attrs.description.is_none() {
                    accumulator
                        .push(darling::Error::custom(ERR_MISSING_DESC).with_span(&serde.ident));
                }

                let original = input;
                let name = serde.attrs.name().deserialize_name().to_string();
                let default_value = get_serde_default_value(&serde.ident, serde.attrs.default());

                let container = Container {
                    original,
                    name,
                    default_value,
                    data,
                    virtual_newtype,
                    tagging: is_enum.then_some(tagging),
                    attrs,
                };

                accumulator.finish_with(container)
            })
    }

    /// Ident of the container.
    ///
    /// This is simply the name or type of a struct/enum, but is not parsed directly as a type via
    /// `syn`, only an `Ident`.
    pub fn ident(&self) -> &Ident {
        &self.original.ident
    }

    /// Generics for the container, if any.
    pub fn generics(&self) -> &Generics {
        &self.original.generics
    }

    /// Data for the container.
    ///
    /// This would be the fields of a struct, or the variants for an enum.
    pub fn data(&self) -> &Data {
        &self.data
    }

    /// Name of the container when deserializing.
    ///
    /// This may be different than the name of the container itself depending on whether it has been
    /// altered with `serde` helper attributes i.e. `#[serde(rename = "...")]`.
    pub fn name(&self) -> &str {
        self.name.as_str()
    }

    /// Title of the container, if any.
    ///
    /// The title specifically refers to the headline portion of a doc comment. For example, if a
    /// struct has the following doc comment:
    ///
    /// ```text
    /// /// My special struct.
    /// ///
    /// /// Here's why it's special:
    /// /// ...
    /// struct Wrapper(u64);
    /// ```
    ///
    /// then the title would be `My special struct`. If the doc comment only contained `My special
    /// struct.`, then we would consider the title _empty_. See `description` for more details on
    /// detecting titles vs descriptions.
    pub fn title(&self) -> Option<&String> {
        self.attrs.title.as_ref()
    }

    /// Description of the struct, if any.
    ///
    /// The description specifically refers to the body portion of a doc comment, or the headline if
    /// only a headline exists.. For example, if a struct has the following doc comment:
    ///
    /// ```text
    /// /// My special struct.
    /// ///
    /// /// Here's why it's special:
    /// /// ...
    /// struct Wrapper(u64);
    /// ```
    ///
    /// then the title would be everything that comes after `My special struct`. If the doc comment
    /// only contained `My special struct.`, then the description would be `My special struct.`, and
    /// the title would be empty. In this way, the description will always be some port of a doc
    /// comment, depending on the formatting applied.
    ///
    /// This logic was chosen to mimic how Rust's own `rustdoc` tool works, where it will use the
    /// "title" portion as a high-level description for an item, only showing the title and
    /// description together when drilling down to the documentation for that specific item. JSON
    /// Schema supports both title and description for a schema, and so we expose both.
    pub fn description(&self) -> Option<&String> {
        self.attrs.description.as_ref()
    }

    /// Virtual type of this container, if any.
    ///
    /// In some cases, a type may be representable by an entirely different type, and then converted
    /// to the desired type using the common `TryFrom`/`From`/`Into` conversion traits. This is a
    /// common pattern with `serde` to re-use existing conversion logic (such as taking a raw string
    /// and parsing it to see if it's a valid regular expression, and so on) but represents a
    /// divergence between the type we're generating a schema for and the actual type that will be
    /// getting (de)serialized.
    ///
    /// When we detect a container with the right `serde` helper attributes (see the code in
    /// `from_derive_input` for details), we switch to treating this container as, for the purpose
    /// of schema generation, having the type specified by those helper attributes.
    pub fn virtual_newtype(&self) -> Option<Type> {
        self.virtual_newtype.clone()
    }

    /// Tagging mode of this container.
    ///
    /// When the container is an enum, `Some(..)` will be returned, where the value can be any of
    /// the four modes supported by `serde`: external, internal, adjacent, or none (untagged).
    ///
    /// When the container is a struct, `None` is returned.
    pub fn tagging(&self) -> Option<&Tagging> {
        self.tagging.as_ref()
    }

    /// Path to a function to call to generate a default value for the container, if any.
    ///
    /// This will boil down to something like `std::default::Default::default` or
    /// `name_of_in_scope_method_to_call`, where we generate code to actually call that path as a
    /// function to generate the default value we include in the schema for this container.
    pub fn default_value(&self) -> Option<ExprPath> {
        self.default_value.clone()
    }

    /// Whether or not the container is deprecated.
    ///
    /// Applying the `#[configurable(deprecated)]` helper attribute will mark this container as
    /// deprecated from the perspective of the resulting schema. It does not interact with Rust's
    /// standard `#[deprecated]` attribute, neither automatically applying it nor deriving the
    /// deprecation status of a field when it is present.
    pub fn deprecated(&self) -> bool {
        self.attrs.deprecated.is_present()
    }

    /// Metadata (custom attributes) for the container, if any.
    ///
    /// Attributes can take the shape of flags (`#[configurable(metadata(im_a_teapot))]`) or
    /// key/value pairs (`#[configurable(metadata(status = "beta"))]`) to allow rich, semantic
    /// metadata to be attached directly to containers.
    pub fn metadata(&self) -> impl Iterator<Item = LazyCustomAttribute> {
        self.attrs
            .metadata
            .clone()
            .into_iter()
            .flat_map(|metadata| metadata.attributes())
    }

    /// Gets the generic types that are used within fields or variants that are part of the schema.
    ///
    /// In order to ensure we can allow for a maximally flexible `Configurable` trait, we add bounds to generic types that are
    /// present on derived containers so that bounds don't need to be added on the actual container itself, essentially
    /// avoiding declarations like `pub struct Foo<T> where T: Configurable {...}`.
    ///
    /// We contain this logic here as we only care about generic type parameters that are present on fields that will be
    /// included in the schema, so skipped fields shouldn't have bounds added, and so on.
    pub fn generic_field_types(&self) -> Vec<TypeParam> {
        let mut generic_types = Vec::new();

        let field_types = match &self.data {
            Data::Struct(_, fields) => fields
                .iter()
                .filter(|f| f.visible())
                .filter_map(|f| get_generic_type_param_idents(f.ty()))
                .flatten()
                .collect::<HashSet<_>>(),
            Data::Enum(variants) => variants
                .iter()
                .filter(|v| v.visible())
                .flat_map(|v| v.fields().iter())
                .filter_map(|f| get_generic_type_param_idents(f.ty()))
                .flatten()
                .collect::<HashSet<_>>(),
        };

        for type_param in self.original.generics.type_params() {
            if field_types.contains(&type_param.ident) {
                generic_types.push(type_param.clone());
            }
        }

        generic_types
    }
}

#[derive(Debug, Default, FromAttributes)]
#[darling(default, attributes(configurable))]
struct Attributes {
    #[darling(default)]
    title: Option<String>,
    #[darling(default)]
    description: Option<String>,
    #[darling(default)]
    deprecated: Flag,
    #[darling(multiple)]
    metadata: Vec<Metadata>,
}

impl Attributes {
    fn finalize(mut self, forwarded_attrs: &[syn::Attribute]) -> darling::Result<Self> {
        // We additionally attempt to extract a title/description from the forwarded doc attributes, if they exist.
        // Whether we extract both a title and description, or just description, is documented in more detail in
        // `try_extract_doc_title_description` itself.
        let (doc_title, doc_description) = try_extract_doc_title_description(forwarded_attrs);
        self.title = self.title.or(doc_title);
        self.description = self.description.or(doc_description);

        Ok(self)
    }
}

/// Gets the idents for a type that potentially represent generic type parameters.
///
/// We use this function to take the `Type` of a field, and figure out if it has any generic type
/// parameters, such as the `T` in `Vec<T>`. As the type itself might be the generic parameter (just
/// a plain `T`) we potentially return the ident of the type itself unless we can determine that the
/// type path has generic type arguments.
fn get_generic_type_param_idents(ty: &Type) -> Option<Vec<Ident>> {
    match ty {
        Type::Path(tp) => match tp.path.segments.len() {
            0 => unreachable!(
                "A type path with no path segments should not be possible to construct normally."
            ),
            // A single path segment would be something like `String` or `Vec<T>`, so we
            // do need to check for both scenarios.
            1 => match tp.path.segments.first() {
                None => unreachable!("Can only reach match arm if segment length was 1."),
                Some(segment) => get_generic_args_from_path_segment(segment, true),
            },
            _ => {
                let idents = tp
                    .path
                    .segments
                    .iter()
                    .filter_map(|segment| get_generic_args_from_path_segment(segment, false))
                    .flatten()
                    .collect::<Vec<_>>();

                if idents.is_empty() {
                    None
                } else {
                    Some(idents)
                }
            }
        },
        _ => None,
    }
}

fn get_generic_args_from_path_segment(
    segment: &PathSegment,
    return_self: bool,
) -> Option<Vec<Ident>> {
    match &segment.arguments {
        // If the segment has no brackets/parens, return its ident as-is if we should return self.
        // When we're trying to parse a higher-level type path that has multiple segments, we
        // wouldn't want to return the segment's ident, because if we were parsing
        // `std::vec::Vec<T>`, that would lead to us returning `std`, `vec`, and `T`... the first
        // two of which would make no sense, obviously.
        PathArguments::None => {
            if return_self {
                Some(vec![segment.ident.clone()])
            } else {
                None
            }
        }
        PathArguments::AngleBracketed(angle_args) => {
            let args = angle_args
                .args
                .iter()
                .filter_map(|generic| match generic {
                    // We only care about generic type arguments.
                    GenericArgument::Type(gty) => get_generic_type_path_ident(gty),
                    _ => None,
                })
                .collect::<Vec<_>>();

            if args.is_empty() {
                None
            } else {
                Some(args)
            }
        }
        // We don't support parenthesized generic arguments as they only come up in the case of
        // function pointers, and we don't support those with `Configurable`.
        PathArguments::Parenthesized(_) => None,
    }
}

/// Gets the ident of a `Type` when it is a "path" type.
///
/// Path types look like `String` or `std::vec::Vec<T>`, and represent a type you could accept as a
/// generic type argument.
fn get_generic_type_path_ident(ty: &Type) -> Option<Ident> {
    match ty {
        Type::Path(tp) => tp.path.get_ident().cloned(),
        _ => None,
    }
}

#[cfg(test)]
mod tests {
    use proc_macro2::Ident;
    use quote::format_ident;
    use syn::{parse_quote, Type};

    use super::get_generic_type_param_idents;

    fn literals_to_idents(idents: &[&str]) -> Vec<Ident> {
        idents.iter().map(|raw| format_ident!("{}", raw)).collect()
    }

    #[test]
    fn test_get_generic_type_param_idents() {
        // A "direct" type reference, like a type that's already in scope, is a single ident so we
        // do want to capture that.
        let direct_concrete_type: Type = parse_quote! { String };
        let idents = get_generic_type_param_idents(&direct_concrete_type)
            .expect("idents should have been found");
        assert_eq!(literals_to_idents(&["String"]), idents);

        // Segmented type paths like this can't get represented as idents, which is also why they
        // can't possibly represent a generic type parameter, as a generic type parameter is always
        // a single ident, i.e. `T`.
        let qualified_concrete_type: Type = parse_quote! { std::string::String };
        let idents = get_generic_type_param_idents(&qualified_concrete_type);
        assert_eq!(None, idents);

        // This one is pretty obvious.
        let direct_generic_type: Type = parse_quote! { T };
        let idents = get_generic_type_param_idents(&direct_generic_type)
            .expect("idents should have been found");
        assert_eq!(literals_to_idents(&["T"]), idents);

        // We should always extract the generic type parameter, even for a "direct" type reference.
        let contained_generic_type: Type = parse_quote! { Vec<T> };
        let idents = get_generic_type_param_idents(&contained_generic_type)
            .expect("idents should have been found");
        assert_eq!(literals_to_idents(&["T"]), idents);

        // Similarly, we should always extract the generic type parameter for segmented type paths,
        // since we traverse all segments.
        let qualified_contained_generic_type: Type = parse_quote! { std::vec::Vec<T> };
        let idents = get_generic_type_param_idents(&qualified_contained_generic_type)
            .expect("idents should have been found");
        assert_eq!(literals_to_idents(&["T"]), idents);

        // We don't support parenthesized type parameters, like when using a function pointer type.
        let parenthesized_type: Type = parse_quote! { Something<fn(bool) -> String> };
        let idents = get_generic_type_param_idents(&parenthesized_type);
        assert_eq!(None, idents);
    }
}