Add lint check to specialize mutableStateOf calls
This CL adds an inspection to warn users when they are creating a state
using the generic `mutableStateOf<T>()` function that could be
specialized to a different state builder function which will not box
type `T`. To make this conversion, a few things must be true (which
this inspection looks for when reporting errors):
- `T` must not be nullable
- `T` must have a known specialized state builder function
- The snapshot mutation policy argument must not be specified or must
be the `structuralEqualityPolicy()`
For AndroidX, this inspection will be treated as an error.
Test: AutoboxingStateCreationDetectorTest
Change-Id: I4781c5b7bb48d177619fe5dece3b23f3989d41ee
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeImplPlugin.kt
index f60a494..c596da5 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeImplPlugin.kt
@@ -153,6 +153,7 @@
error.add("ComposableLambdaParameterPosition")
error.add("CompositionLocalNaming")
error.add("ComposableModifierFactory")
+ error.add("AutoboxingStateCreation")
error.add("AutoboxingStateValueProperty")
error.add("InvalidColorHexValue")
error.add("MissingColorAlphaChannel")
diff --git a/compose/lint/common-test/src/main/java/androidx/compose/lint/test/Stubs.kt b/compose/lint/common-test/src/main/java/androidx/compose/lint/test/Stubs.kt
index 5c61695..092f11da 100644
--- a/compose/lint/common-test/src/main/java/androidx/compose/lint/test/Stubs.kt
+++ b/compose/lint/common-test/src/main/java/androidx/compose/lint/test/Stubs.kt
@@ -519,7 +519,7 @@
val SnapshotState: TestFile = bytecodeStub(
filename = "SnapshotState.kt",
filepath = "androidx/compose/runtime",
- checksum = 0x3a5656cc,
+ checksum = 0x6f0a3199,
source = """
package androidx.compose.runtime
@@ -543,7 +543,10 @@
private class MutableStateImpl<T>(override var value: T) : MutableState<T>
- fun <T> mutableStateOf(value: T): MutableState<T> = MutableStateImpl(value)
+ fun <T> mutableStateOf(
+ value: T,
+ policy: SnapshotMutationPolicy<T> = structuralEqualityPolicy()
+ ): MutableState<T> = MutableStateImpl(value)
fun <T> mutableStateListOf() = SnapshotStateList<T>()
class SnapshotStateList<T>
@@ -598,12 +601,44 @@
) {
this.value = value
}
+
+ interface SnapshotMutationPolicy<T> {
+ fun equivalent(a: T, b: T): Boolean
+ fun merge(previous: T, current: T, applied: T): T? = null
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ fun <T> referentialEqualityPolicy(): SnapshotMutationPolicy<T> =
+ ReferentialEqualityPolicy as SnapshotMutationPolicy<T>
+
+ private object ReferentialEqualityPolicy : SnapshotMutationPolicy<Any?> {
+ override fun equivalent(a: Any?, b: Any?) = a === b
+ override fun toString() = "ReferentialEqualityPolicy"
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ fun <T> structuralEqualityPolicy(): SnapshotMutationPolicy<T> =
+ StructuralEqualityPolicy as SnapshotMutationPolicy<T>
+
+ private object StructuralEqualityPolicy : SnapshotMutationPolicy<Any?> {
+ override fun equivalent(a: Any?, b: Any?) = a == b
+ override fun toString() = "StructuralEqualityPolicy"
+ }
+ @Suppress("UNCHECKED_CAST")
+ fun <T> neverEqualPolicy(): SnapshotMutationPolicy<T> =
+ NeverEqualPolicy as SnapshotMutationPolicy<T>
+
+ private object NeverEqualPolicy : SnapshotMutationPolicy<Any?> {
+ override fun equivalent(a: Any?, b: Any?) = false
+ override fun toString() = "NeverEqualPolicy"
+ }
""",
"""
META-INF/main.kotlin_module:
- H4sIAAAAAAAA/2NgYGBmYGBgBGIOBijgMueSSMxLKcrPTKnQS87PLcgvTtUr
- Ks0rycxNFeIKSs1NzU1KLfIuEeIPzkssKM7ILwkuSSxJ9S7h4uNiKUktLhFi
- CwGS3iVKDFoMAJF5eAthAAAA
+ H4sIAAAAAAAA/3XLvQvCQAwF8IiiEkThBhERBBfBoS6Cszh2s+KetqE9uI9y
+ TcE/3xN1KgZehsf7AcAQAAYxU/gennBNrgxel8+k8LbxLSfktCXR3qllpl1l
+ +EGm48uvTQWPuP2vYhNYzT57yg1HcMZVD4TOibas8MaWbc4hFbXIHDVt7SUT
+ kjfc46YHO51UgZpaF62aXL3xUeIcR8KtqPE9/lR2cIAXjm1D/fEAAAA=
""",
"""
androidx/compose/runtime/DerivedState.class:
@@ -667,6 +702,30 @@
3MPKIZjEfTw4xITEtMSqxEOJtNThjMSsxLxE5hfups0nUgQAAA==
""",
"""
+ androidx/compose/runtime/NeverEqualPolicy.class:
+ H4sIAAAAAAAA/5VU3W8bRRD/7Z0/zhcndZyQpmn4KA3UTpqeE8pnTNoSinqR
+ ayJcBUGe1vbibnK+c+/2rCJeIv4Unnmg4qEIJBTBW/8oxOydCcFJUSLLO7Mz
+ s7/5zc7svfjr198B3MZdhir3u2Egu0+dTtAfBJFwwthXsi+cphiK8P6TmHs7
+ gSc73+bBGL5s7PMhdzzu95zP2/uiozYaL0Vo+XwQPQ7Uw1hxJQM/xamfhtjc
+ YCiNW/PIMDgXBM8jx5CrS1+qTQazUt0twkLBRhY2Q0Y9lhHDysspjxdNxGzx
+ JJZD7glfMdyonHEBpyzVrxmWGkHYc/aFaodc+hEV4gcp08hpxp7H254gdMYZ
+ yqcRyNFmsFTQUqH0ewyzleqJqNRKUdf/L0ugdCJ9t+NlWbjMkO2LsCcYts9V
+ 0xlVnkW7fsGGLX0qvuGxp9z+wIvyuMrwXeWiE3UutufiX8QVvGpjEa/R5Q9C
+ MZRBTAOT78RhmPQ/zwcDT4ouuesdLxkzG4aeLcttth7da27dL+ItTBbI+DbD
+ dOMgUBTmPBSKd7nidENGf2jS62N6sfQCavYB2Z9KvauR1l1jEEeHi7Yxb6T/
+ 0tGhbVhamSBpkpyy/vzemD86XLfLuXJmntWMGvskbxl//JAzSub2YimzYNSy
+ 61Ypt5B4Scun2oO5batkkbfwYE4nW2dYu/CVM027eLJ5NGb/xLYoVNw6oPu6
+ +kUK4/pDGUka+Xv/Dii9x62gSwN4qSF90Yz7bRE+0s9Cv4igw71dHkq9HxmX
+ xrF2eMj7QonwP6BF1/dFuOXxKBK0LbRkz+cqDgnBbgVx2BGfSQ13ZQS3e4oY
+ atS9LNVHnzwaCGonyfepNzmScySz5KevCu0+oJ2jG6ity88x8YwUAx+OgoE+
+ PqK1mAaQnEz6PYVLFKUP36Wdbrth/jh20k9OvpF6Rye1No1y4jcwQ5qBDdIn
+ DTKVEuh0ncUrowSrI3Zmef6nsQzBCW7mKMPJMnXxC+TRMDvI0I8mdnnl5uov
+ eH0cK61zOY05ZmsR/5SthWsJW629SZp5nEkT1jXMmGM1lJLg68f3fDk5Dkz8
+ BuOr51j6GTeeJYYM6rTaFLZILXuXWH+csDOxmcj3cIekS/4K9aC6B9PFsosV
+ FzexSipuudTE2h5YhDWs78GOMBnhnQi3IxQiTCXKdISZCLOk/w1bh/c6QAcA
+ AA==
+ """,
+ """
androidx/compose/runtime/ProduceStateScope.class:
H4sIAAAAAAAA/41T328SQRCeXSgcSPWKv4BWq7ZGJcY7iU9CiEbTFENrI+gL
T8tx4MKxS273sI/EP8UH/wbjgyH45h9lnINeG4u1fbid2ZlvvtnZ/e7X7+8/
@@ -683,128 +742,195 @@
gzXIogtXa3ANrreAKLgBN1uQUpBTkFdQUGAoWFewoeDW3En9Ae+dQkpABAAA
""",
"""
+ androidx/compose/runtime/ReferentialEqualityPolicy.class:
+ H4sIAAAAAAAA/5VUW28bRRT+ZteX9cZOHSekSRquDdS5dR0TrjGhIRTVkROi
+ OgqCANLYnrqTrHfdvViteIn4KTzACw9UPBRRCUXwxo9CnNk1wTgJSmR5zplz
+ znznO3PO7J9//fobgFVsMJS50/Jc2XpsNd1O1/WF5YVOIDvCui8eCE+Qzu27
+ j0Juy+DJrmvL5pM0GMNntUPe45bNnbb1aeNQNIO12oVQdYd3/YdusB0GPJCu
+ E+NUzkKsrzHkh61pJBisK4KnkWJIVaQjg3UGvTi/n4WBjIkkTIZE8FD6DKsX
+ U76wemJoikeh7HGb3Ay3iufcxBnL/BcMczXXa1uHImh4XDo+VeS4MWXf2glt
+ mzdsQeiMMxTOIpCjwWAEbj3wpNNmmCjOD0TFVoq6+X9Z3EAloqjpC+szcJ0h
+ 2RFeWzBsXaq4c8o9j3/lii2c+1g84KEdVDtd20/jBsM3xavO2KXYXop/FtN4
+ 0cQsXqIudD3Rk25II5Ruhp4XDUKad7u2FC1yV5p2NHgmNDVtRnWnvrexs3k3
+ i9eRy5DxDYax2pEbUJi1LQLe4gGnG9I6PZ0eJlOLoRZQ14/I/liqXYm01gqD
+ ODmeNbUpLf7nT45NzVDKCEmd5Kjxx7fa1Mlx2SykCokpVtJK7KO0of3+XUrL
+ 61uz+cSMVkqWjXxqJvKSlo61e5NbRt4gb+bepEpWZli58pUzRTs72Dx61P/E
+ 1ilU3D6i+7pxP4apOj3pS5r9jX8nlV7optuiAbxWk47YCTsN4e2p96Gehtvk
+ 9j73pNr3jTmCbR5t825/PzeMvcs93hGB8P6TJFt1HOFt2tz3BW0zddl2eBB6
+ hGDW3dBrik+kgpvuw+2fIYoSdTNJ9dJHkQaE2kvyHepViuQkyST56btDu3dp
+ Z6mGKuvCM4w8JUXDe/1g4Eu8T2s2DiCZi/o/imsUpQ7vkVRjMLK49D3SiR+Q
+ 0H+krY61OE/mDhtA+ypCeyU+0UdT2hgKkX8E46RpqJCe08iUj9LF6wRe6Cdd
+ 7jPWC1M/DfH9eoCv3s8wWLq6kBnyKJhdJOhHU72wuLT8C14exoprX4hjTtka
+ xD9ma+DViK3SXiNNP82kCKsaxvWhGvJR8M3Tu78eHafCn0P7/Bnmfsatp5Eh
+ gQ9oNSlsltr4FrFej9jp+DCSb+MOySr5i3TN8wfQq1ioYrGKJSyTittVamzp
+ AMzHCsoHMH3kfLzpY9VHxsdopIz5GPcxQfrfIoPaNX8HAAA=
+ """,
+ """
+ androidx/compose/runtime/SnapshotMutationPolicy$DefaultImpls.class:
+ H4sIAAAAAAAA/5VSTW/TQBB96yRNGlL6wWcotEADohwwlTiRqBIqQjJKS0VC
+ D3Da2Eu6ib1rrddREX+KMwd+AD8KMeukAhqEiGXPvnnzZmY9u99/fP0G4Bke
+ MnS4ioyW0Zkf6iTVmfBNrqxMhN9TPM1OtT3MLbdSq2Mdy/BT66X4yPPYBkka
+ Z1UwhrURn3A/5mrovxmMRGirKDFUEmGGguHzo+6CHdrdiwX/i9mdpxjed/rP
+ 5/n9hffU6ffb+20y03eXPoZWV5uhPxJ2YLhUmc+V0tOkzD/K45gPYkGynX/J
+ tHVKUlVa9lRmDHsLj4uhlhoxkTqn9I2/TaEa5sYIZQnxNI2liBjWu2NtY6n8
+ Q2F5xC0nnZdMSnQvmDM1Z8DAxg7QgXpn0qGnDP6CO6yiydD4/d7QpTmX9kgp
+ noxpb+UDHdGFWe1KJY7yZCBM303Q/ZMOeXzCjXT+jFzuyaHiNjeEN99O+wdq
+ IjNJ4Re/RkzHdDF6zA1PhBXmD1kjUEqYg5hnmSC33tO5CcUr6Zo1ZyVO5spj
+ Dx7KmM5qGRUskXeHvNfkO95jX5zFFtkl4oB32Cb7eBpFHZeKbA8NrBRxD5cJ
+ eQVaJVTC3QJXcW9Wo0brffpWykXT86eEHbJ14jzcwiaaaBWJt/GA1i3i10iz
+ /gGlABsBrgS4imsBruNGgJs/AVqKC54UBAAA
+ """,
+ """
+ androidx/compose/runtime/SnapshotMutationPolicy.class:
+ H4sIAAAAAAAA/5VTzW7TQBD+1kltx2mLC6WkKeU3NCkHnFYcEK2QEAjhKgXU
+ RBzoaZNswybOOnjtqHDKs/AYHFDEkYdCjJ1WINIDlbzz/83szox//vr2HcBj
+ bDN4XHWjUHZPvU44HIVaeFGiYjkUXlPxkf4YxodJzGMZqndhIDufLTCG2n7r
+ aaPPx9wLuOp5b9t90Yn3ns2bGNx/bRbyDI74lMgxD4SKGaq1eeC8ZfsDQ6HW
+ au3Rl8oLQxH1BMPBf6EvyHfRbZfPCqQ16DBUGmHU8/oibkdcKk3dUuGsHdp7
+ kwQBbweCwlYagzAOpPIORcy7POZkM4bjHHWZpcROCRjYgOynMtXqJHV3GL5M
+ J6uOUTKc6cQxXCIZLzqGbdsnpenkYd6eTly2a9SNg003Vzbq+V3TXSgbT6YT
+ EsxMeL3146tpuNZB1bXL+RKb+QrnQc65UJxFr2XRi+kNdhl2GpfcAXoca7Hs
+ PZxOm8EeRWIsw0QzWJ0kirK5Wnw0CqToMuxfskDlpTjhSRD7w1GgLdxhWPzb
+ Qlt1jmsSTDwaULVCU/YUj5OIdmLjaJbeV2OpJY3o+Z+xUS5fKRG9CLjWglSn
+ GSZRR7ySASHXz5Dv53AmdQsL6Rhh0AaboD8BlVSDTXoBDkkzvYgHxE3yLxLP
+ Y4uoQ9pd8t3D+llUDtWM30eN+BH5lyj/8jFyPq74cH2s4CqJuOZjFdePwTTW
+ cOMYSxoljXWNsoaZ0Q2NmxqWRkFjU+OWxm0N5zfYvY915wMAAA==
+ """,
+ """
androidx/compose/runtime/SnapshotStateKt$produceState$1.class:
- H4sIAAAAAAAA/41T3U4TURD+znb7w1poqYCAiqhVtkVZqCZqCiSGSNJYNaGk
- MeFq6S7lQHuW7DltuOxT+AA+gSYaEy9Mw6UPZZyzbQwKAhc7f5n55pszsz9/
- ff8B4CmeMDxzhRcG3Dt2GkH7KJC+E3aE4m3fqQn3SO4HqqZc5b9W+aMw8DoN
- P3LzK0kwKq4euF3Xabmi6bzbPfAbqlz9P54uXN3eLq+XGbL/FiZhMsxdXJxE
- giGxygVX6wyT9tnuhTol2NRDGzG7UE8jhWsW4kgzxLtuq+Mz5M7WpTGGzAgM
- ZBlMtc8lw4sLJrnwZWi60bzmyN1WfdAx1fTV0JywC2fbEze7QKyJcyTHq4eB
- anHhvPGV67nKpZjR7sZoaUyLlBZgYIcUP+baWybLW2FY6/dGrX7PMqYNy0iZ
- Rdbvpazpfq+Uypk543m/t8y2prLGrDbfn3wwTz4mLMvIxmfNVCxrapASw/wl
- WyQ69lVfJ4kHDOnTT8Swd87qzokMH+Gg23b2OqKheCCkszm0SuXCZSzTWIBN
- t/YXo6VDxTBS403hqk5IZMyNwCOVqXLhv+20d/1w291tRXcSNPQKQ679YTBd
- EcIPN1qulD5dSeaVaLQCyUWTVrUfeAxWLeiEDX+T6+yZrQGhOpecyl8KERAH
- PQdW6NritEP6kZDT50d6kRZpYJo+2jH0PT4ia5O0jljFbxgtLn7F+Oco7zHJ
- MejlP4RJo46QXiJvapBNqNf1mZA1cQrdImsyytHYjr4i0vHiF4x/+gObiIIL
- EVx6kDCEG4DcIN+JoFnUDJjBMkmTKBSGOTEaUesiSqTXKHOGqmZ3EKvgZgW3
- KriNOTJxp4J53N0Bk7iH+ztISG3mJcYkJiWmJDK/AeLwkAKyBAAA
+ H4sIAAAAAAAA/41TXU8TURA9d7v9YC20VECoiqhV26IsVBM1BRJDJGmsmlDS
+ mPC07C7lwvYu2b3b8Nhf4Q/wF2iiMfHBNDz6o4xzt41BQfBhZ86dzJw5987s
+ j5/fvgN4gscMTy3hBD53jk3b7x75oWsGkZC865otYR2F+75sSUu6r2TpKPCd
+ yHbjY2klDUbFzQOrZ5meJTrm290D15b15r/5VOHq9nZ9vc6Q/7swDZ1h/uLi
+ NFIMqVUuuFxnmC6f7V5pU0KZeiiQKFfaWWRwxUASWYZkz/Iil6Fwti6LCeTG
+ oCHPoMt9HjI8v+AmF74M3W68pDRyy2sPO2Y6rhzBqXLlbHvSVq6QatIc28nm
+ oS89LszXrrQcS1oU07q9BA2NKZNRBgzskOLHXJ2WCTkrDGuD/rgx6BvarGZo
+ Gb3KBv2MMTvo1zIFvaA9G/SX2dZMXisq+O7kvX7yIWUYWj5Z1DOJvK5IagwL
+ l0yR5JT/93XSuMeQPf1EDHvnjO6cyOgRDnpdcy8StuS+CM3NEarVK5epzOIB
+ yrRrfyhaOpQMYy3eEZaMAhKjb/gOuVyTC/dN1N11g21r14v3xLfVCAOuzqNg
+ tiGEG2x4Vhi6tCW5l8L2/JCLDo1q33cYjJYfBba7yVX23NZQUJuHnMpfCOGT
+ BnUPrNC2JWmG9COhoNaP/CINUsMsfTRjqH18SGiTvIoY1a8Yry5+weSnOO8R
+ 2Qmo4Vego0r5FSzRaWaYTaxX1ZoQmjrFbhCajnMUt6m2iHyy+hmTH3/TpuJg
+ NabLDhNGdEOSa3Q2Y2oWNwPmsExWx30SMcxJ0BWVr6JGfo0y56iquINEA9cb
+ uNHATcwTxK0GFnB7ByzEHdzdQSpUsBRiIsR0iJkQuV9AF5e/sgQAAA==
""",
"""
androidx/compose/runtime/SnapshotStateKt$produceState$2.class:
- H4sIAAAAAAAA/41T0U4TURA9d7ttl7XQUgEBFVGrbouyUEzUFEgMkaSxakKb
- xoSnZbuWC+1dsnu34bFf4Qf4BZpoTHwwDY9+lHHutjEoCjzs3DOTmTPn3pn9
- 8fPbdwCPscbwxBGtwOetY9v1u0d+6NlBJCTvenZdOEfhvi/r0pHeS1k4CvxW
- 5HqxWyinwai4duD0HLvjiLb9Zu/Ac2Wl9n8+VbjeaFQ2Kwy5vwvT0BkWzi9O
- I8WQWueCy02Gaets92KTEizqoUDCKjYzMHDFRBIZhmTP6UQeQ/5sXQYTyI5B
- Q45Bl/s8ZHh2zk3OfRm63XhBaeROpznsaLQ9OYJTVvFse9JmFUk1aY7tZO3Q
- lx0u7FeedFqOdCimdXsJGhpTxlAGDOyQ4sdceSuEWqsMG4P+uDnom9qsZmqG
- XmKDvmHODvplI6/ntaeD/grbmclp8wq+PXmvn3xImaaWS87rRiKnK5Iyw+IF
- UyQ51mVfJ417DJnTT8Rw/I/RXSoyepaDXtd+FwlXcl+E9vYIlSvFi3Rn8AAW
- bd8fGpcPJcNYnbeFI6OA5OlbfouObI0L73XU3fOChrPXiTfHd9VQA678UTBT
- FcILtjpOGHq0N9kXwu34IRdtGt6+32Iw634UuN42V9lzO0NBTR5yKn8uhE8a
- 1D2wSvuXpKnSr4W8Wkg6l2i0Gmbpo6lDbehDQtt0qohZ+orx0tIXTH6K8x6R
- nYBahzJ0rFF+GcvkzQyzifWqWhxCU6fYTULTcY7ittVe0Zksfcbkx9+0qTi4
- FtNlhgkjuiHJNfLtmJrFzYA5rJDVcR/FUU6CrqjOEskCNihzjqrmd5Go4noV
- N6q4iQWCuFXFIm7vgoW4g7u7SIUKFkJMhJgOMRMi+wttlNMGxAQAAA==
+ H4sIAAAAAAAA/41TXU8TURA9d7v9YC20VEDAL9Sq26IsVEFNgcQQSRqrJpQ0
+ Jjwtu2u5sL1Ldm8bHvsr/AH+Ak00Jj6Yhkd/lHHutjEoCjzszLmTmTPn3pn9
+ 8fPbdwCP8YjhiS3cMODukeUE7cMg8qywIyRve1ZD2IfRXiAb0pbeS1k8DAO3
+ 43jxsVhJg1Fxfd/u2pZvi5b1Znffc2S1/n8+Vbi6vV1drzLk/y5MQ2e4cXZx
+ GimG1CoXXK4zTJqnu5ealGBSDwUSZqmZRQaXDCSRZUh2bb/jMRRO12UxhtwI
+ NOQZdLnHI4ZnZ9zkzJeh240WlUZu+81Bx0zLk0M4YZZOtydtZolUk+bYjtcP
+ AulzYb3ypO3a0qaY1u4maGhMmYwyYGAHFD/i6rRIyF1iWOv3Ro1+z9CmNUPL
+ 6GXW72WM6X6vkinoBe1pv7fItqby2qyCb4/f68cfUoah5ZOzeiaR1xVJhWHu
+ nCmSHPOir5PGXYbsySdiOPrH6C4UGT7LfrdtvesIR/JARNbmEFWqpfN0Z3Ef
+ Jm3fHxoXDiTDSIO3hC07IcnTNwKXXK7Ohfe60971wm171483J3DUUEOuzsNg
+ tiaEF274dhR5tDe5F8Lxg4iLFg1vL3AZjEbQCR1vk6vsma2BoCaPOJU/FyIg
+ DeoeWKL9S9JU6ddCQS0k+XkarYZp+mjqUBv6gNAmeRUxyl8xWp7/gvFPcd5D
+ smNQ67AMHSuUv4wFOk0Nson1slocQhMn2A1Ck3GO4rbUXpFPlj9j/ONv2lQc
+ XInpsoOEId2A5AqdrZiaxc2AGSyS1XEPpWFOgq6ofBkV8muUOUNVsztI1HC1
+ hms1XMcNgrhZwxxu7YBFuI07O0hFChYjjEWYjDAVIfcLwQpITcQEAAA=
""",
"""
androidx/compose/runtime/SnapshotStateKt$produceState$3.class:
- H4sIAAAAAAAA/41T3U4TURD+znbbLmuhpQJCVUStui3KQjFRU2hiiCSNVRNK
- GpNeLdu1HGjPkt3Thss+hQ/gE2iiMfHCNFz6UMY528agIHix85eZb74zM/vj
- 57fvAB5jneGJI1qBz1vHtut3j/zQs4OekLzr2XXhHIX7vqxLR3ovZf4o8Fs9
- 14vc/HoSjIprB07fsTuOaNtv9g48V5Zr/8ZThRu7u+VKmSHzd2ESOsPixcVJ
- JBgSG1xwWWGYtc52LzQowaIeyohZhUYKBq6YiCPFEO87nZ7HkD1bl8IU0hPQ
- kGHQ5T4PGZ5d8JILJ0Ovm8wrjtzpNEYdjbYnx+aMVTjbnrhZBWJNnCM5XTv0
- ZYcL+5UnnZYjHYpp3X6MlsaUMJQAAzuk+DFX3ipZrTWGzeFg0hwOTG1eMzVD
- L7LhwDDnh4OSkdWz2tPhYJXtzGW0nDLfnrzXTz4kTFPLxHO6EcvoCqTEsHTJ
- FomO9b/TSeIeQ+r0iBja56yuec4tjadw0O/a73rCldwXob09tkrlwmU0U3gA
- i47tD0orh5Jhos7bwpG9gNjoW36LVLrGhfe6193zgl1nrxMdiu+qHQZc+eNg
- qiqEF2x1nDD06EzSL4Tb8UMu2rSrfb/FYNb9XuB621xlL+yMCDV4yKn8uRA+
- cVDvwBqdW5yWSH8Ssur+SC/TJjXM00dLhjrIh2Rtk1YRs/gVk8XlL5j+FOU9
- IjkFtf1N6KhQ/iZWyJsbZRPqVXUnZM2cQjfJmo1yFLatzoh0vPgZ0x9/wyai
- YCWCS40SxnAjkGvk2xE0i5oBC1glqeM+CuOcGD1R6SJKEUWaBlXlmohVcb2K
- G1XcxCKZuFXFEm43wULcwd0mEqEy8yGmQsyGmAuR/gWU18KgswQAAA==
+ H4sIAAAAAAAA/41TXU8TURA9d7v9YC20VEBARdSq26IsFBM1BQIhkDRWTShp
+ TPq0dNdyob1Ldu82PPZX+AP8BZpoTHwwDY/+KOPcbWNQEHzYmTM3M2fOvTP7
+ 4+e37wCeYoXhmS0c3+POidX0Osde4Fp+KCTvuFZN2MfBgSdr0pbuS5k/9j0n
+ bLpRmF9JglFx9dDu2lbbFi3rzf6h25Tl6r/5VOHq3l55vcyQ/bswCZ1h7vLi
+ JBIMiVUuuFxnmDTPdy/UKcGkHgrEzEI9jRSuGYgjzRDv2u3QZcidr0tjDJkR
+ aMgy6PKABwwvLrnJpS9DtxvNK43cbtcHHVMtVw7hhFk43560mQVSTZojO149
+ 8mSbC+uVK23HljadaZ1ujIbGlEkpAwZ2ROcnXEVLhJxlhrV+b9To9wxtWjO0
+ lF5k/V7KmO73SqmcntOe93tLbHcqq80q+Pb0vX76IWEYWjY+q6diWV2RlBjm
+ r5giyTH/93WSeMCQPvtEDK0LRte4YJeGr3DY7VjvQtGU3BOBtTNEpXLhKplp
+ PIJJy/aHpMUjyTBS4y1hy9AnNfqW55DLVLlwX4edfdffs/fb0aJ4TTVDn6t4
+ eJiuCOH6W207CFxak8y2aLa9gIsWzerAcxiMmhf6TXeHq+yZ3YGgOg84lW8K
+ 4ZEGdQ8s07rFaYj0JyGn9o/8Ak1SwzR9NGSohXxMaIe8OjGKXzFaXPiC8U9R
+ 3hOyY1DT34COTcrfwCJFU4NsYr2u9oTQxBl2g9BklKO4LbVG5OPFzxj/+Js2
+ ER1uRnTpQcKQbkByg2IromZRM2AGS2R1PERhmBOjKypfRIn8GmXOUNVsA7EK
+ blZwq4LbmCOIOxXM424DLMA93G8gESiYDzAWYDLAVIDML8ZgJ06zBAAA
""",
"""
androidx/compose/runtime/SnapshotStateKt.class:
- H4sIAAAAAAAA/91XbVMb1xV+riSkZS3DshgD6xgTW7ZBGISJa7dGpXVwiFXA
- L4HQONhNFmmBBWlX3bsiuG+haZt/0Q/tL0g/JalnOoz7rT+l/QudTs9d7UpC
- b0j2ZKZTzazu3XvPec5zXvbs3X/8569/A3ALf2CY0K2cY5u5w1TWLhRtbqSc
- kuWaBSO1ZulFvmu7a67uGstuDIxB2dMP9FRet3ZSj7b2jCythhn6coZjHhg5
- T/LRNsP8xEq94PzkSktL92vU5xneT6/fbdRfmFhf7xQkTaILhHRlxXZ2UnuG
- u+XopsVTumXZtG/aNH9ouw9L+TxJTXaEmSkU8zH0MkTTpmW6CwxDzbzciOMM
- 4jJknGW42hFyDP0MPQd6vmQwqI2YFOBCydW38sbrBni1Rv21A1wLEgS4dehq
- pcuhOy9juH1QanViGKVg1Pq9YnJX+D4z0YbkiaIVGkQy08LdbmACh5Odq8Qw
- xhCeKFfEuIxLeJthoNajVb0oHJrumAkpEIdP0stN/Nl4Ix8JOb2+PL++cUpa
- 65ViuCbjuvAsXnTsXClb9oxhu0mFNlnZt928aaX2Dgqp7ZKVLT+aS/5srl05
- BsX8z9bF3LW99HRre49r3FvL2kX/GZgOILO2Y5dc0zJ4atEmFavkNZp0ReBD
- 6hqkMNWE7Kl+BuV3tbXconcvaovkEu0aH3U9X0zyc+ZImGEYqwmOabmGY+n5
- VMZyHUIwszyGWep52V0ju+/3zse6oxcMEmS43j7dawJkx+uOc3hHxk3cYrjT
- 6asnUVtaiZsx3JZxR7SSsfZRi+EHVJeiW5t6fqPcXyP7xoubDOOnVR7DYWcV
- /F3U9L+7qun/uyp/zcKYi+HHMu6JwhBZnmPYaZLCze8kY/9qnbHuDf7PJ0iE
- l0tYeu1UvRPDAxkZkarBJvGhzrRjuP4T++nEaeFvHWDH2M7TfWqZwlY0HPcF
- udjEWrFF9k4Lx5QXjy7Mp5MUZFISp0h31+QLEh6V27C3LeEJw7mJJhzjWMHa
- GdzFOsPZhJnYTlQDxDJ0QkwIuJrF8dNrNiZUCJ9Bax0vYscroLttInLijNlN
- Rpodohk+6zoljefTLhMjjr/0gnqG+Blo+CQIdNV9P8jVhesdH7kHAsurhqvn
- dFentVDhIEyfYUz8SeIPlMt9MQnR5qEpZvTKDeVustCfjo8+ko+P5NBISA5J
- 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
+ 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
+ uMMflh4AAA==
""",
"""
androidx/compose/runtime/SnapshotStateList.class:
- H4sIAAAAAAAA/41Qy0ocQRQ9VT0v24n2+IijJpqlGcRWEQQVIQrCQJuAM8xm
- VjXThdY8qqSrRlz2t/gHWQVcSOMyHxW8Pbox2WRz7j2nDvfeU7//PD4BOMAX
- hobQcWJUfB/2zfjWWBkmE+3UWIYtLW7tjXEtJ5yMlHVlMIatk/ZRNBB3IhwJ
- fR3+6A1k3x2f/isxBH9rZRQYSidKK3fK4G197VRRQtlHERWGgrtRlmE7+v+L
- aEktGho3Ujq8lE7EwgnS+PjOo3wsh0oOYGBD0u9Vznapi/coSpZWfV7nfpb6
- PCDI0nqWNgqVLA3YPt/lZ8XnhxIPvNy/TyPaLJ8UvDtjZ+jo9HMTS4b5SGn5
- fTLuyaQteiNSFiLTF6OOSFTO38SZlrrWwk0S6v2WmSR9eaHyh9Wr16AdZRU5
- v2ltaIUy2mIPnH7pLUr+aYRrxMIpB4qNX5j5SQ3HOmFpKq7jE2H11QAfs1Q9
- fJ66PGxM6yo2qR6Sp0qeD114Tcw1Md9EgBq1WGhiEUtdMItlfOyiYDFrsWJR
- tyi/AEh/yvNKAgAA
+ H4sIAAAAAAAA/41Qy0ocQRQ9VT0v20nsUaNjYh5LHcRWEQKJCCYQGOgk4Ayz
+ mVXNdKE1jyrpqhGX/S3+gSvBRWiyzEcFb41uNJtszr3n3MN9/fl79wvAIT4w
+ tIROM6PSq3hophfGyjibaaemMu5ocWHPjes44WSirKuCMWwddT8lI3Ep4onQ
+ Z/HPwUgO3efjfyWG6LlWRYmhcqS0cscMwdZ2r44KqiHKqDGU3LmyDDvJ/29E
+ QxrJ2LiJ0vF36UQqnCCNTy8Duo95qHkAAxuTfqU826Ms3adTirwe8iYPizzk
+ EUGRN4u8VaoVecQO+B7/Uv59XeFR4P0H1KLLfKfoyRq7Y0erfzWpZFhKlJY/
+ ZtOBzLpiMCFlOTFDMemJTHn+KC501JkWbpZRHnbMLBvKb8oXNk4fDu0pq8h5
+ orWhEcpoi31w+tLjKf5phK+JxXMOlFu3WLihhOMNYWUuvsMmYf3BgBCLFAO8
+ nbsCqvq4gfcUP5KnTp4XfQRtvGxjqY0IDUqx3MYKVvtgFq+w1kfJYtFi3aJp
+ Ub0HArlo6UoCAAA=
""",
"""
androidx/compose/runtime/SnapshotStateMap.class:
- H4sIAAAAAAAA/42Qy04bMRSGf3tyY0hhoFxCb7S7QqUOoK4gQqKVKkUMrdRU
- s8nKyVhgktjR2EEs51n6Bl0hdYFGLHkoxHFg08uiC//nnM+/j318e/frGsAH
- vGHYEjrLjcou44EZT4yVcT7VTo1l3NViYs+M6zrh5ImY1MEY2u3j/eRcXIh4
- JPRp/LV/LgfuIP0HO/wbMUR/sjoqDLW20sodMgRvt9ImaqiHqKLBUHFnyjK8
- S/77kXTHUjI0bqR0fCKdyIQTxPj4IqCJmZeGFzCwIfFL5asdyrJdhrgsFkLe
- 4iFv0IrKIiyLVllsVxplETEKLOJ7fCf4WL35UeNRxR/bo07HtFLmm0a/Pej9
- 0NEQn0wmGRYTpeWX6bgv8++iPyKynJiBGKUiV75+hHNddaqFm+aUh10zzQfy
- s/IbG98eRk6VVeQ80trQFcpoi11w+q/Hqfz3kT6nKp7VQHX7CnM/KeF4QVqb
- wVd4Sdp8MCDEPMWAqHcF2JzFZ3hNcZ88TfI86SHoYKGDxQ4iLFGK5Q6eYqUH
- ZrGKtR6qFvMW6xYtiw2L+j3Q7/VyagIAAA==
+ H4sIAAAAAAAA/41Qy04UQRQ9t3peNCM0iDr4ZieY2EBc4YRETUwmNJo4pjez
+ qpmuQDEzVZOuGsKyv4U/YEXiwnRc+lHG2wMbHwsX99x7T526rx8/v34D8Bpb
+ hG1pstzq7CIe2enMOhXnc+P1VMV9I2fu1Pq+l14dy1kTROh2jw6SM3ku44k0
+ J/Gn4Zka+TfpP7jDvylC9CfXRI3Q6Gqj/SEheLGdttFAM0QdLULNn2pHeJn8
+ 95DcYy0ZWz/RJj5WXmbSS+bE9DzgjamCVgUg0Jj5C11luxxle4S4LFZC0RGh
+ aLFFZRGWRacsdmqtsoiIHUViX+wG7+rfLxsiqlXf9rnSEVtKVdHot4FejT0v
+ 8d5mirCaaKM+zqdDlX+Rwwkz64kdyUkqc13lt+RSX58Y6ec5x2HfzvOR+qCr
+ h83PNyun2mlWvjXGcgttjcMeBN/rdqvqfIyPOIsXOVDfucbSFQcCjxkbC3IL
+ TxjbNwKEWGYf4OlCFeDZwj/Ec/YHrGmz5s4AQQ8rPaz2EGGNQ6z3cBcbA5DD
+ PdwfoO6w7PDAoeOw6dD8BRdS9ZhqAgAA
""",
"""
androidx/compose/runtime/State.class:
@@ -817,6 +943,31 @@
a2AJMy3V18KOR1wOWsl41JWnKmaydvE+61IZdR3LP1on3KQSbTz+H0VMd+N7
eeC7Y42ZAx/u/8xFbYpVrDMe84sZ7gk6cBuYbWCugXkscIrFBkKUOyCDCn50
4BksGSwbrBismpyW3gBNs/uhAwIAAA==
+ """,
+ """
+ androidx/compose/runtime/StructuralEqualityPolicy.class:
+ H4sIAAAAAAAA/5VVW08bRxT+ZteX9WLAcVJqCE2ThjbmljWkd1xaQohi5FBU
+ R1QtT2N74gysd53dWStRX1B/Sp/70KgPiVqpQu1bf1TVM7suJQYikOU5Z845
+ c+Y7t9m///ntDwAfYoNhiXvtwJftZ07L7/b8UDhB5CnZFU5DBVFLRQF3N55G
+ 3JXq+bbvytbzLBjDt/U93ueOy72O83VzT7TUSv1sTx7vhU989TBSXEnfS/xU
+ T7pYXWEoDEuzSDE4F3SeRYYhU5WeVKsMZnl2Jw8LORtp2Awp9USGDHfeAPmM
+ 4AmgLZ5Gss9d4SmGW+VTEnFCMvs9w0zdDzrOnlDNgEsvpIA8P0EcOluR6/Km
+ K8j7tX1fudJz9vpdR3pKBB53nZqnAjojW2EWlxgsHogYVh6XMW6jiCsMjDMU
+ T15NiiadUH5Du+gwXCnPHrNKpGR1803wfKURklXprLxYmGZId0XQEQyb50rK
+ KWk6DX71gpWfuSce88hVtW7PpXRdZ/ihfNHWPBfac+HP4xres3EDN6kIvUD0
+ pR9R52VbURDEDZTlvZ4rRZvU1ZYb96sNQzepVdtqPFrbWt/IYw6jORLOM1yq
+ D/rjoVC8zRWnDBndvknjzPRi6QVU9H2SP5N6VyGuvcQgDg+mbaNkJP/C4YFt
+ WJoZIWoSHbP++tEoHR4s28VMMVViFaPC7mYt48+fMkbB3JwupKaMSnrZKmSm
+ Yi1x2YR7MLFpFSzS5h5M6MuW6Vm5cMqZhp0/Xjx6C/6zbZCpuL1P+br6TeKm
+ 5vVlKGlm1v5vVBrsdb9NDThel57YirpNETzSc6Unw29xd4cHUu8HwplhX9s8
+ 4F1BQ/ea03zN80Sw7vIwFLTNNWTH4zQD5MFu+FHQEveldjc5cLdzAhgqVL00
+ xUdvJyZ1OYlWqTYZoleJpklPzxPtvqCdowuopXMvMfKCGAOrA2NA4Eta84kB
+ 0dG43mMYJyt9+B5RXfbM/MIrvPXz0OnH8enricXgtOYm8Hasz6BEnIGviB81
+ SFSI3SfrJKYGlywOEJrFd34ZuqFzDJ85uOF4qPTE4V3SaDfbSNGPunZufmHx
+ FWaGfSWxziU2R2gtvD9Aa+GDGK3mbhFnHt2kAesYLptDMRRi4/JRrpPjwMjv
+ ML57idlfsfAiFqSwRqtNZjeobJ9T8HdjdCbWY7pCmQZqpF+kOtzehVmDU0Ol
+ hiUsE4s7NfrCfrQLFuJjfLILO8RoiE9DfBYiF2IsZiZClEJMEv8v0hejE5UH
+ AAA=
"""
)
diff --git a/compose/lint/common/src/main/java/androidx/compose/lint/KotlinUtils.kt b/compose/lint/common/src/main/java/androidx/compose/lint/KotlinUtils.kt
index 9ec00a4..5a39288 100644
--- a/compose/lint/common/src/main/java/androidx/compose/lint/KotlinUtils.kt
+++ b/compose/lint/common/src/main/java/androidx/compose/lint/KotlinUtils.kt
@@ -16,12 +16,17 @@
package androidx.compose.lint
+import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtLambdaExpression
import org.jetbrains.kotlin.psi.KtParameter
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
+import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.uast.ULambdaExpression
+import org.jetbrains.uast.kotlin.KotlinUFunctionCallExpression
+import org.jetbrains.uast.kotlin.KotlinUastResolveProviderService
+import org.jetbrains.uast.sourcePsiElement
import org.jetbrains.uast.toUElement
/**
@@ -143,3 +148,10 @@
else -> true
}
}
+
+fun KotlinUFunctionCallExpression.resolveCall() =
+ (sourcePsiElement as KtElement).getResolvedCall(
+ context = sourcePsi.project
+ .getService(KotlinUastResolveProviderService::class.java)
+ .getBindingContext(sourcePsi)
+ )
diff --git a/compose/lint/common/src/main/java/androidx/compose/lint/Names.kt b/compose/lint/common/src/main/java/androidx/compose/lint/Names.kt
index 71b1990..f51de29 100644
--- a/compose/lint/common/src/main/java/androidx/compose/lint/Names.kt
+++ b/compose/lint/common/src/main/java/androidx/compose/lint/Names.kt
@@ -41,6 +41,10 @@
val State = Name(PackageName, "State")
val MutableState = Name(PackageName, "MutableState")
val MutableStateOf = Name(PackageName, "mutableStateOf")
+ val MutableIntStateOf = Name(PackageName, "mutableIntStateOf")
+ val MutableLongStateOf = Name(PackageName, "mutableLongStateOf")
+ val MutableFloatStateOf = Name(PackageName, "mutableFloatStateOf")
+ val MutableDoubleStateOf = Name(PackageName, "mutableDoubleStateOf")
val MutableStateListOf = Name(PackageName, "mutableStateListOf")
val MutableStateMapOf = Name(PackageName, "mutableStateMapOf")
val ProduceState = Name(PackageName, "produceState")
@@ -50,6 +54,7 @@
val LaunchedEffect = Name(PackageName, "LaunchedEffect")
val ReusableContent = Name(PackageName, "ReusableContent")
val Key = Name(PackageName, "key")
+ val StructuralEqualityPolicy = Name(PackageName, "structuralEqualityPolicy")
}
object Ui {
val PackageName = Package("androidx.compose.ui")
diff --git a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/AutoboxingStateCreationDetector.kt b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/AutoboxingStateCreationDetector.kt
new file mode 100644
index 0000000..b013276
--- /dev/null
+++ b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/AutoboxingStateCreationDetector.kt
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress("UnstableApiUsage")
+
+package androidx.compose.runtime.lint
+
+import androidx.compose.lint.Name
+import androidx.compose.lint.Names
+import androidx.compose.lint.isInPackageName
+import androidx.compose.lint.resolveCall
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.LintFix
+import com.android.tools.lint.detector.api.Location
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.intellij.psi.PsiMethod
+import java.util.EnumSet
+import org.jetbrains.kotlin.js.descriptorUtils.getJetTypeFqName
+import org.jetbrains.kotlin.psi.KtTypeArgumentList
+import org.jetbrains.kotlin.psi.KtValueArgumentList
+import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.kotlin.KotlinUFunctionCallExpression
+import org.jetbrains.uast.skipParenthesizedExprDown
+
+/**
+ * Suggests alternative functions to mutableStateOf<T>() if all of the following are true:
+ * - a snapshot mutation policy argument is not specified (or it is structural equivalent policy)
+ * - `T` is in the [replacements] map
+ * - `T` is a non-nullable type
+ */
+class AutoboxingStateCreationDetector : Detector(), SourceCodeScanner {
+
+ /**
+ * Map of canonical PSI types to the fully-qualified function that should be used to
+ * create MutableState instances of said type.
+ */
+ private val replacements = mapOf(
+ "kotlin.Int" to Names.Runtime.MutableIntStateOf,
+ "kotlin.Long" to Names.Runtime.MutableLongStateOf,
+ "kotlin.Float" to Names.Runtime.MutableFloatStateOf,
+ "kotlin.Double" to Names.Runtime.MutableDoubleStateOf,
+ )
+
+ override fun getApplicableMethodNames() = listOf(Names.Runtime.MutableStateOf.shortName)
+
+ override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
+ if (!method.isInPackageName(Names.Runtime.PackageName)) return
+
+ val replacement = getSuggestedReplacementName(node as KotlinUFunctionCallExpression)
+ ?: return
+
+ context.report(
+ issue = AutoboxingStateCreation,
+ scope = node,
+ location = context.getNameLocation(node),
+ message = "Prefer `${replacement.shortName}` instead of `${method.name}`",
+ quickfixData = createLintFix(context, node, replacement)
+ )
+ }
+
+ private fun createLintFix(
+ context: JavaContext,
+ node: UCallExpression,
+ replacementFunction: Name
+ ): LintFix {
+ val fixes = listOfNotNull(
+ // Replace the function name
+ LintFix.create()
+ .replace()
+ .range(context.getNameLocation(node))
+ .with(replacementFunction.javaFqn)
+ .shortenNames(true)
+ .build(),
+
+ // Remove the type arguments list (if present)
+ context.getLocationOfTypeArguments(node)
+ ?.let { LintFix.create().replace().range(it).with("").build() },
+
+ // Remove the SnapshotMutationPolicy argument (if present)
+ context.getLocationOfArgumentsList(node)
+ ?.takeIf { node.getArgumentForParameter(MUTATION_POLICY_PARAM_IDX) != null }
+ ?.let { argsListLocation ->
+ node.getArgumentForParameter(VALUE_PARAM_IDX)?.sourcePsi?.text
+ ?.let { valueArg ->
+ LintFix.create()
+ .replace()
+ .range(argsListLocation)
+ .with("($valueArg)")
+ .build()
+ }
+ }
+ )
+
+ return LintFix.create()
+ .name("Replace with ${replacementFunction.shortName}")
+ .composite(*fixes.toTypedArray())
+ }
+
+ private fun JavaContext.getLocationOfTypeArguments(node: UCallExpression): Location? {
+ val typeArgsList = node.sourcePsi?.children?.firstIsInstanceOrNull<KtTypeArgumentList>()
+ ?: return null
+ return getLocation(typeArgsList)
+ }
+
+ private fun JavaContext.getLocationOfArgumentsList(node: UCallExpression): Location? {
+ val argsList = node.sourcePsi?.children?.firstIsInstanceOrNull<KtValueArgumentList>()
+ ?: return null
+ return getLocation(argsList)
+ }
+
+ private fun getSuggestedReplacementName(
+ invocation: KotlinUFunctionCallExpression
+ ): Name? {
+ if (!usesStructuralEqualityPolicy(invocation)) return null
+
+ val resolvedCall = invocation.resolveCall() ?: return null
+ val stateType = resolvedCall.typeArguments.asIterable().single().value
+ return when {
+ stateType.isMarkedNullable -> null
+ else -> replacements[stateType.getJetTypeFqName(true)]
+ }
+ }
+
+ private fun usesStructuralEqualityPolicy(
+ invocation: KotlinUFunctionCallExpression
+ ): Boolean {
+ val policyExpr = invocation.valueArguments.getOrNull(MUTATION_POLICY_PARAM_IDX)
+ ?.skipParenthesizedExprDown()
+ ?: return true // No argument passed; we're using the default policy
+
+ val policyMethod = (policyExpr as? KotlinUFunctionCallExpression)?.resolve()
+ ?: return false // Argument isn't a direct function call. Assume it's a more complex
+ // policy, or isn't always the structural equality policy.
+
+ return policyMethod.isInPackageName(Names.Runtime.PackageName) &&
+ policyMethod.name == Names.Runtime.StructuralEqualityPolicy.shortName
+ }
+
+ companion object {
+ private const val VALUE_PARAM_IDX = 0
+ private const val MUTATION_POLICY_PARAM_IDX = 1
+
+ val AutoboxingStateCreation = Issue.create(
+ id = "AutoboxingStateCreation",
+ briefDescription = "`State<T>` will autobox values assigned to this state. " +
+ "Use a specialized state type instead.",
+ explanation = "Calling `mutableStateOf<T>()` when `T` is either backed by a " +
+ "primitive type on the JVM or is a value class results in a state implementation " +
+ "that requires all state values to be boxed. This usually causes an additional " +
+ "allocation for each state write, and adds some additional work to auto-unbox " +
+ "values when reading the value of the state. Instead, prefer to use a " +
+ "specialized primitive state implementation for `Int`, `Long`, `Float`, and " +
+ "`Double` when the state does not need to track null values and does not " +
+ "override the default `SnapshotMutationPolicy`.",
+ category = Category.PERFORMANCE, priority = 3, severity = Severity.WARNING,
+ implementation = Implementation(
+ AutoboxingStateCreationDetector::class.java,
+ EnumSet.of(Scope.JAVA_FILE)
+ )
+ )
+ }
+}
\ No newline at end of file
diff --git a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/RuntimeIssueRegistry.kt b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/RuntimeIssueRegistry.kt
index 8a2444e..1ead6c8 100644
--- a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/RuntimeIssueRegistry.kt
+++ b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/RuntimeIssueRegistry.kt
@@ -31,6 +31,7 @@
override val minApi = CURRENT_API
override val issues get() = listOf(
AutoboxingStateValuePropertyDetector.AutoboxingStateValueProperty,
+ AutoboxingStateCreationDetector.AutoboxingStateCreation,
ComposableCoroutineCreationDetector.CoroutineCreationDuringComposition,
ComposableFlowOperatorDetector.FlowOperatorInvokedInComposition,
ComposableLambdaParameterDetector.ComposableLambdaParameterNaming,
diff --git a/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/AutoboxingStateCreationDetectorTest.kt b/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/AutoboxingStateCreationDetectorTest.kt
new file mode 100644
index 0000000..06eb0d1
--- /dev/null
+++ b/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/AutoboxingStateCreationDetectorTest.kt
@@ -0,0 +1,505 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.runtime.lint
+
+import androidx.compose.lint.test.Stubs
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.jetbrains.kotlin.util.capitalizeDecapitalize.toLowerCaseAsciiOnly
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/* ktlint-disable max-line-length */
+@RunWith(Parameterized::class)
+class AutoboxingStateCreationDetectorTest(
+ typeUnderTest: TypeUnderTest
+) : LintDetectorTest() {
+
+ private val fqType = typeUnderTest.fqName
+ private val type = typeUnderTest.typeName
+ private val stateValue = typeUnderTest.sampleValue
+
+ private val primitiveStateStub = kotlin(
+ """
+ package androidx.compose.runtime
+
+ import kotlin.reflect.KProperty
+ import $fqType
+
+ fun mutable${type}StateOf(value: $type): Mutable${type}State {
+ TODO("Not implemented in lint stubs.")
+ }
+
+ interface Mutable${type}State : State<$type> {
+ override var value: $type
+ var ${type.toLowerCaseAsciiOnly()}Value: $type
+ }
+
+ @Suppress("NOTHING_TO_INLINE")
+ inline operator fun Mutable${type}State.getValue(
+ thisObj: Any?,
+ property: KProperty<*>
+ ): $type = ${type.toLowerCaseAsciiOnly()}Value
+
+ @Suppress("NOTHING_TO_INLINE")
+ inline operator fun Mutable${type}State.setValue(
+ thisObj: Any?,
+ property: KProperty<*>,
+ value: $type
+ ) {
+ ${type.toLowerCaseAsciiOnly()}Value = value
+ }
+ """
+ )
+
+ override fun getDetector(): Detector = AutoboxingStateCreationDetector()
+
+ override fun getIssues(): MutableList<Issue> =
+ mutableListOf(AutoboxingStateCreationDetector.AutoboxingStateCreation)
+
+ @Test
+ fun testTrivialMutableStateOf_thatCouldBeMutablePrimitiveStateOf() {
+ lint().files(
+ primitiveStateStub,
+ Stubs.Composable,
+ Stubs.SnapshotState,
+ kotlin(
+ """
+ package androidx.compose.runtime.lint.test
+
+ import androidx.compose.runtime.*
+ import $fqType
+
+ fun valueAssignment() {
+ val state = mutableStateOf<$type>($stateValue)
+ state.value = $stateValue
+ }
+ """
+ )
+ ).run().expect(
+ """
+src/androidx/compose/runtime/lint/test/test.kt:8: Warning: Prefer mutable${type}StateOf instead of mutableStateOf [AutoboxingStateCreation]
+ val state = mutableStateOf<$type>($stateValue)
+ ~~~~~~~~~~~~~~
+0 errors, 1 warnings
+ """
+ ).expectFixDiffs(
+ """
+Fix for src/androidx/compose/runtime/lint/test/test.kt line 8: Replace with mutable${type}StateOf:
+@@ -8 +8
+- val state = mutableStateOf<$type>($stateValue)
++ val state = mutable${type}StateOf($stateValue)
+ """
+ )
+ }
+
+ @Test
+ fun testInferredMutableStateOf_thatCouldBeMutablePrimitiveStateOf() {
+ lint().files(
+ primitiveStateStub,
+ Stubs.Composable,
+ Stubs.SnapshotState,
+ kotlin(
+ """
+ package androidx.compose.runtime.lint.test
+
+ import androidx.compose.runtime.*
+ import $fqType
+
+ fun valueAssignment() {
+ val state = mutableStateOf($stateValue)
+ state.value = $stateValue
+ }
+ """
+ )
+ ).run().expect(
+ """
+src/androidx/compose/runtime/lint/test/test.kt:8: Warning: Prefer mutable${type}StateOf instead of mutableStateOf [AutoboxingStateCreation]
+ val state = mutableStateOf($stateValue)
+ ~~~~~~~~~~~~~~
+0 errors, 1 warnings
+ """
+ ).expectFixDiffs(
+ """
+Fix for src/androidx/compose/runtime/lint/test/test.kt line 8: Replace with mutable${type}StateOf:
+@@ -8 +8
+- val state = mutableStateOf($stateValue)
++ val state = mutable${type}StateOf($stateValue)
+ """
+ )
+ }
+
+ @Test
+ fun testFqMutableStateOf_thatCouldBeMutablePrimitiveStateOf() {
+ lint().files(
+ primitiveStateStub,
+ Stubs.Composable,
+ Stubs.SnapshotState,
+ kotlin(
+ """
+ package androidx.compose.runtime.lint.test
+
+ import androidx.compose.runtime.*
+ import $fqType
+
+ fun valueAssignment() {
+ val state = mutableStateOf<$fqType>($stateValue)
+ state.value = $stateValue
+ }
+ """
+ )
+ ).run().expect(
+ """
+src/androidx/compose/runtime/lint/test/test.kt:8: Warning: Prefer mutable${type}StateOf instead of mutableStateOf [AutoboxingStateCreation]
+ val state = mutableStateOf<$fqType>($stateValue)
+ ~~~~~~~~~~~~~~
+0 errors, 1 warnings
+ """
+ ).expectFixDiffs(
+ """
+Fix for src/androidx/compose/runtime/lint/test/test.kt line 8: Replace with mutable${type}StateOf:
+@@ -8 +8
+- val state = mutableStateOf<$fqType>($stateValue)
++ val state = mutable${type}StateOf($stateValue)
+ """
+ )
+ }
+
+ @Test
+ fun testStateDelegate_withExplicitType_thatCouldBeMutablePrimitiveStateOf() {
+ lint().files(
+ primitiveStateStub,
+ Stubs.Composable,
+ Stubs.SnapshotState,
+ kotlin(
+ """
+ package androidx.compose.runtime.lint.test
+
+ import androidx.compose.runtime.*
+ import $fqType
+
+ fun propertyDelegation() {
+ var state by mutableStateOf<$type>($stateValue)
+ state = $stateValue
+ }
+ """
+ )
+ ).run().expect(
+ """
+src/androidx/compose/runtime/lint/test/test.kt:8: Warning: Prefer mutable${type}StateOf instead of mutableStateOf [AutoboxingStateCreation]
+ var state by mutableStateOf<$type>($stateValue)
+ ~~~~~~~~~~~~~~
+0 errors, 1 warnings
+ """
+ ).expectFixDiffs(
+ """
+Fix for src/androidx/compose/runtime/lint/test/test.kt line 8: Replace with mutable${type}StateOf:
+@@ -8 +8
+- var state by mutableStateOf<$type>($stateValue)
++ var state by mutable${type}StateOf($stateValue)
+ """
+ )
+ }
+
+ @Test
+ fun testStateDelegate_withInferredType_thatCouldBeMutablePrimitiveStateOf() {
+ lint().files(
+ primitiveStateStub,
+ Stubs.Composable,
+ Stubs.SnapshotState,
+ kotlin(
+ """
+ package androidx.compose.runtime.lint.test
+
+ import androidx.compose.runtime.*
+ import $fqType
+
+ fun propertyDelegation() {
+ var state by mutableStateOf($stateValue)
+ state = $stateValue
+ }
+ """
+ )
+ ).run().expect(
+ """
+src/androidx/compose/runtime/lint/test/test.kt:8: Warning: Prefer mutable${type}StateOf instead of mutableStateOf [AutoboxingStateCreation]
+ var state by mutableStateOf($stateValue)
+ ~~~~~~~~~~~~~~
+0 errors, 1 warnings
+ """
+ ).expectFixDiffs(
+ """
+Fix for src/androidx/compose/runtime/lint/test/test.kt line 8: Replace with mutable${type}StateOf:
+@@ -8 +8
+- var state by mutableStateOf($stateValue)
++ var state by mutable${type}StateOf($stateValue)
+ """
+ )
+ }
+
+ @Test
+ fun testStateDelegate_withInferredType_andInternalSetter_thatCouldBeMutablePrimitiveStateOf() {
+ lint().files(
+ primitiveStateStub,
+ Stubs.Composable,
+ Stubs.SnapshotState,
+ kotlin(
+ """
+ package androidx.compose.runtime.lint.test
+
+ import androidx.compose.runtime.*
+ import $fqType
+
+ class Test(initialValue: $type = $stateValue) {
+ var state by mutableStateOf(initialValue)
+ private set
+ }
+ """
+ )
+ ).run().expect(
+ """
+src/androidx/compose/runtime/lint/test/Test.kt:8: Warning: Prefer mutable${type}StateOf instead of mutableStateOf [AutoboxingStateCreation]
+ var state by mutableStateOf(initialValue)
+ ~~~~~~~~~~~~~~
+0 errors, 1 warnings
+ """
+ ).expectFixDiffs(
+ """
+Fix for src/androidx/compose/runtime/lint/test/Test.kt line 7: Replace with mutable${type}StateOf:
+@@ -8 +8
+- var state by mutableStateOf(initialValue)
++ var state by mutable${type}StateOf(initialValue)
+ """
+ )
+ }
+
+ @Test
+ fun testStateDelegate_withTypeInferredFromProperty_thatCouldBeMutablePrimitiveStateOf() {
+ lint().files(
+ primitiveStateStub,
+ Stubs.Composable,
+ Stubs.SnapshotState,
+ kotlin(
+ """
+ package androidx.compose.runtime.lint.test
+
+ import androidx.compose.runtime.*
+ import $fqType
+
+ fun propertyDelegation() {
+ var state: $type by mutableStateOf($stateValue)
+ state = $stateValue
+ }
+ """
+ )
+ ).run().expect(
+ """
+src/androidx/compose/runtime/lint/test/test.kt:8: Warning: Prefer mutable${type}StateOf instead of mutableStateOf [AutoboxingStateCreation]
+ var state: $type by mutableStateOf($stateValue)
+ ${" ".repeat(type.length)} ~~~~~~~~~~~~~~
+0 errors, 1 warnings
+ """
+ ).expectFixDiffs(
+ """
+Fix for src/androidx/compose/runtime/lint/test/test.kt line 8: Replace with mutable${type}StateOf:
+@@ -8 +8
+- var state: $type by mutableStateOf($stateValue)
++ var state: $type by mutable${type}StateOf($stateValue)
+ """
+ )
+ }
+
+ @Test
+ fun testStateDelegate_withNullableInferredType_cannotBeReplacedWithMutablePrimitiveStateOf() {
+ lint().files(
+ primitiveStateStub,
+ Stubs.Composable,
+ Stubs.SnapshotState,
+ kotlin(
+ """
+ package androidx.compose.runtime.lint.test
+
+ import androidx.compose.runtime.*
+ import $fqType
+
+ fun propertyDelegation() {
+ var state: $type? by mutableStateOf($stateValue)
+ state = $stateValue
+ }
+ """
+ )
+ ).run().expectClean()
+ }
+
+ @Test
+ fun testInferredMutableStateOf_withExplicitEqualityPolicy_thatCouldBeMutablePrimitiveStateOf() {
+ lint().files(
+ primitiveStateStub,
+ Stubs.Composable,
+ Stubs.SnapshotState,
+ kotlin(
+ """
+ package androidx.compose.runtime.lint.test
+
+ import androidx.compose.runtime.*
+ import $fqType
+
+ fun valueAssignment() {
+ val state = mutableStateOf($stateValue, structuralEqualityPolicy())
+ state.value = $stateValue
+ }
+ """
+ )
+ ).run().expect(
+ """
+src/androidx/compose/runtime/lint/test/test.kt:8: Warning: Prefer mutable${type}StateOf instead of mutableStateOf [AutoboxingStateCreation]
+ val state = mutableStateOf($stateValue, structuralEqualityPolicy())
+ ~~~~~~~~~~~~~~
+0 errors, 1 warnings
+ """
+ ).expectFixDiffs(
+ """
+Fix for src/androidx/compose/runtime/lint/test/test.kt line 8: Replace with mutable${type}StateOf:
+@@ -8 +8
+- val state = mutableStateOf($stateValue, structuralEqualityPolicy())
++ val state = mutable${type}StateOf($stateValue)
+ """
+ )
+ }
+
+ @Test
+ fun testNonStructuralEqualityPolicy_cannotBeReplacedWithMutablePrimitiveStateOf() {
+ lint().files(
+ primitiveStateStub,
+ Stubs.Composable,
+ Stubs.SnapshotState,
+ kotlin(
+ """
+ package androidx.compose.runtime.lint.test
+
+ import androidx.compose.runtime.*
+ import $fqType
+
+ fun valueAssignment() {
+ val state = mutableStateOf($stateValue, neverEqualPolicy())
+ state.value = $stateValue
+ }
+ """
+ )
+ ).run().expectClean()
+ }
+
+ @Test
+ fun testNullableMutableStateOf_cannotBeReplacedWithMutablePrimitiveStateOf() {
+ lint().files(
+ primitiveStateStub,
+ Stubs.Composable,
+ Stubs.SnapshotState,
+ kotlin(
+ """
+ package androidx.compose.runtime.lint.test
+
+ import androidx.compose.runtime.*
+ import $fqType
+
+ fun valueAssignment() {
+ val state = mutableStateOf<$type?>($stateValue)
+ state.value = $stateValue
+ }
+ """
+ )
+ ).run().expectClean()
+ }
+
+ @Test
+ fun testInferredNullableMutableStateOf_cannotBeReplacedWithMutablePrimitiveStateOf() {
+ lint().files(
+ primitiveStateStub,
+ Stubs.Composable,
+ Stubs.SnapshotState,
+ kotlin(
+ """
+ package androidx.compose.runtime.lint.test
+
+ import androidx.compose.runtime.*
+ import $fqType
+
+ fun valueAssignment() {
+ val state: MutableState<$type?> = mutableStateOf($stateValue)
+ state.value = $stateValue
+ }
+ """
+ )
+ ).run().expectClean()
+ }
+
+ @Test
+ fun testInferredByCastNullableMutableStateOf_cannotBeReplacedWithMutablePrimitiveStateOf() {
+ lint().files(
+ primitiveStateStub,
+ Stubs.Composable,
+ Stubs.SnapshotState,
+ kotlin(
+ """
+ package androidx.compose.runtime.lint.test
+
+ import androidx.compose.runtime.*
+ import $fqType
+
+ fun valueAssignment() {
+ val state = mutableStateOf($stateValue as $type?)
+ state.value = $stateValue
+ }
+ """
+ )
+ ).run().expectClean()
+ }
+
+ companion object {
+ @JvmStatic
+ @Parameterized.Parameters(name = "{0}")
+ fun initParameters() = listOf(
+ testCase("kotlin.Int", "42"),
+ testCase("kotlin.Long", "0xABCDEF1234"),
+ testCase("kotlin.Float", "1.5f"),
+ testCase("kotlin.Double", "1.024")
+ )
+
+ private fun testCase(fqName: String, value: String): TypeUnderTest {
+ val parts = fqName.split('.')
+ return TypeUnderTest(
+ fqName = fqName,
+ typeName = parts.last(),
+ packageName = parts.dropLast(1).joinToString(separator = "."),
+ sampleValue = value
+ )
+ }
+ }
+
+ data class TypeUnderTest(
+ val fqName: String,
+ val typeName: String,
+ val packageName: String,
+ val sampleValue: String,
+ ) {
+ // Formatting for test parameter list.
+ override fun toString() = "type = $fqName"
+ }
+}
+/* ktlint-enable max-line-length */
\ No newline at end of file