Skip to content

Commit 48534a3

Browse files
committed
reanalyze: add scoped @@live/@@dead annotations
Add support for @@live/@@dead to set a default liveness annotation for subsequent items in a module/file scope, inheriting into nested modules and allowing later overrides. Add focused DCE fixtures covering override + scope boundaries and live-vs-dead liveness propagation. Update reanalyze deadcode expected output.
1 parent df33e5d commit 48534a3

File tree

5 files changed

+170
-22
lines changed

5 files changed

+170
-22
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626

2727
#### :rocket: New Feature
2828

29+
- Reanalyze: add scoped `@@live`/`@@dead` annotations for marking module/file sections as live or dead. https://github.com/rescript-lang/rescript/pull/8197
30+
2931
#### :bug: Bug fix
3032

3133
- Reanalyze: fix reactive/server stale results when cross-file references change without changing dead declarations (non-transitive mode). https://github.com/rescript-lang/rescript/pull/8173

analysis/reanalyze/src/CollectAnnotations.ml

Lines changed: 46 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,15 @@
55

66
open DeadCommon
77

8-
let processAttributes ~state ~config ~doGenType ~name ~pos attributes =
8+
type scope_default = FileAnnotations.annotated_as option
9+
10+
let processAttributes ~(scope_default : scope_default) ~state ~config ~doGenType
11+
~name ~pos attributes =
12+
(match scope_default with
13+
| Some FileAnnotations.Live -> FileAnnotations.annotate_live state pos
14+
| Some FileAnnotations.Dead -> FileAnnotations.annotate_dead state pos
15+
| Some FileAnnotations.GenType -> FileAnnotations.annotate_gentype state pos
16+
| None -> ());
917
let getPayloadFun f = attributes |> Annotation.getAttributePayload f in
1018
let getPayload (x : string) =
1119
attributes |> Annotation.getAttributePayload (( = ) x)
@@ -39,15 +47,29 @@ let processAttributes ~state ~config ~doGenType ~name ~pos attributes =
3947
let collectExportLocations ~state ~config ~doGenType =
4048
let super = Tast_mapper.default in
4149
let currentlyDisableWarnings = ref false in
50+
let currentScopeDefault : scope_default ref = ref None in
51+
52+
let scopeDefaultFromToplevelAttribute (attribute : Parsetree.attribute) :
53+
scope_default =
54+
let attrs = [attribute] in
55+
let getPayload (x : string) =
56+
attrs |> Annotation.getAttributePayload (( = ) x)
57+
in
58+
if getPayload "dead" <> None then Some FileAnnotations.Dead
59+
else if getPayload "live" <> None then Some FileAnnotations.Live
60+
else if getPayload "genType" <> None then Some FileAnnotations.GenType
61+
else None
62+
in
63+
4264
let value_binding self
4365
({vb_attributes; vb_pat} as value_binding : Typedtree.value_binding) =
4466
(match vb_pat.pat_desc with
4567
| Tpat_var (id, {loc = {loc_start = pos}})
4668
| Tpat_alias ({pat_desc = Tpat_any}, id, {loc = {loc_start = pos}}) ->
4769
if !currentlyDisableWarnings then FileAnnotations.annotate_live state pos;
4870
vb_attributes
49-
|> processAttributes ~state ~config ~doGenType ~name:(id |> Ident.name)
50-
~pos
71+
|> processAttributes ~scope_default:!currentScopeDefault ~state ~config
72+
~doGenType ~name:(id |> Ident.name) ~pos
5173
| _ -> ());
5274
super.value_binding self value_binding
5375
in
@@ -58,8 +80,8 @@ let collectExportLocations ~state ~config ~doGenType =
5880
|> List.iter
5981
(fun ({ld_attributes; ld_loc} : Typedtree.label_declaration) ->
6082
toplevelAttrs @ ld_attributes
61-
|> processAttributes ~state ~config ~doGenType:false ~name:""
62-
~pos:ld_loc.loc_start)
83+
|> processAttributes ~scope_default:!currentScopeDefault ~state
84+
~config ~doGenType:false ~name:"" ~pos:ld_loc.loc_start)
6385
| Ttype_variant constructorDeclarations ->
6486
constructorDeclarations
6587
|> List.iter
@@ -74,14 +96,15 @@ let collectExportLocations ~state ~config ~doGenType =
7496
(fun ({ld_attributes; ld_loc} : Typedtree.label_declaration)
7597
->
7698
toplevelAttrs @ cd_attributes @ ld_attributes
77-
|> processAttributes ~state ~config ~doGenType:false
78-
~name:"" ~pos:ld_loc.loc_start)
99+
|> processAttributes ~scope_default:!currentScopeDefault
100+
~state ~config ~doGenType:false ~name:""
101+
~pos:ld_loc.loc_start)
79102
flds
80103
| Cstr_tuple _ -> ()
81104
in
82105
toplevelAttrs @ cd_attributes
83-
|> processAttributes ~state ~config ~doGenType:false ~name:""
84-
~pos:cd_loc.loc_start)
106+
|> processAttributes ~scope_default:!currentScopeDefault ~state
107+
~config ~doGenType:false ~name:"" ~pos:cd_loc.loc_start)
85108
| _ -> ());
86109
super.type_kind self typeKind
87110
in
@@ -96,36 +119,42 @@ let collectExportLocations ~state ~config ~doGenType =
96119
Typedtree.value_description) =
97120
if !currentlyDisableWarnings then FileAnnotations.annotate_live state pos;
98121
val_attributes
99-
|> processAttributes ~state ~config ~doGenType ~name:(val_id |> Ident.name)
100-
~pos;
122+
|> processAttributes ~scope_default:!currentScopeDefault ~state ~config
123+
~doGenType ~name:(val_id |> Ident.name) ~pos;
101124
super.value_description self value_description
102125
in
103126
let structure_item self (item : Typedtree.structure_item) =
104127
(match item.str_desc with
105-
| Tstr_attribute attribute
106-
when [attribute] |> Annotation.isOcamlSuppressDeadWarning ->
107-
currentlyDisableWarnings := true
128+
| Tstr_attribute attribute -> (
129+
match scopeDefaultFromToplevelAttribute attribute with
130+
| Some _ as newDefault -> currentScopeDefault := newDefault
131+
| None -> ())
108132
| _ -> ());
109133
super.structure_item self item
110134
in
111135
let structure self (structure : Typedtree.structure) =
112136
let oldDisableWarnings = !currentlyDisableWarnings in
137+
let oldScopeDefault = !currentScopeDefault in
113138
super.structure self structure |> ignore;
114139
currentlyDisableWarnings := oldDisableWarnings;
140+
currentScopeDefault := oldScopeDefault;
115141
structure
116142
in
117143
let signature_item self (item : Typedtree.signature_item) =
118144
(match item.sig_desc with
119-
| Tsig_attribute attribute
120-
when [attribute] |> Annotation.isOcamlSuppressDeadWarning ->
121-
currentlyDisableWarnings := true
145+
| Tsig_attribute attribute -> (
146+
match scopeDefaultFromToplevelAttribute attribute with
147+
| Some _ as newDefault -> currentScopeDefault := newDefault
148+
| None -> ())
122149
| _ -> ());
123150
super.signature_item self item
124151
in
125152
let signature self (signature : Typedtree.signature) =
126153
let oldDisableWarnings = !currentlyDisableWarnings in
154+
let oldScopeDefault = !currentScopeDefault in
127155
super.signature self signature |> ignore;
128156
currentlyDisableWarnings := oldDisableWarnings;
157+
currentScopeDefault := oldScopeDefault;
129158
signature
130159
in
131160
{

tests/analysis_tests/tests-reanalyze/deadcode/expected/deadcode.txt

Lines changed: 83 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1312,6 +1312,26 @@
13121312
addTypeReference RepeatedLabel.res:12:16 --> RepeatedLabel.res:8:2
13131313
addValueReference RepeatedLabel.res:14:7 --> RepeatedLabel.res:12:4
13141314
Scanning RequireCond.cmt Source:RequireCond.res
1315+
Scanning ScopedAnnotationsLiveVsDead.cmt Source:ScopedAnnotationsLiveVsDead.res
1316+
addValueDeclaration +leafLive ScopedAnnotationsLiveVsDead.res:1:4 path:+ScopedAnnotationsLiveVsDead
1317+
addValueDeclaration +middleLive ScopedAnnotationsLiveVsDead.res:2:4 path:+ScopedAnnotationsLiveVsDead
1318+
addValueDeclaration +root ScopedAnnotationsLiveVsDead.res:6:6 path:+ScopedAnnotationsLiveVsDead.LiveScope
1319+
addValueDeclaration +stillDeadOutside ScopedAnnotationsLiveVsDead.res:9:4 path:+ScopedAnnotationsLiveVsDead
1320+
addValueDeclaration +leafDead ScopedAnnotationsLiveVsDead.res:11:4 path:+ScopedAnnotationsLiveVsDead
1321+
addValueDeclaration +middleDead ScopedAnnotationsLiveVsDead.res:12:4 path:+ScopedAnnotationsLiveVsDead
1322+
addValueDeclaration +root ScopedAnnotationsLiveVsDead.res:16:6 path:+ScopedAnnotationsLiveVsDead.DeadScope
1323+
addValueReference ScopedAnnotationsLiveVsDead.res:2:4 --> ScopedAnnotationsLiveVsDead.res:1:4
1324+
addValueReference ScopedAnnotationsLiveVsDead.res:6:6 --> ScopedAnnotationsLiveVsDead.res:2:4
1325+
addValueReference ScopedAnnotationsLiveVsDead.res:12:4 --> ScopedAnnotationsLiveVsDead.res:11:4
1326+
addValueReference ScopedAnnotationsLiveVsDead.res:16:6 --> ScopedAnnotationsLiveVsDead.res:12:4
1327+
Scanning ScopedAnnotationsOverride.cmt Source:ScopedAnnotationsOverride.res
1328+
addValueDeclaration +before ScopedAnnotationsOverride.res:2:6 path:+ScopedAnnotationsOverride.M
1329+
addValueDeclaration +live1 ScopedAnnotationsOverride.res:5:6 path:+ScopedAnnotationsOverride.M
1330+
addValueDeclaration +nestedLive ScopedAnnotationsOverride.res:8:8 path:+ScopedAnnotationsOverride.M.NestedInLive
1331+
addValueDeclaration +dead1 ScopedAnnotationsOverride.res:12:6 path:+ScopedAnnotationsOverride.M
1332+
addValueDeclaration +nestedDead ScopedAnnotationsOverride.res:15:8 path:+ScopedAnnotationsOverride.M.NestedInDead
1333+
addValueDeclaration +live2 ScopedAnnotationsOverride.res:19:6 path:+ScopedAnnotationsOverride.M
1334+
addValueDeclaration +afterModules ScopedAnnotationsOverride.res:22:4 path:+ScopedAnnotationsOverride
13151335
Scanning Shadow.cmt Source:Shadow.res
13161336
addValueDeclaration +test Shadow.res:2:4 path:+Shadow
13171337
addValueDeclaration +test Shadow.res:5:4 path:+Shadow
@@ -1807,9 +1827,9 @@
18071827

18081828
Forward Liveness Analysis
18091829

1810-
decls: 641
1830+
decls: 655
18111831
roots(external targets): 122
1812-
decl-deps: decls_with_out=371 edges_to_decls=248
1832+
decl-deps: decls_with_out=375 edges_to_decls=252
18131833

18141834
Root (annotated): Value +Hooks.+default
18151835
Root (external ref): Value +FirstClassModules.M.InnerModule2.+k
@@ -1821,6 +1841,7 @@ Forward Liveness Analysis
18211841
Root (external ref): Value +CreateErrorHandler2.Error2.+notification
18221842
Root (annotated): Value +DeadTest.+fortyTwoButExported
18231843
Root (annotated): Value +Docstrings.+grouped
1844+
Root (annotated): Value +ScopedAnnotationsOverride.M.+live1
18241845
Root (external ref): RecordLabel +DeadTest.inlineRecord.IR.b
18251846
Root (annotated): Value +NestedModules.Universe.Nested2.+nested2Function
18261847
Root (annotated): Value +Tuples.+marry
@@ -1938,6 +1959,7 @@ Forward Liveness Analysis
19381959
Root (annotated): Value +Uncurried.+callback
19391960
Root (annotated): Value +TestPromise.+convert
19401961
Root (external ref): Value +EmptyArray.Z.+make
1962+
Root (annotated): Value +ScopedAnnotationsLiveVsDead.LiveScope.+root
19411963
Root (external ref): Value +Newton.+result
19421964
Root (annotated): Value +Records.+findAllAddresses
19431965
Root (annotated): Value +Variants.+id2
@@ -2025,6 +2047,7 @@ Forward Liveness Analysis
20252047
Root (annotated): Value +NestedModules.Universe.Nested2.+nested2Value
20262048
Root (annotated): Value +Records.+testMyObj
20272049
Root (external ref): VariantCase DeadTypeTest.deadType.InBoth
2050+
Root (annotated): Value +ScopedAnnotationsOverride.M.+live2
20282051
Root (annotated): Value +Records.+testMyRecBsAs2
20292052
Root (annotated): Value +VariantsWithPayload.+testManyPayloads
20302053
Root (annotated): Value +FirstClassModules.+someFunctorAsFunction
@@ -2049,6 +2072,7 @@ Forward Liveness Analysis
20492072
Root (annotated): Value +ImportJsValue.+useColor
20502073
Root (annotated): Value +Records.+getPayload
20512074
Root (external ref): VariantCase +Unison.break.IfNeed
2075+
Root (annotated): Value +ScopedAnnotationsOverride.M.NestedInLive.+nestedLive
20522076
Root (external ref): Value +FirstClassModules.M.+y
20532077
Root (annotated): Value +ModuleAliases.+testInner2
20542078
Root (annotated): RecordLabel +ImportHooks.props.person
@@ -2112,7 +2136,7 @@ Forward Liveness Analysis
21122136
Root (annotated): Value +UseImportJsValue.+useGetProp
21132137
Root (external ref): RecordLabel +Hooks.RenderPropRequiresConversion.props.renderVehicle
21142138

2115-
300 roots found
2139+
304 roots found
21162140

21172141
Propagate: +Hooks.+default -> +Hooks.+make
21182142
Propagate: DeadRT.moduleAccessPath.Root -> +DeadRT.moduleAccessPath.Root
@@ -2130,6 +2154,7 @@ Forward Liveness Analysis
21302154
Propagate: +DeadTest.WithInclude.t.A -> +DeadTest.WithInclude.t.A
21312155
Propagate: ErrorHandler.Make.+notify -> +ErrorHandler.Make.+notify
21322156
Propagate: +References.+make -> +References.R.+make
2157+
Propagate: +ScopedAnnotationsLiveVsDead.LiveScope.+root -> +ScopedAnnotationsLiveVsDead.+middleLive
21332158
Propagate: +Newton.+result -> +Newton.+newton
21342159
Propagate: +Newton.+result -> +Newton.+fPrimed
21352160
Propagate: +Records.+findAllAddresses -> +Records.+getOpt
@@ -2148,6 +2173,7 @@ Forward Liveness Analysis
21482173
Propagate: DeadValueTest.+valueAlive -> +DeadValueTest.+valueAlive
21492174
Propagate: +References.R.+get -> +References.R.+get
21502175
Propagate: +References.R.+make -> +References.R.+make
2176+
Propagate: +ScopedAnnotationsLiveVsDead.+middleLive -> +ScopedAnnotationsLiveVsDead.+leafLive
21512177
Propagate: +Newton.+newton -> +Newton.+/
21522178
Propagate: +Newton.+newton -> +Newton.+current
21532179
Propagate: +Newton.+newton -> +Newton.+iterateMore
@@ -2160,7 +2186,7 @@ Forward Liveness Analysis
21602186
Propagate: +DeadTest.MM.+x -> +DeadTest.MM.+y
21612187
Propagate: +ImportJsValue.AbsoluteValue.+getAbs -> +ImportJsValue.AbsoluteValue.+getAbs
21622188

2163-
45 declarations marked live via propagation
2189+
47 declarations marked live via propagation
21642190

21652191
Dead VariantCase +AutoAnnotate.variant.R
21662192
Dead RecordLabel +AutoAnnotate.record.variant
@@ -3386,6 +3412,34 @@ Forward Liveness Analysis
33863412
deps: in=0 (live=0 dead=0) out=2
33873413
-> +RepeatedLabel.tabState.a
33883414
-> +RepeatedLabel.tabState.b
3415+
Live (propagated) Value +ScopedAnnotationsLiveVsDead.+leafLive
3416+
deps: in=1 (live=1 dead=0) out=0
3417+
<- +ScopedAnnotationsLiveVsDead.+middleLive (live)
3418+
Live (propagated) Value +ScopedAnnotationsLiveVsDead.+middleLive
3419+
deps: in=1 (live=1 dead=0) out=1
3420+
<- +ScopedAnnotationsLiveVsDead.LiveScope.+root (live)
3421+
-> +ScopedAnnotationsLiveVsDead.+leafLive
3422+
Live (annotated) Value +ScopedAnnotationsLiveVsDead.LiveScope.+root
3423+
deps: in=0 (live=0 dead=0) out=1
3424+
-> +ScopedAnnotationsLiveVsDead.+middleLive
3425+
Dead Value +ScopedAnnotationsLiveVsDead.+stillDeadOutside
3426+
Dead Value +ScopedAnnotationsLiveVsDead.+leafDead
3427+
deps: in=1 (live=0 dead=1) out=0
3428+
<- +ScopedAnnotationsLiveVsDead.+middleDead (dead)
3429+
Dead Value +ScopedAnnotationsLiveVsDead.+middleDead
3430+
deps: in=1 (live=0 dead=1) out=1
3431+
<- +ScopedAnnotationsLiveVsDead.DeadScope.+root (dead)
3432+
-> +ScopedAnnotationsLiveVsDead.+leafDead
3433+
Dead Value +ScopedAnnotationsLiveVsDead.DeadScope.+root
3434+
deps: in=0 (live=0 dead=0) out=1
3435+
-> +ScopedAnnotationsLiveVsDead.+middleDead
3436+
Dead Value +ScopedAnnotationsOverride.M.+before
3437+
Live (annotated) Value +ScopedAnnotationsOverride.M.+live1
3438+
Live (annotated) Value +ScopedAnnotationsOverride.M.NestedInLive.+nestedLive
3439+
Dead Value +ScopedAnnotationsOverride.M.+dead1
3440+
Dead Value +ScopedAnnotationsOverride.M.NestedInDead.+nestedDead
3441+
Live (annotated) Value +ScopedAnnotationsOverride.M.+live2
3442+
Dead Value +ScopedAnnotationsOverride.+afterModules
33893443
Live (annotated) Value +Shadow.+test
33903444
Live (annotated) Value +Shadow.+test
33913445
Live (annotated) Value +Shadow.M.+test
@@ -4595,6 +4649,30 @@ Forward Liveness Analysis
45954649
RepeatedLabel.res:9:3-11
45964650
tabState.f is a record label never used to read a value
45974651

4652+
Warning Dead Value
4653+
ScopedAnnotationsLiveVsDead.res:9:1-24
4654+
stillDeadOutside is never used
4655+
4656+
Warning Dead Value
4657+
ScopedAnnotationsLiveVsDead.res:11:1-16
4658+
leafDead is never used
4659+
4660+
Warning Dead Value
4661+
ScopedAnnotationsLiveVsDead.res:12:1-25
4662+
middleDead is never used
4663+
4664+
Warning Dead Value
4665+
ScopedAnnotationsOverride.res:2:3-16
4666+
M.before is never used
4667+
4668+
Warning Dead Module
4669+
ScopedAnnotationsOverride.res:0:1
4670+
ScopedAnnotationsOverride is a dead module as all its items are dead.
4671+
4672+
Warning Dead Value
4673+
ScopedAnnotationsOverride.res:22:1-20
4674+
afterModules is never used
4675+
45984676
Warning Dead Value
45994677
Shadow.res:11:3-22
46004678
M.test is never used
@@ -4847,4 +4925,4 @@ Forward Liveness Analysis
48474925
OptArg.res:26:1-70
48484926
optional argument c of function wrapfourArgs is always supplied (2 calls)
48494927

4850-
Analysis reported 305 issues (Incorrect Dead Annotation:1, Warning Dead Exception:2, Warning Dead Module:21, Warning Dead Type:87, Warning Dead Value:174, Warning Dead Value With Side Effects:2, Warning Redundant Optional Argument:6, Warning Unused Argument:12)
4928+
Analysis reported 311 issues (Incorrect Dead Annotation:1, Warning Dead Exception:2, Warning Dead Module:22, Warning Dead Type:87, Warning Dead Value:179, Warning Dead Value With Side Effects:2, Warning Redundant Optional Argument:6, Warning Unused Argument:12)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
let leafLive = 1 /* Used only by middleLive; becomes live only via propagation from a live root. */
2+
let middleLive = leafLive /* Used only by LiveScope.root; should be kept alive by @@live. */
3+
4+
module LiveScope = {
5+
@@live /* From here on (inside LiveScope): treat items as if annotated @live. */
6+
let root = middleLive /* Live root: keeps middleLive -> leafLive alive via liveness propagation. */
7+
}
8+
9+
let stillDeadOutside = 2 /* Outside any @@live scope and unused: should be reported as dead. */
10+
11+
let leafDead = 3 /* Only referenced from middleDead; should still be reported (dead). */
12+
let middleDead = leafDead /* Only referenced from DeadScope.root; @@dead does not keep it alive. */
13+
14+
module DeadScope = {
15+
@@dead /* From here on (inside DeadScope): treat items as if annotated @dead (suppressed). */
16+
let root = middleDead /* Suppressed, but NOT a liveness root: middleDead/leafDead remain dead and are reported. */
17+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
module M = {
2+
let before = 1 /* Before any @@ annotation in M: should be reported as dead (unused). */
3+
4+
@@live /* Set default to live roots for the rest of M (until overridden). */
5+
let live1 = 1 /* Live root (suppressed) and can keep deps alive, if it referenced anything. */
6+
7+
module NestedInLive = {
8+
let nestedLive = 1 /* Inherits @@live from M: treated as a live root (suppressed). */
9+
}
10+
11+
@@dead /* Override default to dead (suppressed) for the rest of M (until overridden). */
12+
let dead1 = 1 /* Suppressed, but NOT a liveness root: would not keep dependencies alive. */
13+
14+
module NestedInDead = {
15+
let nestedDead = 1 /* Inherits @@dead from M: suppressed. */
16+
}
17+
18+
@@live /* Override again: back to live roots for the remainder of M. */
19+
let live2 = 1 /* Live root (suppressed). */
20+
}
21+
22+
let afterModules = 1 /* Outside M: scope does not leak, so this should be reported as dead (unused). */

0 commit comments

Comments
 (0)