sway_error/
warning.rs

1use crate::{
2    diagnostic::{Code, Diagnostic, Hint, Issue, Reason, ToDiagnostic},
3    formatting::{
4        did_you_mean_help, first_line, num_to_str, sequence_to_list, sequence_to_str, Enclosing,
5        Indent,
6    },
7};
8
9use core::fmt;
10
11use either::Either;
12
13use sway_types::{Ident, IdentUnique, SourceId, Span, Spanned};
14
15// TODO: since moving to using Idents instead of strings,
16// the warning_content will usually contain a duplicate of the span.
17#[derive(Debug, Clone, PartialEq, Eq, Hash)]
18pub struct CompileWarning {
19    pub span: Span,
20    pub warning_content: Warning,
21}
22
23impl Spanned for CompileWarning {
24    fn span(&self) -> Span {
25        self.span.clone()
26    }
27}
28
29impl CompileWarning {
30    pub fn to_friendly_warning_string(&self) -> String {
31        self.warning_content.to_string()
32    }
33
34    pub fn source_id(&self) -> Option<SourceId> {
35        self.span.source_id().cloned()
36    }
37}
38
39#[derive(Debug, Clone, PartialEq, Eq, Hash)]
40pub enum Warning {
41    NonClassCaseStructName {
42        struct_name: Ident,
43    },
44    NonClassCaseTypeParameter {
45        name: Ident,
46    },
47    NonClassCaseTraitName {
48        name: Ident,
49    },
50    NonClassCaseEnumName {
51        enum_name: Ident,
52    },
53    NonClassCaseEnumVariantName {
54        variant_name: Ident,
55    },
56    NonSnakeCaseStructFieldName {
57        field_name: Ident,
58    },
59    NonSnakeCaseFunctionName {
60        name: Ident,
61    },
62    NonScreamingSnakeCaseConstName {
63        name: Ident,
64    },
65    UnusedReturnValue {
66        r#type: String,
67    },
68    SimilarMethodFound {
69        lib: Ident,
70        module: Ident,
71        name: Ident,
72    },
73    ShadowsOtherSymbol {
74        name: Ident,
75    },
76    AsmBlockIsEmpty,
77    UninitializedAsmRegShadowsItem {
78        /// Text "Constant" or "Configurable" or "Variable".
79        /// Denotes the type of the `item` that shadows the uninitialized ASM register.
80        constant_or_configurable_or_variable: &'static str,
81        /// The name of the item that shadows the register, that points to the name in
82        /// the item declaration.
83        item: IdentUnique,
84    },
85    OverridingTraitImplementation,
86    DeadDeclaration,
87    DeadEnumDeclaration,
88    DeadFunctionDeclaration,
89    DeadStructDeclaration,
90    DeadTrait,
91    UnreachableCode,
92    DeadEnumVariant {
93        variant_name: Ident,
94    },
95    DeadMethod,
96    StructFieldNeverRead,
97    ShadowingReservedRegister {
98        reg_name: Ident,
99    },
100    DeadStorageDeclaration,
101    DeadStorageDeclarationForFunction {
102        unneeded_attrib: String,
103    },
104    MatchExpressionUnreachableArm {
105        match_value: Span,
106        match_type: String,
107        // Either preceding non catch-all arms or a single interior catch-all arm.
108        preceding_arms: Either<Vec<Span>, Span>,
109        unreachable_arm: Span,
110        is_last_arm: bool,
111        is_catch_all_arm: bool,
112    },
113    UnknownAttribute {
114        attribute: IdentUnique,
115        known_attributes: &'static [&'static str],
116    },
117    UnknownAttributeArg {
118        attribute: Ident,
119        arg: IdentUnique,
120        expected_args: Vec<&'static str>,
121    },
122    EffectAfterInteraction {
123        effect: String,
124        effect_in_suggestion: String,
125        block_name: Ident,
126    },
127    UsingDeprecated {
128        deprecated_element: DeprecatedElement,
129        deprecated_element_name: String,
130        help: Option<String>,
131    },
132    DuplicatedStorageKey {
133        first_field: IdentUnique,
134        first_field_full_name: String,
135        first_field_key_is_compiler_generated: bool,
136        second_field: IdentUnique,
137        second_field_full_name: String,
138        second_field_key_is_compiler_generated: bool,
139        key: String,
140    },
141    ErrorTypeEmptyEnum {
142        enum_name: IdentUnique,
143    },
144    ErrorEmptyErrorMessage {
145        enum_name: Ident,
146        enum_variant_name: Ident,
147    },
148    ErrorDuplicatedErrorMessage {
149        last_occurrence: Span,
150        previous_occurrences: Vec<Span>,
151    },
152}
153
154/// Elements that can be deprecated.
155#[derive(Debug, Clone, PartialEq, Eq, Hash)]
156pub enum DeprecatedElement {
157    Struct,
158    StructField,
159    Enum,
160    EnumVariant,
161    Function,
162    Const,
163    Configurable,
164}
165
166impl fmt::Display for DeprecatedElement {
167    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
168        match self {
169            Self::Struct => write!(f, "Struct"),
170            Self::StructField => write!(f, "Struct field"),
171            Self::Enum => write!(f, "Enum"),
172            Self::EnumVariant => write!(f, "Enum variant"),
173            Self::Function => write!(f, "Function"),
174            Self::Const => write!(f, "Constant"),
175            Self::Configurable => write!(f, "Configurable"),
176        }
177    }
178}
179
180impl fmt::Display for Warning {
181    // This trait requires `fmt` with this exact signature.
182    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
183        use sway_types::style::*;
184        use Warning::*;
185        match self {
186            NonClassCaseStructName { struct_name } => {
187                write!(f,
188                "Struct name \"{}\" is not idiomatic. Structs should have a ClassCase name, like \
189                 \"{}\".",
190                struct_name,
191                to_upper_camel_case(struct_name.as_str())
192            )
193            }
194            NonClassCaseTypeParameter { name } => {
195                write!(f,
196                "Type parameter \"{}\" is not idiomatic. TypeParameters should have a ClassCase name, like \
197                 \"{}\".",
198                name,
199                to_upper_camel_case(name.as_str())
200            )
201            }
202            NonClassCaseTraitName { name } => {
203                write!(f,
204                "Trait name \"{}\" is not idiomatic. Traits should have a ClassCase name, like \
205                 \"{}\".",
206                name,
207                to_upper_camel_case(name.as_str())
208            )
209            }
210            NonClassCaseEnumName { enum_name } => write!(
211                f,
212                "Enum \"{}\"'s capitalization is not idiomatic. Enums should have a ClassCase \
213                 name, like \"{}\".",
214                enum_name,
215                to_upper_camel_case(enum_name.as_str())
216            ),
217            NonSnakeCaseStructFieldName { field_name } => write!(
218                f,
219                "Struct field name \"{}\" is not idiomatic. Struct field names should have a \
220                 snake_case name, like \"{}\".",
221                field_name,
222                to_snake_case(field_name.as_str())
223            ),
224            NonClassCaseEnumVariantName { variant_name } => write!(
225                f,
226                "Enum variant name \"{}\" is not idiomatic. Enum variant names should be \
227                 ClassCase, like \"{}\".",
228                variant_name,
229                to_upper_camel_case(variant_name.as_str())
230            ),
231            NonSnakeCaseFunctionName { name } => {
232                write!(f,
233                "Function name \"{}\" is not idiomatic. Function names should be snake_case, like \
234                 \"{}\".",
235                name,
236                to_snake_case(name.as_str())
237            )
238            }
239            NonScreamingSnakeCaseConstName { name } => {
240                write!(
241                    f,
242                    "Constant name \"{name}\" is not idiomatic. Constant names should be SCREAMING_SNAKE_CASE, like \
243                    \"{}\".",
244                    to_screaming_snake_case(name.as_str()),
245                )
246            },
247            UnusedReturnValue { r#type } => write!(
248                f,
249                "This returns a value of type \"{type}\", which is not assigned to anything and is \
250                 ignored."
251            ),
252            SimilarMethodFound { lib, module, name } => write!(
253                f,
254                "A method with the same name was found for type {name} in dependency \"{lib}::{module}\". \
255                 Traits must be in scope in order to access their methods. "
256            ),
257            ShadowsOtherSymbol { name } => write!(
258                f,
259                "This shadows another symbol in this scope with the same name \"{name}\"."
260            ),
261            AsmBlockIsEmpty => write!(
262                f,
263                "This ASM block is empty."
264            ),
265            UninitializedAsmRegShadowsItem { constant_or_configurable_or_variable, item } => write!(
266                f,
267                "This uninitialized register is shadowing a {}. You probably meant to also initialize it, like \"{item}: {item}\".",
268                constant_or_configurable_or_variable.to_ascii_lowercase(),
269            ),
270            OverridingTraitImplementation => write!(
271                f,
272                "This trait implementation overrides another one that was previously defined."
273            ),
274            DeadDeclaration => write!(f, "This declaration is never used."),
275            DeadEnumDeclaration => write!(f, "This enum is never used."),
276            DeadStructDeclaration => write!(f, "This struct is never used."),
277            DeadFunctionDeclaration => write!(f, "This function is never called."),
278            UnreachableCode => write!(f, "This code is unreachable."),
279            DeadEnumVariant { variant_name } => {
280                write!(f, "Enum variant {variant_name} is never constructed.")
281            }
282            DeadTrait => write!(f, "This trait is never implemented."),
283            DeadMethod => write!(f, "This method is never called."),
284            StructFieldNeverRead => write!(f, "This struct field is never accessed."),
285            ShadowingReservedRegister { reg_name } => write!(
286                f,
287                "This register declaration shadows the reserved register, \"{reg_name}\"."
288            ),
289            DeadStorageDeclaration => write!(
290                f,
291                "This storage declaration is never accessed and can be removed."
292            ),
293            DeadStorageDeclarationForFunction { unneeded_attrib } => write!(
294                f,
295                "This function's storage attributes declaration does not match its \
296                 actual storage access pattern: '{unneeded_attrib}' attribute(s) can be removed."
297            ),
298            MatchExpressionUnreachableArm { .. } => write!(f, "This match arm is unreachable."),
299            UnknownAttribute { attribute, .. } => write!(f, "Unknown attribute \"{attribute}\"."),
300            UnknownAttributeArg { attribute, arg, expected_args } => write!(
301                f,
302                "\"{arg}\" is an unknown argument for attribute \"{attribute}\". Known arguments are: {}.", sequence_to_str(expected_args, Enclosing::DoubleQuote, usize::MAX)
303            ),
304            EffectAfterInteraction {effect, effect_in_suggestion, block_name} =>
305                write!(f, "{effect} after external contract interaction in function or method \"{block_name}\". \
306                          Consider {effect_in_suggestion} before calling another contract"),
307            UsingDeprecated { deprecated_element_name, deprecated_element, help } =>
308                write!(f, "{deprecated_element} \"{deprecated_element_name}\" is deprecated. {}", help.as_ref().unwrap_or(&"".into())),
309            DuplicatedStorageKey { first_field_full_name, second_field_full_name, key, .. } =>
310                write!(f, "Two storage fields have the same storage key.\nFirst field: {first_field_full_name}\nSecond field: {second_field_full_name}\nKey: {key}"),
311            ErrorTypeEmptyEnum { enum_name } =>
312                write!(f, "Empty error type enum \"{enum_name}\" can never be instantiated and used in `panic` expressions."),
313            ErrorEmptyErrorMessage { enum_name, enum_variant_name } =>
314                write!(f, "Error enum variant \"{enum_name}::{enum_variant_name}\" has an empty error message. Consider adding a helpful error message."),
315            ErrorDuplicatedErrorMessage { previous_occurrences, .. } =>
316                write!(f, "This error message is duplicated{}. Consider using a unique error message for every error variant.",
317                    if previous_occurrences.len() == 1 {
318                        "".to_string()
319                    } else {
320                        format!(" {} times", num_to_str(previous_occurrences.len()))
321                    }
322                ),
323        }
324    }
325}
326
327#[allow(dead_code)]
328const FUTURE_HARD_ERROR_HELP: &str =
329    "In future versions of Sway this warning will become a hard error.";
330
331impl ToDiagnostic for CompileWarning {
332    fn to_diagnostic(&self, source_engine: &sway_types::SourceEngine) -> Diagnostic {
333        let code = Code::warnings;
334        use sway_types::style::*;
335        use Warning::*;
336        match &self.warning_content {
337            NonScreamingSnakeCaseConstName { name } => Diagnostic {
338                reason: Some(Reason::new(code(1), "Constant name is not idiomatic".to_string())),
339                issue: Issue::warning(
340                    source_engine,
341                    name.span(),
342                    format!("Constant \"{name}\" should be SCREAMING_SNAKE_CASE."),
343                ),
344                hints: vec![
345                    Hint::help(
346                        source_engine,
347                        name.span(),
348                        format!("Consider renaming it to, e.g., \"{}\".", to_screaming_snake_case(name.as_str())),
349                    ),
350                ],
351                help: vec![
352                    format!("In Sway, ABIs, structs, traits, and enums are CapitalCase."),
353                    format!("Modules, variables, and functions are snake_case, while constants are SCREAMING_SNAKE_CASE."),
354                ],
355            },
356            MatchExpressionUnreachableArm { match_value, match_type, preceding_arms, unreachable_arm, is_last_arm, is_catch_all_arm } => Diagnostic {
357                reason: Some(Reason::new(code(1), "Match arm is unreachable".to_string())),
358                issue: Issue::warning(
359                    source_engine,
360                    unreachable_arm.clone(),
361                    match (*is_last_arm, *is_catch_all_arm) {
362                        (true, true) => format!("Last catch-all match arm `{}` is unreachable.", unreachable_arm.as_str()),
363                        _ => format!("Match arm `{}` is unreachable.", unreachable_arm.as_str())
364                    }
365                ),
366                hints: vec![
367                    Hint::info(
368                        source_engine,
369                        match_value.clone(),
370                        format!("The expression to match on is of type \"{match_type}\".")
371                    ),
372                    if preceding_arms.is_right() {
373                        Hint::help(
374                            source_engine,
375                            preceding_arms.as_ref().unwrap_right().clone(),
376                            format!("Catch-all arm `{}` makes all match arms below it unreachable.", preceding_arms.as_ref().unwrap_right().as_str())
377                        )
378                    }
379                    else {
380                        Hint::info(
381                            source_engine,
382                            Span::join_all(preceding_arms.as_ref().unwrap_left().clone()),
383                            if *is_last_arm {
384                                format!("Preceding match arms already match all possible values of `{}`.", match_value.as_str())
385                            }
386                            else {
387                                format!("Preceding match arms already match all the values that `{}` can match.", unreachable_arm.as_str())
388                            }
389                        )
390                    }
391                ],
392                help: if preceding_arms.is_right() {
393                    let catch_all_arm = preceding_arms.as_ref().unwrap_right().as_str();
394                    vec![
395                        format!("Catch-all patterns make sense only in last match arms."),
396                        format!("Consider removing the catch-all arm `{catch_all_arm}` or making it the last arm."),
397                        format!("Consider removing the unreachable arms below the `{catch_all_arm}` arm."),
398                    ]
399                }
400                else if *is_last_arm && *is_catch_all_arm {
401                    vec![
402                        format!("Catch-all patterns are often used in last match arms."),
403                        format!("But in this case, the preceding arms already match all possible values of `{}`.", match_value.as_str()),
404                        format!("Consider removing the unreachable last catch-all arm."),
405                    ]
406                }
407                else {
408                    vec![
409                        format!("Consider removing the unreachable arm."),
410                    ]
411                }
412            },
413            UninitializedAsmRegShadowsItem { constant_or_configurable_or_variable, item } => Diagnostic {
414                reason: Some(Reason::new(code(1), format!("Uninitialized ASM register is shadowing a {}", constant_or_configurable_or_variable.to_ascii_lowercase()))),
415                issue: Issue::warning(
416                    source_engine,
417                    self.span(),
418                    format!("Uninitialized register \"{item}\" is shadowing a {} of the same name.", constant_or_configurable_or_variable.to_ascii_lowercase()),
419                ),
420                hints: {
421                    let mut hints = vec![
422                        Hint::info(
423                            source_engine,
424                            item.span(),
425                            format!("{constant_or_configurable_or_variable} \"{item}\" is declared here.")
426                        ),
427                    ];
428
429                    hints.append(&mut Hint::multi_help(
430                        source_engine,
431                        &self.span(),
432                        vec![
433                            format!("Are you trying to initialize the register to the value of the {}?", constant_or_configurable_or_variable.to_ascii_lowercase()),
434                            format!("In that case, you must do it explicitly: `{item}: {item}`."),
435                            format!("Otherwise, to avoid the confusion with the shadowed {}, consider renaming the register \"{item}\".", constant_or_configurable_or_variable.to_ascii_lowercase()),
436                        ]
437                    ));
438
439                    hints
440                },
441                help: vec![],
442            },
443            AsmBlockIsEmpty => Diagnostic {
444                reason: Some(Reason::new(code(1), "ASM block is empty".to_string())),
445                issue: Issue::warning(
446                    source_engine,
447                    self.span(),
448                    "This ASM block is empty.".to_string(),
449                ),
450                hints: vec![],
451                help: vec![
452                    "Consider adding assembly instructions or a return register to the ASM block, or removing the block altogether.".to_string(),
453                ],
454            },
455            DuplicatedStorageKey { first_field, first_field_full_name, first_field_key_is_compiler_generated, second_field, second_field_full_name, second_field_key_is_compiler_generated, key } => Diagnostic {
456                reason: Some(Reason::new(code(1), "Two storage fields have the same storage key".to_string())),
457                issue: Issue::warning(
458                    source_engine,
459                    first_field.span(),
460                    format!("\"{first_field_full_name}\" has the same storage key as \"{second_field_full_name}\"."),
461                ),
462                hints: vec![
463                    Hint::info(
464                        source_engine,
465                        second_field.span(),
466                        format!("\"{second_field_full_name}\" is declared here."),
467                    ),
468                ],
469                help: vec![
470                    if *first_field_key_is_compiler_generated || *second_field_key_is_compiler_generated {
471                        format!("The key of \"{}\" is generated by the compiler using the following formula:",
472                            if *first_field_key_is_compiler_generated {
473                                first_field_full_name
474                            } else {
475                                second_field_full_name
476                            }
477                        )
478                    } else {
479                        "Both keys are explicitly defined by using the `in` keyword.".to_string()
480                    },
481                    if *first_field_key_is_compiler_generated || *second_field_key_is_compiler_generated {
482                        format!("{}sha256((0u8, \"{}\"))",
483                            Indent::Single,
484                            if *first_field_key_is_compiler_generated {
485                                first_field_full_name
486                            } else {
487                                second_field_full_name
488                            }
489                        )
490                    } else {
491                        Diagnostic::help_none()
492                    },
493                    format!("The common key is: {key}.")
494                ],
495            },
496            UnknownAttribute { attribute, known_attributes } => Diagnostic {
497                reason: Some(Reason::new(code(1), "Attribute is unknown".to_string())),
498                issue: Issue::warning(
499                    source_engine,
500                    attribute.span(),
501                    format!("\"{attribute}\" attribute is unknown.")
502                ),
503                hints: vec![did_you_mean_help(source_engine, attribute.span(), known_attributes.iter(), 2, Enclosing::DoubleQuote)],
504                help: vec![
505                    "Unknown attributes are allowed and can be used by third-party tools,".to_string(),
506                    "but the compiler ignores them.".to_string(),
507                ],
508            },
509            UnknownAttributeArg { attribute, arg, expected_args } => Diagnostic {
510                reason: Some(Reason::new(code(1), "Attribute argument is unknown".to_string())),
511                issue: Issue::warning(
512                    source_engine,
513                    arg.span(),
514                    format!("\"{arg}\" is an unknown argument for attribute \"{attribute}\".")
515                ),
516                hints: {
517                    let mut hints = vec![did_you_mean_help(source_engine, arg.span(), expected_args, 2, Enclosing::DoubleQuote)];
518                    if expected_args.len() == 1 {
519                        hints.push(Hint::help(source_engine, arg.span(), format!("The only known argument is \"{}\".", expected_args[0])));
520                    } else if expected_args.len() <= 3 {
521                        hints.push(Hint::help(source_engine, arg.span(), format!("Known arguments are {}.", sequence_to_str(expected_args, Enclosing::DoubleQuote, usize::MAX))));
522                    } else {
523                        hints.push(Hint::help(source_engine, arg.span(), "Known arguments are:".to_string()));
524                        hints.append(&mut Hint::multi_help(source_engine, &arg.span(), sequence_to_list(expected_args, Indent::Single, usize::MAX)))
525                    }
526                    hints
527                },
528                help: vec![
529                    format!("Unknown attribute arguments are allowed for some attributes like \"{attribute}\"."),
530                    "They can be used by third-party tools, but the compiler ignores them.".to_string(),
531                ],
532            },
533            UsingDeprecated { deprecated_element, deprecated_element_name, help } => Diagnostic {
534                reason: Some(Reason::new(code(1), format!("{deprecated_element} is deprecated"))),
535                issue: Issue::warning(
536                    source_engine,
537                    self.span(),
538                    format!("{deprecated_element} \"{deprecated_element_name}\" is deprecated."),
539                ),
540                hints: help.as_ref().map_or(vec![], |help| vec![
541                    Hint::help(
542                        source_engine,
543                        self.span(),
544                        help.clone(),
545                    ),
546                ]),
547                help: vec![],
548            },
549            ErrorTypeEmptyEnum { enum_name } => Diagnostic {
550                reason: Some(Reason::new(code(1), "Empty error type enum cannot be used in `panic` expressions".to_string())),
551                issue: Issue::warning(
552                    source_engine,
553                    enum_name.span(),
554                    format!("Error type enum \"{enum_name}\" is empty and can never be used in `panic` expressions."),
555                ),
556                hints: vec![],
557                help: vec![
558                    "Empty enums with no enum variants can never be instantiated.".to_string(),
559                    "Thus, they cannot have instances to use as arguments in `panic` expressions.".to_string(),
560                    Diagnostic::help_empty_line(),
561                    format!("Consider adding enum variants to \"{enum_name}\" and attributing them"),
562                    "with the `#[error]` attribute.".to_string(),
563                ],
564            },
565            ErrorEmptyErrorMessage { enum_name, enum_variant_name } => Diagnostic {
566                reason: Some(Reason::new(code(1), "Error message is empty".to_string())),
567                issue: Issue::warning(
568                    source_engine,
569                    self.span(),
570                    format!("Error enum variant \"{enum_name}::{enum_variant_name}\" has an empty error message."),
571                ),
572                hints: vec![
573                    Hint::help(
574                        source_engine,
575                        self.span(),
576                        "Consider adding a helpful error message here.".to_string(),
577                    )
578                ],
579                help: vec![],
580            },
581            ErrorDuplicatedErrorMessage { last_occurrence, previous_occurrences } => Diagnostic {
582                reason: Some(Reason::new(code(1), "Error message is duplicated".to_string())),
583                issue: Issue::error(
584                    source_engine,
585                    last_occurrence.clone(),
586                    "This error message is duplicated.".to_string(),
587                ),
588                hints: {
589                    let (first_occurrence, other_occurrences) = previous_occurrences.split_first().expect("there is at least one previous occurrence in `previous_occurrences`");
590                    let mut hints = vec![Hint::info(source_engine, first_occurrence.clone(), "It is already used here.".to_string())];
591                    other_occurrences.iter().for_each(|occurrence| hints.push(Hint::info(source_engine, occurrence.clone(), "And here.".to_string())));
592                    hints.push(Hint::help(source_engine, last_occurrence.clone(), "Consider using a unique error message for every error variant.".to_string()));
593                    hints
594                },
595                help: vec![],
596            },
597            UnusedReturnValue { r#type } => Diagnostic {
598                reason: Some(Reason::new(code(1), "Returned value is ignored".to_string())),
599                issue: Issue::warning(
600                    source_engine,
601                    self.span(),
602                    "This returns a value which is not assigned to anything and is ignored.".to_string(),
603                ),
604                hints: vec![
605                    Hint::help(
606                        source_engine,
607                        self.span(),
608                        format!("The returned value has type \"{type}\"."),
609                    )
610                ],
611                help: vec![
612                    "If you want to intentionally ignore the returned value, use `let _ = ...`:".to_string(),
613                    format!("{}let _ = {};", Indent::Single, first_line(self.span.as_str(), true)),
614                ],
615            },
616           _ => Diagnostic {
617                    // TODO: Temporarily we use self here to achieve backward compatibility.
618                    //       In general, self must not be used and will not be used once we
619                    //       switch to our own #[error] macro. All the values for the formatting
620                    //       of a diagnostic must come from the enum variant parameters.
621                    issue: Issue::warning(source_engine, self.span(), format!("{}", self.warning_content)),
622                    ..Default::default()
623                }
624        }
625    }
626}
627
628#[derive(Debug, Clone, PartialEq, Eq, Hash)]
629pub struct CollectedTraitImpl {
630    pub impl_span: Span,
631    pub trait_name: String,
632}
633
634#[derive(Debug, Clone, PartialEq, Eq, Hash)]
635pub enum Info {
636    ImplTraitsForType { impls: Vec<CollectedTraitImpl> },
637}
638
639impl fmt::Display for Info {
640    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
641        use Info::*;
642        match self {
643            ImplTraitsForType { impls } => {
644                write!(
645                    f,
646                    "Implemented traits: \"{:?}\"",
647                    impls
648                        .iter()
649                        .map(|i| i.impl_span.as_str())
650                        .collect::<Vec<_>>()
651                )
652            }
653        }
654    }
655}
656
657#[derive(Debug, Clone, PartialEq, Eq, Hash)]
658pub struct CompileInfo {
659    pub span: Span,
660    pub content: Info,
661}
662
663impl Spanned for CompileInfo {
664    fn span(&self) -> Span {
665        self.span.clone()
666    }
667}
668
669impl CompileInfo {
670    pub fn source_id(&self) -> Option<SourceId> {
671        self.span.source_id().cloned()
672    }
673
674    pub fn to_friendly_string(&self) -> String {
675        self.content.to_string()
676    }
677}
678
679impl ToDiagnostic for CompileInfo {
680    fn to_diagnostic(&self, source_engine: &sway_types::SourceEngine) -> Diagnostic {
681        let code = Code::warnings;
682        use Info::*;
683        match &self.content {
684            ImplTraitsForType { impls } => Diagnostic {
685                reason: Some(Reason::new(
686                    code(1),
687                    "Trait impls dump was requested.".to_string(),
688                )),
689                issue: Issue::info(
690                    source_engine,
691                    self.span(),
692                    "Matching implemented traits for this type.".to_string(),
693                ),
694                hints: impls
695                    .iter()
696                    .map(|i| {
697                        Hint::help(
698                            source_engine,
699                            i.impl_span.clone(),
700                            format!("trait is {}", i.trait_name.clone()),
701                        )
702                    })
703                    .collect::<Vec<_>>(),
704                help: vec![],
705            },
706        }
707    }
708}
709
710#[cfg(test)]
711mod test {
712    use sway_types::style::*;
713
714    #[test]
715    fn detect_styles() {
716        let snake_cases = [
717            "hello",
718            "__hello",
719            "blah32",
720            "some_words_here",
721            "___some_words_here",
722        ];
723        let screaming_snake_cases = ["SOME_WORDS_HERE", "___SOME_WORDS_HERE"];
724        let upper_camel_cases = [
725            "Hello",
726            "__Hello",
727            "Blah32",
728            "SomeWordsHere",
729            "___SomeWordsHere",
730        ];
731        let screaming_snake_case_or_upper_camel_case_idents = ["HELLO", "__HELLO", "BLAH32"];
732        let styleless_idents = ["Mix_Of_Things", "__Mix_Of_Things", "FooBar_123"];
733        for name in &snake_cases {
734            assert!(is_snake_case(name));
735            assert!(!is_screaming_snake_case(name));
736            assert!(!is_upper_camel_case(name));
737        }
738        for name in &screaming_snake_cases {
739            assert!(!is_snake_case(name));
740            assert!(is_screaming_snake_case(name));
741            assert!(!is_upper_camel_case(name));
742        }
743        for name in &upper_camel_cases {
744            assert!(!is_snake_case(name));
745            assert!(!is_screaming_snake_case(name));
746            assert!(is_upper_camel_case(name));
747        }
748        for name in &screaming_snake_case_or_upper_camel_case_idents {
749            assert!(!is_snake_case(name));
750            assert!(is_screaming_snake_case(name));
751            assert!(is_upper_camel_case(name));
752        }
753        for name in &styleless_idents {
754            assert!(!is_snake_case(name));
755            assert!(!is_screaming_snake_case(name));
756            assert!(!is_upper_camel_case(name));
757        }
758    }
759
760    #[test]
761    fn convert_to_snake_case() {
762        assert_eq!("hello", to_snake_case("HELLO"));
763        assert_eq!("___hello", to_snake_case("___HELLO"));
764        assert_eq!("blah32", to_snake_case("BLAH32"));
765        assert_eq!("some_words_here", to_snake_case("SOME_WORDS_HERE"));
766        assert_eq!("___some_words_here", to_snake_case("___SOME_WORDS_HERE"));
767        assert_eq!("hello", to_snake_case("Hello"));
768        assert_eq!("___hello", to_snake_case("___Hello"));
769        assert_eq!("blah32", to_snake_case("Blah32"));
770        assert_eq!("some_words_here", to_snake_case("SomeWordsHere"));
771        assert_eq!("___some_words_here", to_snake_case("___SomeWordsHere"));
772        assert_eq!("some_words_here", to_snake_case("someWordsHere"));
773        assert_eq!("___some_words_here", to_snake_case("___someWordsHere"));
774        assert_eq!("mix_of_things", to_snake_case("Mix_Of_Things"));
775        assert_eq!("__mix_of_things", to_snake_case("__Mix_Of_Things"));
776        assert_eq!("foo_bar_123", to_snake_case("FooBar_123"));
777    }
778
779    #[test]
780    fn convert_to_screaming_snake_case() {
781        assert_eq!("HELLO", to_screaming_snake_case("hello"));
782        assert_eq!("___HELLO", to_screaming_snake_case("___hello"));
783        assert_eq!("BLAH32", to_screaming_snake_case("blah32"));
784        assert_eq!(
785            "SOME_WORDS_HERE",
786            to_screaming_snake_case("some_words_here")
787        );
788        assert_eq!(
789            "___SOME_WORDS_HERE",
790            to_screaming_snake_case("___some_words_here")
791        );
792        assert_eq!("HELLO", to_screaming_snake_case("Hello"));
793        assert_eq!("___HELLO", to_screaming_snake_case("___Hello"));
794        assert_eq!("BLAH32", to_screaming_snake_case("Blah32"));
795        assert_eq!("SOME_WORDS_HERE", to_screaming_snake_case("SomeWordsHere"));
796        assert_eq!(
797            "___SOME_WORDS_HERE",
798            to_screaming_snake_case("___SomeWordsHere")
799        );
800        assert_eq!("SOME_WORDS_HERE", to_screaming_snake_case("someWordsHere"));
801        assert_eq!(
802            "___SOME_WORDS_HERE",
803            to_screaming_snake_case("___someWordsHere")
804        );
805        assert_eq!("MIX_OF_THINGS", to_screaming_snake_case("Mix_Of_Things"));
806        assert_eq!(
807            "__MIX_OF_THINGS",
808            to_screaming_snake_case("__Mix_Of_Things")
809        );
810        assert_eq!("FOO_BAR_123", to_screaming_snake_case("FooBar_123"));
811    }
812
813    #[test]
814    fn convert_to_upper_camel_case() {
815        assert_eq!("Hello", to_upper_camel_case("hello"));
816        assert_eq!("___Hello", to_upper_camel_case("___hello"));
817        assert_eq!("Blah32", to_upper_camel_case("blah32"));
818        assert_eq!("SomeWordsHere", to_upper_camel_case("some_words_here"));
819        assert_eq!(
820            "___SomeWordsHere",
821            to_upper_camel_case("___some_words_here")
822        );
823        assert_eq!("Hello", to_upper_camel_case("HELLO"));
824        assert_eq!("___Hello", to_upper_camel_case("___HELLO"));
825        assert_eq!("Blah32", to_upper_camel_case("BLAH32"));
826        assert_eq!("SomeWordsHere", to_upper_camel_case("SOME_WORDS_HERE"));
827        assert_eq!(
828            "___SomeWordsHere",
829            to_upper_camel_case("___SOME_WORDS_HERE")
830        );
831        assert_eq!("SomeWordsHere", to_upper_camel_case("someWordsHere"));
832        assert_eq!("___SomeWordsHere", to_upper_camel_case("___someWordsHere"));
833        assert_eq!("MixOfThings", to_upper_camel_case("Mix_Of_Things"));
834        assert_eq!("__MixOfThings", to_upper_camel_case("__Mix_Of_Things"));
835        assert_eq!("FooBar123", to_upper_camel_case("FooBar_123"));
836    }
837}