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