Browse Source

Fix if chain attributes with mixed expressions and strings (#3149)

* Fix if chain attributes with mixed expressions and strings
Evan Almloff 7 months ago
parent
commit
37a6e9200f

+ 19 - 0
packages/core/tests/conditional_formatted_attributes.rs

@@ -10,4 +10,23 @@ fn partially_formatted_conditional_attribute() {
             width: if true { "{width}" } else { "100px" }
         }
     };
+
+    // And make sure it works if one of those branches is an expression
+    // Regression test for https://github.com/DioxusLabs/dioxus/issues/3146
+    let opt = "button";
+
+    _ = rsx! {
+        input {
+            type: if true { opt } else { "text" },
+        }
+        input {
+            type: if true { opt.to_string() } else { "text with" },
+        }
+        input {
+            type: if true { opt.to_string() } else { "text with {width}" },
+        }
+        input {
+            type: if true { opt.to_string() } else if true { "" } else { "text with {width}" },
+        }
+    };
 }

+ 42 - 8
packages/rsx/src/attribute.rs

@@ -582,6 +582,20 @@ impl IfAttributeValue {
         }
     }
 
+    fn contains_expression(&self) -> bool {
+        if let AttributeValue::AttrExpr(_) = &*self.then_value {
+            return true;
+        }
+        match &self.else_value {
+            Some(attribute) => match attribute.as_ref() {
+                AttributeValue::IfExpr(if_expr) => if_expr.is_terminated(),
+                AttributeValue::AttrExpr(_) => true,
+                _ => false,
+            },
+            None => false,
+        }
+    }
+
     fn parse_attribute_value_from_block(block: &Block) -> syn::Result<Box<AttributeValue>> {
         let stmts = &block.stmts;
 
@@ -609,7 +623,12 @@ impl IfAttributeValue {
         }
     }
 
-    fn to_tokens_with_terminated(&self, tokens: &mut TokenStream2, terminated: bool) {
+    fn to_tokens_with_terminated(
+        &self,
+        tokens: &mut TokenStream2,
+        terminated: bool,
+        contains_expression: bool,
+    ) {
         let IfAttributeValue {
             condition,
             then_value,
@@ -619,15 +638,29 @@ impl IfAttributeValue {
         // Quote an attribute value and convert the value to a string if it is formatted
         // We always quote formatted segments as strings inside if statements so they have a consistent type
         // This fixes https://github.com/DioxusLabs/dioxus/issues/2997
-        fn quote_attribute_value_string(value: &AttributeValue) -> TokenStream2 {
-            if matches!(value, AttributeValue::AttrLiteral(HotLiteral::Fmted(_))) {
-                quote! { #value.to_string() }
+        fn quote_attribute_value_string(
+            value: &AttributeValue,
+            contains_expression: bool,
+        ) -> TokenStream2 {
+            if let AttributeValue::AttrLiteral(HotLiteral::Fmted(fmted)) = value {
+                if let Some(str) = fmted.to_static().filter(|_| contains_expression) {
+                    // If this is actually a static string, the user may be using a static string expression in another branch
+                    // use into to convert the string to whatever the other branch is using
+                    quote! {
+                        {
+                            #[allow(clippy::useless_conversion)]
+                            #str.into()
+                        }
+                    }
+                } else {
+                    quote! { #value.to_string() }
+                }
             } else {
                 value.to_token_stream()
             }
         }
 
-        let then_value = quote_attribute_value_string(then_value);
+        let then_value = quote_attribute_value_string(then_value, terminated);
 
         let then_value = if terminated {
             quote! { #then_value }
@@ -640,11 +673,11 @@ impl IfAttributeValue {
         let else_value = match else_value.as_deref() {
             Some(AttributeValue::IfExpr(else_value)) => {
                 let mut tokens = TokenStream2::new();
-                else_value.to_tokens_with_terminated(&mut tokens, terminated);
+                else_value.to_tokens_with_terminated(&mut tokens, terminated, contains_expression);
                 tokens
             }
             Some(other) => {
-                let other = quote_attribute_value_string(other);
+                let other = quote_attribute_value_string(other, contains_expression);
                 if terminated {
                     quote! { #other }
                 } else {
@@ -709,7 +742,8 @@ impl ToTokens for IfAttributeValue {
     fn to_tokens(&self, tokens: &mut TokenStream2) {
         // If the if expression is terminated, we can just return the then value
         let terminated = self.is_terminated();
-        self.to_tokens_with_terminated(tokens, terminated)
+        let contains_expression = self.contains_expression();
+        self.to_tokens_with_terminated(tokens, terminated, contains_expression)
     }
 }