sway_error/
warning.rs

1use crate::{
2    diagnostic::{Code, Diagnostic, Hint, Issue, Reason, ToDiagnostic},
3    formatting::Indent,
4};
5
6use core::fmt;
7
8use either::Either;
9
10use sway_types::{Ident, IdentUnique, SourceId, Span, Spanned};
11
12// TODO: since moving to using Idents instead of strings,
13// the warning_content will usually contain a duplicate of the span.
14#[derive(Debug, Clone, PartialEq, Eq, Hash)]
15pub struct CompileWarning {
16    pub span: Span,
17    pub warning_content: Warning,
18}
19
20impl Spanned for CompileWarning {
21    fn span(&self) -> Span {
22        self.span.clone()
23    }
24}
25
26impl CompileWarning {
27    pub fn to_friendly_warning_string(&self) -> String {
28        self.warning_content.to_string()
29    }
30
31    pub fn source_id(&self) -> Option<SourceId> {
32        self.span.source_id().cloned()
33    }
34}
35
36#[derive(Debug, Clone, PartialEq, Eq, Hash)]
37pub enum Warning {
38    NonClassCaseStructName {
39        struct_name: Ident,
40    },
41    NonClassCaseTypeParameter {
42        name: Ident,
43    },
44    NonClassCaseTraitName {
45        name: Ident,
46    },
47    NonClassCaseEnumName {
48        enum_name: Ident,
49    },
50    NonClassCaseEnumVariantName {
51        variant_name: Ident,
52    },
53    NonSnakeCaseStructFieldName {
54        field_name: Ident,
55    },
56    NonSnakeCaseFunctionName {
57        name: Ident,
58    },
59    NonScreamingSnakeCaseConstName {
60        name: Ident,
61    },
62    UnusedReturnValue {
63        r#type: String,
64    },
65    SimilarMethodFound {
66        lib: Ident,
67        module: Ident,
68        name: Ident,
69    },
70    ShadowsOtherSymbol {
71        name: Ident,
72    },
73    AsmBlockIsEmpty,
74    UninitializedAsmRegShadowsItem {
75        /// Text "Constant" or "Configurable" or "Variable".
76        /// Denotes the type of the `item` that shadows the uninitialized ASM register.
77        constant_or_configurable_or_variable: &'static str,
78        /// The name of the item that shadows the register, that points to the name in
79        /// the item declaration.
80        item: IdentUnique,
81    },
82    OverridingTraitImplementation,
83    DeadDeclaration,
84    DeadEnumDeclaration,
85    DeadFunctionDeclaration,
86    DeadStructDeclaration,
87    DeadTrait,
88    UnreachableCode,
89    DeadEnumVariant {
90        variant_name: Ident,
91    },
92    DeadMethod,
93    StructFieldNeverRead,
94    ShadowingReservedRegister {
95        reg_name: Ident,
96    },
97    DeadStorageDeclaration,
98    DeadStorageDeclarationForFunction {
99        unneeded_attrib: String,
100    },
101    MatchExpressionUnreachableArm {
102        match_value: Span,
103        match_type: String,
104        // Either preceding non catch-all arms or a single interior catch-all arm.
105        preceding_arms: Either<Vec<Span>, Span>,
106        unreachable_arm: Span,
107        is_last_arm: bool,
108        is_catch_all_arm: bool,
109    },
110    UnrecognizedAttribute {
111        attrib_name: Ident,
112    },
113    AttributeExpectedNumberOfArguments {
114        attrib_name: Ident,
115        received_args: usize,
116        expected_min_len: usize,
117        expected_max_len: Option<usize>,
118    },
119    UnexpectedAttributeArgumentValue {
120        attrib_name: Ident,
121        received_value: String,
122        expected_values: Vec<String>,
123    },
124    EffectAfterInteraction {
125        effect: String,
126        effect_in_suggestion: String,
127        block_name: Ident,
128    },
129    ModulePrivacyDisabled,
130    UsingDeprecated {
131        message: String,
132    },
133    DuplicatedStorageKey {
134        first_field: IdentUnique,
135        first_field_full_name: String,
136        first_field_key_is_compiler_generated: bool,
137        second_field: IdentUnique,
138        second_field_full_name: String,
139        second_field_key_is_compiler_generated: bool,
140        key: String,
141        // True if the experimental feature `storage_domains` is used.
142        experimental_storage_domains: bool,
143    },
144}
145
146impl fmt::Display for Warning {
147    // This trait requires `fmt` with this exact signature.
148    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
149        use sway_types::style::*;
150        use Warning::*;
151        match self {
152            NonClassCaseStructName { struct_name } => {
153                write!(f,
154                "Struct name \"{}\" is not idiomatic. Structs should have a ClassCase name, like \
155                 \"{}\".",
156                struct_name,
157                to_upper_camel_case(struct_name.as_str())
158            )
159            }
160            NonClassCaseTypeParameter { name } => {
161                write!(f,
162                "Type parameter \"{}\" is not idiomatic. TypeParameters should have a ClassCase name, like \
163                 \"{}\".",
164                name,
165                to_upper_camel_case(name.as_str())
166            )
167            }
168            NonClassCaseTraitName { name } => {
169                write!(f,
170                "Trait name \"{}\" is not idiomatic. Traits should have a ClassCase name, like \
171                 \"{}\".",
172                name,
173                to_upper_camel_case(name.as_str())
174            )
175            }
176            NonClassCaseEnumName { enum_name } => write!(
177                f,
178                "Enum \"{}\"'s capitalization is not idiomatic. Enums should have a ClassCase \
179                 name, like \"{}\".",
180                enum_name,
181                to_upper_camel_case(enum_name.as_str())
182            ),
183            NonSnakeCaseStructFieldName { field_name } => write!(
184                f,
185                "Struct field name \"{}\" is not idiomatic. Struct field names should have a \
186                 snake_case name, like \"{}\".",
187                field_name,
188                to_snake_case(field_name.as_str())
189            ),
190            NonClassCaseEnumVariantName { variant_name } => write!(
191                f,
192                "Enum variant name \"{}\" is not idiomatic. Enum variant names should be \
193                 ClassCase, like \"{}\".",
194                variant_name,
195                to_upper_camel_case(variant_name.as_str())
196            ),
197            NonSnakeCaseFunctionName { name } => {
198                write!(f,
199                "Function name \"{}\" is not idiomatic. Function names should be snake_case, like \
200                 \"{}\".",
201                name,
202                to_snake_case(name.as_str())
203            )
204            }
205            NonScreamingSnakeCaseConstName { name } => {
206                write!(
207                    f,
208                    "Constant name \"{name}\" is not idiomatic. Constant names should be SCREAMING_SNAKE_CASE, like \
209                    \"{}\".",
210                    to_screaming_snake_case(name.as_str()),
211                )
212            },
213            UnusedReturnValue { r#type } => write!(
214                f,
215                "This returns a value of type {type}, which is not assigned to anything and is \
216                 ignored."
217            ),
218            SimilarMethodFound { lib, module, name } => write!(
219                f,
220                "A method with the same name was found for type {name} in dependency \"{lib}::{module}\". \
221                 Traits must be in scope in order to access their methods. "
222            ),
223            ShadowsOtherSymbol { name } => write!(
224                f,
225                "This shadows another symbol in this scope with the same name \"{name}\"."
226            ),
227            AsmBlockIsEmpty => write!(
228                f,
229                "This ASM block is empty."
230            ),
231            UninitializedAsmRegShadowsItem { constant_or_configurable_or_variable, item } => write!(
232                f,
233                "This uninitialized register is shadowing a {}. You probably meant to also initialize it, like \"{item}: {item}\".",
234                constant_or_configurable_or_variable.to_ascii_lowercase(),
235            ),
236            OverridingTraitImplementation => write!(
237                f,
238                "This trait implementation overrides another one that was previously defined."
239            ),
240            DeadDeclaration => write!(f, "This declaration is never used."),
241            DeadEnumDeclaration => write!(f, "This enum is never used."),
242            DeadStructDeclaration => write!(f, "This struct is never used."),
243            DeadFunctionDeclaration => write!(f, "This function is never called."),
244            UnreachableCode => write!(f, "This code is unreachable."),
245            DeadEnumVariant { variant_name } => {
246                write!(f, "Enum variant {variant_name} is never constructed.")
247            }
248            DeadTrait => write!(f, "This trait is never implemented."),
249            DeadMethod => write!(f, "This method is never called."),
250            StructFieldNeverRead => write!(f, "This struct field is never accessed."),
251            ShadowingReservedRegister { reg_name } => write!(
252                f,
253                "This register declaration shadows the reserved register, \"{reg_name}\"."
254            ),
255            DeadStorageDeclaration => write!(
256                f,
257                "This storage declaration is never accessed and can be removed."
258            ),
259            DeadStorageDeclarationForFunction { unneeded_attrib } => write!(
260                f,
261                "This function's storage attributes declaration does not match its \
262                 actual storage access pattern: '{unneeded_attrib}' attribute(s) can be removed."
263            ),
264            MatchExpressionUnreachableArm { .. } => write!(f, "This match arm is unreachable."),
265            UnrecognizedAttribute {attrib_name} => write!(f, "Unknown attribute: \"{attrib_name}\"."),
266            AttributeExpectedNumberOfArguments {attrib_name, received_args, expected_min_len, expected_max_len } => write!(
267                f,
268                "Attribute: \"{attrib_name}\" expected {} argument(s) received {received_args}.",
269                if let Some(expected_max_len) = expected_max_len {
270                    if expected_min_len == expected_max_len {
271                        format!("exactly {expected_min_len}")
272                    } else {
273                        format!("between {expected_min_len} and {expected_max_len}")
274                    }
275                } else {
276                    format!("at least {expected_min_len}")
277                }
278            ),
279            UnexpectedAttributeArgumentValue {attrib_name, received_value, expected_values } => write!(
280                f,
281                "Unexpected attribute value: \"{received_value}\" for attribute: \"{attrib_name}\" expected value {}",
282                expected_values.iter().map(|v| format!("\"{v}\"")).collect::<Vec<_>>().join(" or ")
283            ),
284            EffectAfterInteraction {effect, effect_in_suggestion, block_name} =>
285                write!(f, "{effect} after external contract interaction in function or method \"{block_name}\". \
286                          Consider {effect_in_suggestion} before calling another contract"),
287            ModulePrivacyDisabled => write!(f, "Module privacy rules will soon change to make modules private by default.
288                                            You can enable the new behavior with the --experimental-private-modules flag, which will become the default behavior in a later release.
289                                            More details are available in the related RFC: https://github.com/FuelLabs/sway-rfcs/blob/master/rfcs/0008-private-modules.md"),
290            UsingDeprecated { message } => write!(f, "{}", message),
291            DuplicatedStorageKey { first_field_full_name, second_field_full_name, key, .. } =>
292                write!(f, "Two storage fields have the same storage key.\nFirst field: {first_field_full_name}\nSecond field: {second_field_full_name}\nKey: {key}"),
293        }
294    }
295}
296
297#[allow(dead_code)]
298const FUTURE_HARD_ERROR_HELP: &str =
299    "In future versions of Sway this warning will become a hard error.";
300
301impl ToDiagnostic for CompileWarning {
302    fn to_diagnostic(&self, source_engine: &sway_types::SourceEngine) -> Diagnostic {
303        let code = Code::warnings;
304        use sway_types::style::*;
305        use Warning::*;
306        match &self.warning_content {
307            NonScreamingSnakeCaseConstName { name } => Diagnostic {
308                reason: Some(Reason::new(code(1), "Constant name is not idiomatic".to_string())),
309                issue: Issue::warning(
310                    source_engine,
311                    name.span(),
312                    format!("Constant \"{name}\" should be SCREAMING_SNAKE_CASE."),
313                ),
314                hints: vec![
315                    Hint::help(
316                        source_engine,
317                        name.span(),
318                        format!("Consider renaming it to, e.g., \"{}\".", to_screaming_snake_case(name.as_str())),
319                    ),
320                ],
321                help: vec![
322                    format!("In Sway, ABIs, structs, traits, and enums are CapitalCase."),
323                    format!("Modules, variables, and functions are snake_case, while constants are SCREAMING_SNAKE_CASE."),
324                ],
325            },
326            MatchExpressionUnreachableArm { match_value, match_type, preceding_arms, unreachable_arm, is_last_arm, is_catch_all_arm } => Diagnostic {
327                reason: Some(Reason::new(code(1), "Match arm is unreachable".to_string())),
328                issue: Issue::warning(
329                    source_engine,
330                    unreachable_arm.clone(),
331                    match (*is_last_arm, *is_catch_all_arm) {
332                        (true, true) => format!("Last catch-all match arm `{}` is unreachable.", unreachable_arm.as_str()),
333                        _ => format!("Match arm `{}` is unreachable.", unreachable_arm.as_str())
334                    }
335                ),
336                hints: vec![
337                    Hint::info(
338                        source_engine,
339                        match_value.clone(),
340                        format!("The expression to match on is of type \"{match_type}\".")
341                    ),
342                    if preceding_arms.is_right() {
343                        Hint::help(
344                            source_engine,
345                            preceding_arms.as_ref().unwrap_right().clone(),
346                            format!("Catch-all arm `{}` makes all match arms below it unreachable.", preceding_arms.as_ref().unwrap_right().as_str())
347                        )
348                    }
349                    else {
350                        Hint::info(
351                            source_engine,
352                            Span::join_all(preceding_arms.as_ref().unwrap_left().clone()),
353                            if *is_last_arm {
354                                format!("Preceding match arms already match all possible values of `{}`.", match_value.as_str())
355                            }
356                            else {
357                                format!("Preceding match arms already match all the values that `{}` can match.", unreachable_arm.as_str())
358                            }
359                        )
360                    }
361                ],
362                help: if preceding_arms.is_right() {
363                    let catch_all_arm = preceding_arms.as_ref().unwrap_right().as_str();
364                    vec![
365                        format!("Catch-all patterns make sense only in last match arms."),
366                        format!("Consider removing the catch-all arm `{catch_all_arm}` or making it the last arm."),
367                        format!("Consider removing the unreachable arms below the `{catch_all_arm}` arm."),
368                    ]
369                }
370                else if *is_last_arm && *is_catch_all_arm {
371                    vec![
372                        format!("Catch-all patterns are often used in last match arms."),
373                        format!("But in this case, the preceding arms already match all possible values of `{}`.", match_value.as_str()),
374                        format!("Consider removing the unreachable last catch-all arm."),
375                    ]
376                }
377                else {
378                    vec![
379                        format!("Consider removing the unreachable arm."),
380                    ]
381                }
382            },
383            UninitializedAsmRegShadowsItem { constant_or_configurable_or_variable, item } => Diagnostic {
384                reason: Some(Reason::new(code(1), format!("Uninitialized ASM register is shadowing a {}", constant_or_configurable_or_variable.to_ascii_lowercase()))),
385                issue: Issue::warning(
386                    source_engine,
387                    self.span(),
388                    format!("Uninitialized register \"{item}\" is shadowing a {} of the same name.", constant_or_configurable_or_variable.to_ascii_lowercase()),
389                ),
390                hints: {
391                    let mut hints = vec![
392                        Hint::info(
393                            source_engine,
394                            item.span(),
395                            format!("{constant_or_configurable_or_variable} \"{item}\" is declared here.")
396                        ),
397                    ];
398
399                    hints.append(&mut Hint::multi_help(
400                        source_engine,
401                        &self.span(),
402                        vec![
403                            format!("Are you trying to initialize the register to the value of the {}?", constant_or_configurable_or_variable.to_ascii_lowercase()),
404                            format!("In that case, you must do it explicitly: `{item}: {item}`."),
405                            format!("Otherwise, to avoid the confusion with the shadowed {}, consider renaming the register \"{item}\".", constant_or_configurable_or_variable.to_ascii_lowercase()),
406                        ]
407                    ));
408
409                    hints
410                },
411                help: vec![],
412            },
413            AsmBlockIsEmpty => Diagnostic {
414                reason: Some(Reason::new(code(1), "ASM block is empty".to_string())),
415                issue: Issue::warning(
416                    source_engine,
417                    self.span(),
418                    "This ASM block is empty.".to_string(),
419                ),
420                hints: vec![],
421                help: vec![
422                    "Consider adding assembly instructions or a return register to the ASM block, or removing the block altogether.".to_string(),
423                ],
424            },
425            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, experimental_storage_domains } => Diagnostic {
426                reason: Some(Reason::new(code(1), "Two storage fields have the same storage key".to_string())),
427                issue: Issue::warning(
428                    source_engine,
429                    first_field.span(),
430                    format!("\"{first_field_full_name}\" has the same storage key as \"{second_field_full_name}\"."),
431                ),
432                hints: vec![
433                    Hint::info(
434                        source_engine,
435                        second_field.span(),
436                        format!("\"{second_field_full_name}\" is declared here."),
437                    ),
438                ],
439                help: vec![
440                    if *first_field_key_is_compiler_generated || *second_field_key_is_compiler_generated {
441                        format!("The key of \"{}\" is generated by the compiler using the following formula:",
442                            if *first_field_key_is_compiler_generated {
443                                first_field_full_name
444                            } else {
445                                second_field_full_name
446                            }
447                        )
448                    } else {
449                        "Both keys are explicitly defined by using the `in` keyword.".to_string()
450                    },
451                    if *first_field_key_is_compiler_generated || *second_field_key_is_compiler_generated {
452                        if *experimental_storage_domains {
453                            format!("{}sha256((0u8, \"{}\"))",
454                                Indent::Single,
455                                if *first_field_key_is_compiler_generated {
456                                    first_field_full_name
457                                } else {
458                                    second_field_full_name
459                                }
460                            )
461                        } else {
462                            format!("{}sha256(\"{}\")",
463                                Indent::Single,
464                                if *first_field_key_is_compiler_generated {
465                                    first_field_full_name
466                                } else {
467                                    second_field_full_name
468                                }
469                            )
470                        }
471                    } else {
472                        Diagnostic::help_none()
473                    },
474                    format!("The common key is: {key}.")
475                ],
476            },
477           _ => Diagnostic {
478                    // TODO: Temporary we use self here to achieve backward compatibility.
479                    //       In general, self must not be used and will not be used once we
480                    //       switch to our own #[error] macro. All the values for the formatting
481                    //       of a diagnostic must come from the enum variant parameters.
482                    issue: Issue::warning(source_engine, self.span(), format!("{}", self.warning_content)),
483                    ..Default::default()
484                }
485        }
486    }
487}
488
489#[cfg(test)]
490mod test {
491    use sway_types::style::*;
492
493    #[test]
494    fn detect_styles() {
495        let snake_cases = [
496            "hello",
497            "__hello",
498            "blah32",
499            "some_words_here",
500            "___some_words_here",
501        ];
502        let screaming_snake_cases = ["SOME_WORDS_HERE", "___SOME_WORDS_HERE"];
503        let upper_camel_cases = [
504            "Hello",
505            "__Hello",
506            "Blah32",
507            "SomeWordsHere",
508            "___SomeWordsHere",
509        ];
510        let screaming_snake_case_or_upper_camel_case_idents = ["HELLO", "__HELLO", "BLAH32"];
511        let styleless_idents = ["Mix_Of_Things", "__Mix_Of_Things", "FooBar_123"];
512        for name in &snake_cases {
513            assert!(is_snake_case(name));
514            assert!(!is_screaming_snake_case(name));
515            assert!(!is_upper_camel_case(name));
516        }
517        for name in &screaming_snake_cases {
518            assert!(!is_snake_case(name));
519            assert!(is_screaming_snake_case(name));
520            assert!(!is_upper_camel_case(name));
521        }
522        for name in &upper_camel_cases {
523            assert!(!is_snake_case(name));
524            assert!(!is_screaming_snake_case(name));
525            assert!(is_upper_camel_case(name));
526        }
527        for name in &screaming_snake_case_or_upper_camel_case_idents {
528            assert!(!is_snake_case(name));
529            assert!(is_screaming_snake_case(name));
530            assert!(is_upper_camel_case(name));
531        }
532        for name in &styleless_idents {
533            assert!(!is_snake_case(name));
534            assert!(!is_screaming_snake_case(name));
535            assert!(!is_upper_camel_case(name));
536        }
537    }
538
539    #[test]
540    fn convert_to_snake_case() {
541        assert_eq!("hello", to_snake_case("HELLO"));
542        assert_eq!("___hello", to_snake_case("___HELLO"));
543        assert_eq!("blah32", to_snake_case("BLAH32"));
544        assert_eq!("some_words_here", to_snake_case("SOME_WORDS_HERE"));
545        assert_eq!("___some_words_here", to_snake_case("___SOME_WORDS_HERE"));
546        assert_eq!("hello", to_snake_case("Hello"));
547        assert_eq!("___hello", to_snake_case("___Hello"));
548        assert_eq!("blah32", to_snake_case("Blah32"));
549        assert_eq!("some_words_here", to_snake_case("SomeWordsHere"));
550        assert_eq!("___some_words_here", to_snake_case("___SomeWordsHere"));
551        assert_eq!("some_words_here", to_snake_case("someWordsHere"));
552        assert_eq!("___some_words_here", to_snake_case("___someWordsHere"));
553        assert_eq!("mix_of_things", to_snake_case("Mix_Of_Things"));
554        assert_eq!("__mix_of_things", to_snake_case("__Mix_Of_Things"));
555        assert_eq!("foo_bar_123", to_snake_case("FooBar_123"));
556    }
557
558    #[test]
559    fn convert_to_screaming_snake_case() {
560        assert_eq!("HELLO", to_screaming_snake_case("hello"));
561        assert_eq!("___HELLO", to_screaming_snake_case("___hello"));
562        assert_eq!("BLAH32", to_screaming_snake_case("blah32"));
563        assert_eq!(
564            "SOME_WORDS_HERE",
565            to_screaming_snake_case("some_words_here")
566        );
567        assert_eq!(
568            "___SOME_WORDS_HERE",
569            to_screaming_snake_case("___some_words_here")
570        );
571        assert_eq!("HELLO", to_screaming_snake_case("Hello"));
572        assert_eq!("___HELLO", to_screaming_snake_case("___Hello"));
573        assert_eq!("BLAH32", to_screaming_snake_case("Blah32"));
574        assert_eq!("SOME_WORDS_HERE", to_screaming_snake_case("SomeWordsHere"));
575        assert_eq!(
576            "___SOME_WORDS_HERE",
577            to_screaming_snake_case("___SomeWordsHere")
578        );
579        assert_eq!("SOME_WORDS_HERE", to_screaming_snake_case("someWordsHere"));
580        assert_eq!(
581            "___SOME_WORDS_HERE",
582            to_screaming_snake_case("___someWordsHere")
583        );
584        assert_eq!("MIX_OF_THINGS", to_screaming_snake_case("Mix_Of_Things"));
585        assert_eq!(
586            "__MIX_OF_THINGS",
587            to_screaming_snake_case("__Mix_Of_Things")
588        );
589        assert_eq!("FOO_BAR_123", to_screaming_snake_case("FooBar_123"));
590    }
591
592    #[test]
593    fn convert_to_upper_camel_case() {
594        assert_eq!("Hello", to_upper_camel_case("hello"));
595        assert_eq!("___Hello", to_upper_camel_case("___hello"));
596        assert_eq!("Blah32", to_upper_camel_case("blah32"));
597        assert_eq!("SomeWordsHere", to_upper_camel_case("some_words_here"));
598        assert_eq!(
599            "___SomeWordsHere",
600            to_upper_camel_case("___some_words_here")
601        );
602        assert_eq!("Hello", to_upper_camel_case("HELLO"));
603        assert_eq!("___Hello", to_upper_camel_case("___HELLO"));
604        assert_eq!("Blah32", to_upper_camel_case("BLAH32"));
605        assert_eq!("SomeWordsHere", to_upper_camel_case("SOME_WORDS_HERE"));
606        assert_eq!(
607            "___SomeWordsHere",
608            to_upper_camel_case("___SOME_WORDS_HERE")
609        );
610        assert_eq!("SomeWordsHere", to_upper_camel_case("someWordsHere"));
611        assert_eq!("___SomeWordsHere", to_upper_camel_case("___someWordsHere"));
612        assert_eq!("MixOfThings", to_upper_camel_case("Mix_Of_Things"));
613        assert_eq!("__MixOfThings", to_upper_camel_case("__Mix_Of_Things"));
614        assert_eq!("FooBar123", to_upper_camel_case("FooBar_123"));
615    }
616}