Skip to content

Commit 0fadfca

Browse files
authored
Parse language default expressions (#2279)
Deprecate old annotation-based mechanism.
1 parent 09d400e commit 0fadfca

File tree

10 files changed

+115
-88
lines changed

10 files changed

+115
-88
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
[Unreleased]: https://github.com/cashapp/redwood/compare/0.13.0...HEAD
55

66
New:
7+
- Default expressions can now be used directly in the schema rather than using the `@Default` annotation. The annotation has been deprecated, and will be removed in the next release.
78
- `EventListener.Factory.close()` is called by `TreehouseApp.close()` to release any resources held by the factory.
89

910
Changed:

redwood-layout-schema/api/redwood-layout-schema.api

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
public final class app/cash/redwood/layout/Box {
2+
public synthetic fun <init> (IILapp/cash/redwood/ui/Margin;IILkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
23
public synthetic fun <init> (IILapp/cash/redwood/ui/Margin;IILkotlin/jvm/functions/Function1;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
34
public final fun component1-jfchsNQ ()I
45
public final fun component2-jfchsNQ ()I
@@ -24,6 +25,7 @@ public final class app/cash/redwood/layout/BoxScope {
2425
}
2526

2627
public final class app/cash/redwood/layout/Column {
28+
public synthetic fun <init> (IILapp/cash/redwood/ui/Margin;IIILkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
2729
public synthetic fun <init> (IILapp/cash/redwood/ui/Margin;IIILkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
2830
public final fun component1-jfchsNQ ()I
2931
public final fun component2-jfchsNQ ()I
@@ -111,6 +113,7 @@ public abstract interface class app/cash/redwood/layout/RedwoodLayout {
111113
}
112114

113115
public final class app/cash/redwood/layout/Row {
116+
public synthetic fun <init> (IILapp/cash/redwood/ui/Margin;IIILkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
114117
public synthetic fun <init> (IILapp/cash/redwood/ui/Margin;IIILkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
115118
public final fun component1-jfchsNQ ()I
116119
public final fun component2-jfchsNQ ()I
@@ -164,6 +167,7 @@ public final class app/cash/redwood/layout/Size {
164167
}
165168

166169
public final class app/cash/redwood/layout/Spacer {
170+
public synthetic fun <init> (DDILkotlin/jvm/internal/DefaultConstructorMarker;)V
167171
public synthetic fun <init> (DDLkotlin/jvm/internal/DefaultConstructorMarker;)V
168172
public final fun component1-hdgK9uQ ()D
169173
public final fun component2-hdgK9uQ ()D

redwood-layout-schema/src/main/kotlin/app/cash/redwood/layout/widgets.kt

Lines changed: 21 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import app.cash.redwood.layout.api.CrossAxisAlignment
2020
import app.cash.redwood.layout.api.MainAxisAlignment
2121
import app.cash.redwood.layout.api.Overflow
2222
import app.cash.redwood.schema.Children
23-
import app.cash.redwood.schema.Default
2423
import app.cash.redwood.schema.Property
2524
import app.cash.redwood.schema.Widget
2625
import app.cash.redwood.ui.Dp
@@ -37,49 +36,43 @@ public data class Row(
3736
* its parent ([Constraint.Fill]).
3837
*/
3938
@Property(1)
40-
@Default("Constraint.Wrap")
41-
val width: Constraint,
39+
val width: Constraint = Constraint.Wrap,
4240

4341
/**
4442
* Sets whether the row's height will wrap its contents ([Constraint.Wrap]) or match the height of
4543
* its parent ([Constraint.Fill]).
4644
*/
4745
@Property(2)
48-
@Default("Constraint.Wrap")
49-
val height: Constraint,
46+
val height: Constraint = Constraint.Wrap,
5047

5148
/**
5249
* Applies margin (space) around the row.
5350
*
5451
* This can also be applied to an individual widget using `Modifier.margin`.
5552
*/
5653
@Property(3)
57-
@Default("Margin.Zero")
58-
val margin: Margin,
54+
val margin: Margin = Margin.Zero,
5955

6056
/**
6157
* Sets whether the row allows scrolling ([Overflow.Scroll]) if its content overflows its bounds
6258
* or if it does not allow scrolling ([Overflow.Clip]).
6359
*/
6460
@Property(4)
65-
@Default("Overflow.Clip")
66-
val overflow: Overflow,
61+
val overflow: Overflow = Overflow.Clip,
6762

6863
/**
6964
* Sets the horizontal alignment for widgets in this row.
7065
*/
7166
@Property(5)
72-
@Default("MainAxisAlignment.Start")
73-
val horizontalAlignment: MainAxisAlignment,
67+
val horizontalAlignment: MainAxisAlignment = MainAxisAlignment.Start,
7468

7569
/**
7670
* Sets the default vertical alignment for widgets in this row.
7771
*
7872
* This can also be applied to an individual widget using `Modifier.verticalAlignment`.
7973
*/
8074
@Property(6)
81-
@Default("CrossAxisAlignment.Start")
82-
val verticalAlignment: CrossAxisAlignment,
75+
val verticalAlignment: CrossAxisAlignment = CrossAxisAlignment.Start,
8376

8477
/**
8578
* Invoked when the container scrolls. The function's `offset` is represented in units in the
@@ -88,8 +81,7 @@ public data class Row(
8881
* @see Overflow.Scroll
8982
*/
9083
@Property(7)
91-
@Default("null")
92-
val onScroll: ((offset: Px) -> Unit)?,
84+
val onScroll: ((offset: Px) -> Unit)? = null,
9385

9486
/**
9587
* A slot to add widgets in.
@@ -109,49 +101,43 @@ public data class Column(
109101
* of its parent ([Constraint.Fill]).
110102
*/
111103
@Property(1)
112-
@Default("Constraint.Wrap")
113-
val width: Constraint,
104+
val width: Constraint = Constraint.Wrap,
114105

115106
/**
116107
* Sets whether the column's height will wrap its contents ([Constraint.Wrap]) or match the height
117108
* of its parent ([Constraint.Fill]).
118109
*/
119110
@Property(2)
120-
@Default("Constraint.Wrap")
121-
val height: Constraint,
111+
val height: Constraint = Constraint.Wrap,
122112

123113
/**
124114
* Applies margin (space) around the column.
125115
*
126116
* This can also be applied to an individual widget using `Modifier.margin`.
127117
*/
128118
@Property(3)
129-
@Default("Margin.Zero")
130-
val margin: Margin,
119+
val margin: Margin = Margin.Zero,
131120

132121
/**
133122
* Sets whether the column allows scrolling ([Overflow.Scroll]) if its content overflows its bounds
134123
* or if it does not allow scrolling ([Overflow.Clip]).
135124
*/
136125
@Property(4)
137-
@Default("Overflow.Clip")
138-
val overflow: Overflow,
126+
val overflow: Overflow = Overflow.Clip,
139127

140128
/**
141129
* Sets the default horizontal alignment for widgets in this column.
142130
*
143131
* This can also be applied to an individual widget using `Modifier.horizontalAlignment`.
144132
*/
145133
@Property(5)
146-
@Default("CrossAxisAlignment.Start")
147-
val horizontalAlignment: CrossAxisAlignment,
134+
val horizontalAlignment: CrossAxisAlignment = CrossAxisAlignment.Start,
148135

149136
/**
150137
* Sets the vertical alignment for widgets in this column.
151138
*/
152139
@Property(6)
153-
@Default("MainAxisAlignment.Start")
154-
val verticalAlignment: MainAxisAlignment,
140+
val verticalAlignment: MainAxisAlignment = MainAxisAlignment.Start,
155141

156142
/**
157143
* Invoked when the container scrolls. The function's `offset` is represented in units in the
@@ -160,8 +146,7 @@ public data class Column(
160146
* @see Overflow.Scroll
161147
*/
162148
@Property(7)
163-
@Default("null")
164-
val onScroll: ((offset: Px) -> Unit)?,
149+
val onScroll: ((offset: Px) -> Unit)? = null,
165150

166151
/**
167152
* A slot to add widgets in.
@@ -180,15 +165,13 @@ public data class Spacer(
180165
* Sets the width of the spacer.
181166
*/
182167
@Property(1)
183-
@Default("Dp(0.0)")
184-
val width: Dp,
168+
val width: Dp = Dp(0.0),
185169

186170
/**
187171
* Sets the height of the spacer.
188172
*/
189173
@Property(2)
190-
@Default("Dp(0.0)")
191-
val height: Dp,
174+
val height: Dp = Dp(0.0),
192175
)
193176

194177
/**
@@ -203,43 +186,38 @@ public data class Box(
203186
* of its parent ([Constraint.Fill]).
204187
*/
205188
@Property(1)
206-
@Default("Constraint.Wrap")
207-
val width: Constraint,
189+
val width: Constraint = Constraint.Wrap,
208190

209191
/**
210192
* Sets whether the box's height will match its tallest child ([Constraint.Wrap]) or match the
211193
* height of its parent ([Constraint.Fill]).
212194
*/
213195
@Property(2)
214-
@Default("Constraint.Wrap")
215-
val height: Constraint,
196+
val height: Constraint = Constraint.Wrap,
216197

217198
/**
218199
* Applies margin (space) around the box.
219200
*
220201
* This can also be applied to an individual widget using `Modifier.margin`.
221202
*/
222203
@Property(3)
223-
@Default("Margin.Zero")
224-
val margin: Margin,
204+
val margin: Margin = Margin.Zero,
225205

226206
/**
227207
* Sets the default horizontal alignment for widgets in this Box.
228208
*
229209
* This can also be applied to an individual widget using `Modifier.horizontalAlignment`.
230210
*/
231211
@Property(4)
232-
@Default("CrossAxisAlignment.Start")
233-
val horizontalAlignment: CrossAxisAlignment,
212+
val horizontalAlignment: CrossAxisAlignment = CrossAxisAlignment.Start,
234213

235214
/**
236215
* Sets the default vertical alignment for widgets in this Box.
237216
*
238217
* This can also be applied to an individual widget using `Modifier.horizontalAlignment`.
239218
*/
240219
@Property(5)
241-
@Default("CrossAxisAlignment.Start")
242-
val verticalAlignment: CrossAxisAlignment,
220+
val verticalAlignment: CrossAxisAlignment = CrossAxisAlignment.Start,
243221

244222
/**
245223
* A slot to add widgets in.

redwood-schema/src/main/kotlin/app/cash/redwood/schema/annotations.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ public annotation class Children(
197197
*/
198198
@Retention(RUNTIME)
199199
@Target(PROPERTY)
200+
@Deprecated("Migrate to normal default parameter expressions")
200201
public annotation class Default(val expression: String)
201202

202203
/**

redwood-tooling-codegen/src/test/kotlin/app/cash/redwood/tooling/codegen/ComposeGenerationTest.kt

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ package app.cash.redwood.tooling.codegen
1717

1818
import androidx.compose.runtime.Composable
1919
import app.cash.redwood.schema.Children
20-
import app.cash.redwood.schema.Default
2120
import app.cash.redwood.schema.Property
2221
import app.cash.redwood.schema.Schema
2322
import app.cash.redwood.schema.Widget
@@ -76,14 +75,11 @@ class ComposeGenerationTest {
7675
@Widget(1)
7776
data class DefaultTestWidget(
7877
@Property(1)
79-
@Default("\"test\"")
80-
val trait: String,
78+
val trait: String = "test",
8179
@Property(2)
82-
@Default("{ error(\"test\") }")
83-
val onEvent: () -> Unit,
80+
val onEvent: () -> Unit = { error("test") },
8481
@Children(1)
85-
@Default("{}")
86-
val block: () -> Unit,
82+
val block: () -> Unit = {},
8783
)
8884

8985
@Test fun `default is supported for all property types`() {

redwood-tooling-schema/src/main/kotlin/app/cash/redwood/tooling/schema/schemaParserFir.kt

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ import org.jetbrains.kotlin.fir.expressions.arguments
7070
import org.jetbrains.kotlin.fir.expressions.impl.FirResolvedArgumentList
7171
import org.jetbrains.kotlin.fir.references.FirNamedReference
7272
import org.jetbrains.kotlin.fir.resolve.fqName
73+
import org.jetbrains.kotlin.fir.symbols.impl.FirValueParameterSymbol
7374
import org.jetbrains.kotlin.fir.types.ConeClassLikeType
7475
import org.jetbrains.kotlin.fir.types.ConeKotlinTypeProjection
7576
import org.jetbrains.kotlin.fir.types.ConeStarProjection
@@ -413,7 +414,7 @@ private fun FirContext.parseWidget(
413414

414415
val propertyAnnotation = findPropertyAnnotation(property.annotations)
415416
val childrenAnnotation = findChildrenAnnotation(property.annotations)
416-
val defaultAnnotation = findDefaultAnnotation(property.annotations)
417+
val defaultExpression = findDefaultExpression(memberType, parameter, property)
417418
val deprecation = findDeprecationAnnotation(property.annotations)
418419
?.toDeprecation { "$memberType.$name" }
419420
val documentation = parameter.source?.findAndParseKDoc()
@@ -427,7 +428,7 @@ private fun FirContext.parseWidget(
427428
documentation = documentation,
428429
parameterTypes = arguments.map { it.toFqType() },
429430
isNullable = type.isNullable,
430-
defaultExpression = defaultAnnotation?.expression,
431+
defaultExpression = defaultExpression,
431432
deprecation = deprecation,
432433
)
433434
} else {
@@ -436,7 +437,7 @@ private fun FirContext.parseWidget(
436437
name = name,
437438
documentation = documentation,
438439
type = type.toFqType(),
439-
defaultExpression = defaultAnnotation?.expression,
440+
defaultExpression = defaultExpression,
440441
deprecation = deprecation,
441442
)
442443
}
@@ -463,7 +464,7 @@ private fun FirContext.parseWidget(
463464
name = name,
464465
documentation = documentation,
465466
scope = scope?.toFqType(),
466-
defaultExpression = defaultAnnotation?.expression,
467+
defaultExpression = defaultExpression,
467468
deprecation = deprecation,
468469
)
469470
} else {
@@ -577,7 +578,7 @@ private fun FirContext.parseModifier(
577578
val parameterType = parameter.resolvedReturnType.toFqType()
578579
val property = firClass.declarations.filterIsInstance<FirProperty>().single { it.name == parameter.name }
579580

580-
val defaultAnnotation = findDefaultAnnotation(property.annotations)
581+
val defaultExpression = findDefaultExpression(memberType, parameter, property)
581582
val deprecation = findDeprecationAnnotation(property.annotations)
582583
?.toDeprecation { "$memberType.$name" }
583584
val documentation = parameter.source?.findAndParseKDoc()
@@ -592,7 +593,7 @@ private fun FirContext.parseModifier(
592593
documentation = documentation,
593594
type = parameterType,
594595
isSerializable = isSerializable,
595-
defaultExpression = defaultAnnotation?.expression,
596+
defaultExpression = defaultExpression,
596597
deprecation = deprecation,
597598
)
598599
}
@@ -770,23 +771,31 @@ private data class ChildrenAnnotation(
770771
val tag: Int,
771772
)
772773

773-
private fun FirContext.findDefaultAnnotation(
774-
annotations: List<FirAnnotation>,
775-
): DefaultAnnotation? {
776-
val annotation = annotations.find { it.fqName(firSession) == FqNames.Default }
777-
?: return null
774+
private fun FirContext.findDefaultExpression(
775+
memberType: FqType,
776+
parameter: FirValueParameterSymbol,
777+
property: FirProperty,
778+
): String? {
779+
val annotation = property.annotations.find { it.fqName(firSession) == FqNames.Default }
780+
?.let { annotation ->
781+
annotation.argumentMapping
782+
.mapping[Name.identifier("expression")] as? FirLiteralExpression
783+
?: throw AssertionError(annotation.source?.text)
784+
}
785+
?.value as String?
778786

779-
val expression = annotation.argumentMapping
780-
.mapping[Name.identifier("expression")] as? FirLiteralExpression
781-
?: throw AssertionError(annotation.source?.text)
787+
val expression = parameter.defaultValueSource?.let { defaultValue ->
788+
defaultValue.treeStructure
789+
.toString(defaultValue.lighterASTNode)
790+
.toString()
791+
}
782792

783-
return DefaultAnnotation(expression.value as String)
793+
check(annotation == null || expression == null) {
794+
"Only @Default or a default expression may be present–not both: $memberType.${parameter.name}"
795+
}
796+
return expression ?: annotation
784797
}
785798

786-
private data class DefaultAnnotation(
787-
val expression: String,
788-
)
789-
790799
private fun FirContext.findModifierAnnotation(
791800
annotations: List<FirAnnotation>,
792801
): ModifierAnnotation? {

0 commit comments

Comments
 (0)