@@ -765,6 +765,8 @@ else if (functions.length <= 10) {
765765 // newInstance/initComponent/call
766766 writeOutStatic (optionalPS , constr , keys , cw , comp , className );
767767
768+ writeOutSeedExpressionDefaults (optionalPS , constr , keys , cw , comp , className );
769+
768770 // set field subs
769771 FieldVisitor fv = cw .visitField (Opcodes .ACC_PRIVATE , "subs" , "[Llucee/runtime/CIPage;" , null , null );
770772 fv .visitEnd ();
@@ -1229,10 +1231,9 @@ private void writeOutStatic(PageSource optionalPS, ConstrBytecodeContext constr,
12291231 // prop.setDefault(value) if it's a simple literal
12301232 // Only handle simple literals (Literal interface) - complex expressions like now()
12311233 // or #myVar# need PageContext and must be evaluated at runtime, not class-load time
1232- if (propDefaultAttr != null && propDefaultAttr .getValue () instanceof Literal ) {
1234+ if (propDefaultAttr != null && propDefaultAttr .getValue () != null ) {
12331235 Expression defaultExpr = propDefaultAttr .getValue ();
12341236
1235- // Handle simple literals only - complex expressions handled at runtime
12361237 if (defaultExpr instanceof LitStringImpl ) {
12371238 String value = ((LitStringImpl ) defaultExpr ).getString ();
12381239 ga .loadLocal (propLocal );
@@ -1253,7 +1254,31 @@ else if (defaultExpr instanceof LitBooleanImpl) {
12531254 ga .box (Type .BOOLEAN_TYPE );
12541255 ga .invokeVirtual (Types .PROPERTY_IMPL , new Method ("setDefault" , Type .VOID_TYPE , new Type [] { Types .OBJECT }));
12551256 }
1256- // else: complex expression - will be handled at runtime in TagProperty
1257+ else {
1258+ String source ;
1259+ try {
1260+ int start = defaultExpr .getStart ().pos ;
1261+ int end = defaultExpr .getEnd ().pos ;
1262+ // json() consumes opening { or [ before recording position; back up
1263+ if (start > 0 ) {
1264+ String peek = sourceCode .subCFMLString (start - 1 , 1 ).toString ();
1265+ if (peek .length () == 1 && (peek .charAt (0 ) == '{' || peek .charAt (0 ) == '[' )) {
1266+ start --;
1267+ }
1268+ }
1269+ source = sourceCode .subCFMLString (start , end - start ).toString ();
1270+ }
1271+ catch (Exception e ) {
1272+ source = "" ;
1273+ }
1274+ Type EXPRESSION_DEFAULT = Type .getType ("Llucee/runtime/component/ExpressionDefault;" );
1275+ ga .loadLocal (propLocal );
1276+ ga .newInstance (EXPRESSION_DEFAULT );
1277+ ga .dup ();
1278+ ga .push (source );
1279+ ga .invokeConstructor (EXPRESSION_DEFAULT , new Method ("<init>" , Type .VOID_TYPE , new Type [] { Types .STRING }));
1280+ ga .invokeVirtual (Types .PROPERTY_IMPL , new Method ("setDefault" , Type .VOID_TYPE , new Type [] { Types .OBJECT }));
1281+ }
12571282 }
12581283
12591284 // Collect dynamic attributes (non-standard attributes)
@@ -1540,6 +1565,59 @@ else if (defaultExpr instanceof LitBooleanImpl) {
15401565
15411566 }
15421567
1568+ private void writeOutSeedExpressionDefaults (PageSource optionalPS , ConstrBytecodeContext constr , Map <LitString , Integer > keys , ClassWriter cw , TagCIObject component ,
1569+ String name ) throws TransformerException {
1570+ if (component == null || component .getBody () == null ) return ;
1571+ List <Statement > statements = component .getBody ().getStatements ();
1572+ if (statements == null ) return ;
1573+
1574+ List <TagProperty > exprDefProps = new ArrayList <TagProperty >();
1575+ for (Statement stmt : statements ) {
1576+ if (!(stmt instanceof TagProperty )) continue ;
1577+ TagProperty tagProp = (TagProperty ) stmt ;
1578+ Attribute defaultAttr = tagProp .getAttribute ("default" );
1579+ if (defaultAttr == null || defaultAttr .getValue () == null ) continue ;
1580+ Expression defaultExpr = defaultAttr .getValue ();
1581+ if (defaultExpr instanceof LitStringImpl || defaultExpr instanceof LitNumberImpl || defaultExpr instanceof LitBooleanImpl ) continue ;
1582+ Attribute nameAttr = tagProp .getAttribute ("name" );
1583+ if (nameAttr == null || nameAttr .getValue () == null ) continue ;
1584+ exprDefProps .add (tagProp );
1585+ }
1586+
1587+ // Method shell always emitted on component classes — empty body is JIT no-op; ensures
1588+ // presence-of-method is stable for ComponentImpl._duplicate's reflection lookup.
1589+ Method seedMethod = new Method ("_seedExpressionDefaults" , Types .VOID , new Type [] { Types .PAGE_CONTEXT });
1590+ GeneratorAdapter ga = new GeneratorAdapter (Opcodes .ACC_PUBLIC + Opcodes .ACC_STATIC , seedMethod , null , new Type [] { Types .THROWABLE }, cw );
1591+ BytecodeContext bc = new BytecodeContext (config , optionalPS , constr , this , keys , cw , name , ga , seedMethod , writeLog (), suppressWSbeforeArg , output , returnValue ,
1592+ sourceCode .getSourceOffset ());
1593+
1594+ Method METHOD_VARIABLES_SCOPE = new Method ("variablesScope" , Types .VARIABLES , new Type [] {});
1595+ Method METHOD_SET_EL = new Method ("setEL" , Types .OBJECT , new Type [] { Types .COLLECTION_KEY , Types .OBJECT });
1596+
1597+ for (TagProperty tagProp : exprDefProps ) {
1598+ Attribute nameAttr = tagProp .getAttribute ("name" );
1599+ Attribute defaultAttr = tagProp .getAttribute ("default" );
1600+ String propName = nameAttr .getValue () instanceof Literal ? ((Literal ) nameAttr .getValue ()).getString () : null ;
1601+ if (propName == null ) continue ;
1602+ Expression defaultExpr = defaultAttr .getValue ();
1603+
1604+ defaultExpr .writeOut (bc , Expression .MODE_REF );
1605+ int defaultLocal = ga .newLocal (Types .OBJECT );
1606+ ga .storeLocal (defaultLocal );
1607+
1608+ ga .loadArg (0 );
1609+ ga .invokeVirtual (Types .PAGE_CONTEXT , METHOD_VARIABLES_SCOPE );
1610+ ga .push (propName );
1611+ ga .invokeStatic (KEY_IMPL , KEY_INIT );
1612+ ga .loadLocal (defaultLocal );
1613+ ga .invokeInterface (Types .SCOPE , METHOD_SET_EL );
1614+ ga .pop ();
1615+ }
1616+
1617+ ga .returnValue ();
1618+ ga .endMethod ();
1619+ }
1620+
15431621 private String getTagAttributeValue (Tag tag , String attrName ) {
15441622 Attribute attr = tag .getAttribute (attrName );
15451623 if (attr != null && attr .getValue () != null ) {
0 commit comments