@@ -182,30 +182,32 @@ const concreteTypeDebug = false
182
182
// Returns nil when the concrete type could not be determined, or when there are multiple
183
183
// (different) types assigned to an interface.
184
184
func concreteType (s * State , n ir.Node ) (typ * types.Type ) {
185
- typ , isNil := concreteType1 (s , n , make (map [* ir.Name ]struct {}))
186
- if isNil && typ != nil {
187
- base .Fatalf ("typ = %v; want = <nil>" , typ )
188
- }
185
+ typ = concreteType1 (s , n , make (map [* ir.Name ]struct {}))
189
186
if typ != nil && typ .IsInterface () {
190
187
base .Fatalf ("typ.IsInterface() = true; want = false; typ = %v" , typ )
191
188
}
189
+ if typ == & noType {
190
+ return nil
191
+ }
192
192
return typ
193
193
}
194
194
195
+ // noType is a sentinel value returned by [concreteType1].
196
+ var noType types.Type
197
+
195
198
// concreteType1 analyzes the node n and returns its concrete type if it is statically known.
196
199
// Otherwise, it returns a nil Type, indicating that a concrete type was not determined.
197
- // This can happen in cases where n is assigned an interface type and the concrete type of that
198
- // interface is not statically known (e.g. a non-inlined function call returning an interface type)
199
- // or when multiple distinct concrete types are assigned.
200
- //
201
- // If n is statically known to be nil, this function returns a nil Type with isNil == true.
202
- // However, if any concrete type is found, it is returned instead, even if n was assigned with nil.
203
- func concreteType1 (s * State , n ir.Node , seen map [* ir.Name ]struct {}) (t * types.Type , isNil bool ) {
200
+ // When n is known to be statically nil or a self-assignment is detected, in returns a sentinel [noType] type instead.
201
+ func concreteType1 (s * State , n ir.Node , seen map [* ir.Name ]struct {}) (outT * types.Type ) {
204
202
nn := n // for debug messages
205
203
206
204
if concreteTypeDebug {
207
205
defer func () {
208
- base .Warn ("concreteType1(%v) -> (%v;%v)" , nn , t , isNil )
206
+ t := "&noType"
207
+ if outT != & noType {
208
+ t = outT .String ()
209
+ }
210
+ base .Warn ("concreteType1(%v) -> %v" , nn , t )
209
211
}()
210
212
}
211
213
@@ -215,7 +217,7 @@ func concreteType1(s *State, n ir.Node, seen map[*ir.Name]struct{}) (t *types.Ty
215
217
}
216
218
217
219
if ! n .Type ().IsInterface () {
218
- return n .Type (), false
220
+ return n .Type ()
219
221
}
220
222
221
223
switch n1 := n .(type ) {
@@ -249,12 +251,12 @@ func concreteType1(s *State, n ir.Node, seen map[*ir.Name]struct{}) (t *types.Ty
249
251
}
250
252
251
253
if n .Op () != ir .ONAME {
252
- return nil , false
254
+ return nil
253
255
}
254
256
255
257
name := n .(* ir.Name ).Canonical ()
256
258
if name .Class != ir .PAUTO {
257
- return nil , false
259
+ return nil
258
260
}
259
261
260
262
if name .Op () != ir .ONAME {
@@ -267,14 +269,14 @@ func concreteType1(s *State, n ir.Node, seen map[*ir.Name]struct{}) (t *types.Ty
267
269
}
268
270
269
271
if name .Addrtaken () {
270
- return nil , false // conservatively assume it's reassigned with a different type indirectly
272
+ return nil // conservatively assume it's reassigned with a different type indirectly
271
273
}
272
274
273
275
if _ , ok := seen [name ]; ok {
274
276
// Self assignment, treat it the same as a nil assignment.
275
277
// In case this is the only assignment then we are not going to devirtualize anything.
276
278
// In case there are other assignment, we still preserve the correct type.
277
- return nil , true
279
+ return & noType
278
280
}
279
281
seen [name ] = struct {}{}
280
282
@@ -284,19 +286,18 @@ func concreteType1(s *State, n ir.Node, seen map[*ir.Name]struct{}) (t *types.Ty
284
286
285
287
var typ * types.Type
286
288
for _ , v := range s .assignments (name ) {
287
- t := v .typ
288
- if v .node != nil {
289
- var isNil bool
290
- t , isNil = concreteType1 (s , v .node , seen )
291
- if isNil {
292
- if t != nil {
293
- base .Fatalf ("t = %v; want = <nil>" , t )
294
- }
289
+ var t * types.Type
290
+ switch v := v .(type ) {
291
+ case * types.Type :
292
+ t = v
293
+ case ir.Node :
294
+ t = concreteType1 (s , v , seen )
295
+ if t == & noType {
295
296
continue
296
297
}
297
298
}
298
299
if t == nil || (typ != nil && ! types .Identical (typ , t )) {
299
- return nil , false
300
+ return nil
300
301
}
301
302
typ = t
302
303
}
@@ -305,27 +306,30 @@ func concreteType1(s *State, n ir.Node, seen map[*ir.Name]struct{}) (t *types.Ty
305
306
306
307
if typ == nil {
307
308
// Variable either declared with zero value, or only assigned with nil.
308
- return nil , true
309
+ return & noType
309
310
}
310
311
311
- return typ , false
312
+ return typ
312
313
}
313
314
314
- // valOrTyp stores either node or a type that is assigned to a variable.
315
- // Never both of these fields are populated.
316
- // If both are nil, then either an interface type was assigned (e.g. a non-inlined
317
- // function call returning an interface type, in such case we don't know the
318
- // concrete type) or a basic type (i.e. int), which we know that does not have any
319
- // methods, thus not possible to devirtualize.
320
- type valOrTyp struct {
321
- typ * types.Type
322
- node ir.Node
323
- }
315
+ // assignment can be one of:
316
+ // - nil - assignment to an interface type.
317
+ // - *types.Type - assignment to a concrete type (non-interface).
318
+ // - ir.Node - assignment to a ir.Node.
319
+ //
320
+ // In most cases assignment should be an [ir.Node], but in cases where we
321
+ // do not follow the data-flow, we return either a concrete type (*types.Type) or a nil.
322
+ // For example in range over a slice, if the slice elem is of an interface type, then we return
323
+ // a nil, otherwise the elem's concrete type (We do so because we do not analyze assignment to the
324
+ // slice being ranged-over).
325
+ type assignment any
324
326
325
327
// State holds precomputed state for use in [StaticCall].
326
328
type State struct {
327
- // ifaceAssignments stores all assignments to all interface variables.
328
- ifaceAssignments map [* ir.Name ][]valOrTyp
329
+ // ifaceAssignments maps interface variables to all their assignments
330
+ // defined inside functions stored in the analyzedFuncs set.
331
+ // Note: it does not include direct assignments to nil.
332
+ ifaceAssignments map [* ir.Name ][]assignment
329
333
330
334
// ifaceCallExprAssigns stores every [*ir.CallExpr], which has an interface
331
335
// result, that is assigned to a variable.
@@ -362,8 +366,8 @@ func (s *State) InlinedCall(fun *ir.Func, origCall *ir.CallExpr, inlinedCall *ir
362
366
// Update assignments to reference the new ReturnVars of the inlined call.
363
367
for _ , ref := range refs {
364
368
vt := & s.ifaceAssignments [ref.name ][ref.valOrTypeIndex ]
365
- if vt . node != nil || vt . typ != nil {
366
- base .Fatalf ("unexpected non-empty valOrTyp " )
369
+ if * vt != nil {
370
+ base .Fatalf ("unexpected non-nil assignment " )
367
371
}
368
372
if concreteTypeDebug {
369
373
base .Warn (
@@ -373,12 +377,12 @@ func (s *State) InlinedCall(fun *ir.Func, origCall *ir.CallExpr, inlinedCall *ir
373
377
inlinedCall .ReturnVars [ref .returnIndex ].Type (),
374
378
)
375
379
}
376
- * vt = valOrTyp { node : inlinedCall .ReturnVars [ref .returnIndex ]}
380
+ * vt = inlinedCall .ReturnVars [ref .returnIndex ]
377
381
}
378
382
}
379
383
380
384
// assignments returns all assignments to n.
381
- func (s * State ) assignments (n * ir.Name ) []valOrTyp {
385
+ func (s * State ) assignments (n * ir.Name ) []assignment {
382
386
fun := n .Curfn
383
387
if fun == nil {
384
388
base .Fatalf ("n.Curfn = <nil>" )
@@ -394,7 +398,7 @@ func (s *State) assignments(n *ir.Name) []valOrTyp {
394
398
base .Warn ("concreteType(): analyzing assignments in %v func" , fun )
395
399
}
396
400
if s .analyzedFuncs == nil {
397
- s .ifaceAssignments = make (map [* ir.Name ][]valOrTyp )
401
+ s .ifaceAssignments = make (map [* ir.Name ][]assignment )
398
402
s .ifaceCallExprAssigns = make (map [* ir.CallExpr ][]ifaceAssignRef )
399
403
s .analyzedFuncs = make (map [* ir.Func ]struct {})
400
404
}
@@ -408,7 +412,7 @@ func (s *State) assignments(n *ir.Name) []valOrTyp {
408
412
409
413
// analyze analyzes every assignment to interface variables in nodes, updating [State].
410
414
func (s * State ) analyze (nodes ir.Nodes ) {
411
- assign := func (name ir.Node , value valOrTyp ) (* ir.Name , int ) {
415
+ assign := func (name ir.Node , assignment assignment ) (* ir.Name , int ) {
412
416
if name == nil || name .Op () != ir .ONAME || ir .IsBlank (name ) {
413
417
return nil , - 1
414
418
}
@@ -429,20 +433,26 @@ func (s *State) analyze(nodes ir.Nodes) {
429
433
base .Fatalf ("reassigned %v" , n )
430
434
}
431
435
432
- // n is assigned with nil, we can safely ignore them, see [StaticCall].
433
- if ir .IsNil (value .node ) {
434
- return nil , - 1
435
- }
436
-
437
- if value .typ != nil && value .typ .IsInterface () {
438
- value .typ = nil
436
+ switch a := assignment .(type ) {
437
+ case nil :
438
+ case * types.Type :
439
+ if a != nil && a .IsInterface () {
440
+ assignment = nil // non-concrete type
441
+ }
442
+ case ir.Node :
443
+ // nil assignment, we can safely ignore them, see [StaticCall].
444
+ if ir .IsNil (a ) {
445
+ return nil , - 1
446
+ }
447
+ default :
448
+ base .Fatalf ("unexpected type: %v" , assignment )
439
449
}
440
450
441
451
if concreteTypeDebug {
442
- base .Warn ("analyze(): assignment found %v = (%v;%v) " , name , value . typ , value . node )
452
+ base .Warn ("analyze(): assignment found %v = %v " , name , assignment )
443
453
}
444
454
445
- s .ifaceAssignments [n ] = append (s .ifaceAssignments [n ], value )
455
+ s .ifaceAssignments [n ] = append (s .ifaceAssignments [n ], assignment )
446
456
return n , len (s .ifaceAssignments [n ]) - 1
447
457
}
448
458
@@ -462,35 +472,35 @@ func (s *State) analyze(nodes ir.Nodes) {
462
472
}
463
473
if call , ok := rhs .(* ir.CallExpr ); ok && call .Fun != nil {
464
474
retTyp := call .Fun .Type ().Results ()[0 ].Type
465
- n , idx := assign (n .X , valOrTyp { typ : retTyp } )
475
+ n , idx := assign (n .X , retTyp )
466
476
if n != nil && retTyp .IsInterface () {
467
477
s .ifaceCallExprAssigns [call ] = append (s .ifaceCallExprAssigns [call ], ifaceAssignRef {n , idx , 0 })
468
478
}
469
479
} else {
470
- assign (n .X , valOrTyp { node : rhs } )
480
+ assign (n .X , rhs )
471
481
}
472
482
}
473
483
case ir .OAS2 :
474
484
n := n .(* ir.AssignListStmt )
475
485
for i , p := range n .Lhs {
476
486
if n .Rhs [i ] != nil {
477
- assign (p , valOrTyp { node : n .Rhs [i ]} )
487
+ assign (p , n .Rhs [i ])
478
488
}
479
489
}
480
490
case ir .OAS2DOTTYPE :
481
491
n := n .(* ir.AssignListStmt )
482
492
if n .Rhs [0 ] == nil {
483
493
base .Fatalf ("n.Rhs[0] == nil; n = %v" , n )
484
494
}
485
- assign (n .Lhs [0 ], valOrTyp { node : n .Rhs [0 ]} )
486
- assign (n .Lhs [1 ], valOrTyp {} ) // boolean does not have methods to devirtualize
495
+ assign (n .Lhs [0 ], n .Rhs [0 ])
496
+ assign (n .Lhs [1 ], nil ) // boolean does not have methods to devirtualize
487
497
case ir .OAS2MAPR , ir .OAS2RECV , ir .OSELRECV2 :
488
498
n := n .(* ir.AssignListStmt )
489
499
if n .Rhs [0 ] == nil {
490
500
base .Fatalf ("n.Rhs[0] == nil; n = %v" , n )
491
501
}
492
- assign (n .Lhs [0 ], valOrTyp { typ : n .Rhs [0 ].Type ()} )
493
- assign (n .Lhs [1 ], valOrTyp {} ) // boolean does not have methods to devirtualize
502
+ assign (n .Lhs [0 ], n .Rhs [0 ].Type ())
503
+ assign (n .Lhs [1 ], nil ) // boolean does not have methods to devirtualize
494
504
case ir .OAS2FUNC :
495
505
n := n .(* ir.AssignListStmt )
496
506
rhs := n .Rhs [0 ]
@@ -504,19 +514,19 @@ func (s *State) analyze(nodes ir.Nodes) {
504
514
if call , ok := rhs .(* ir.CallExpr ); ok {
505
515
for i , p := range n .Lhs {
506
516
retTyp := call .Fun .Type ().Results ()[i ].Type
507
- n , idx := assign (p , valOrTyp { typ : retTyp } )
517
+ n , idx := assign (p , retTyp )
508
518
if n != nil && retTyp .IsInterface () {
509
519
s .ifaceCallExprAssigns [call ] = append (s .ifaceCallExprAssigns [call ], ifaceAssignRef {n , idx , i })
510
520
}
511
521
}
512
522
} else if call , ok := rhs .(* ir.InlinedCallExpr ); ok {
513
523
for i , p := range n .Lhs {
514
- assign (p , valOrTyp { node : call .ReturnVars [i ]} )
524
+ assign (p , call .ReturnVars [i ])
515
525
}
516
526
} else {
517
527
// TODO: can we reach here?
518
528
for _ , p := range n .Lhs {
519
- assign (p , valOrTyp {} )
529
+ assign (p , nil )
520
530
}
521
531
}
522
532
case ir .ORANGE :
@@ -529,18 +539,18 @@ func (s *State) analyze(nodes ir.Nodes) {
529
539
}
530
540
531
541
if xTyp .IsArray () || xTyp .IsSlice () {
532
- assign (n .Key , valOrTyp {} ) // boolean
533
- assign (n .Value , valOrTyp { typ : xTyp .Elem ()} )
542
+ assign (n .Key , nil ) // inteager does not have methods to devirtualize
543
+ assign (n .Value , xTyp .Elem ())
534
544
} else if xTyp .IsChan () {
535
- assign (n .Key , valOrTyp { typ : xTyp .Elem ()} )
545
+ assign (n .Key , xTyp .Elem ())
536
546
base .Assertf (n .Value == nil , "n.Value != nil in range over chan" )
537
547
} else if xTyp .IsMap () {
538
- assign (n .Key , valOrTyp { typ : xTyp .Key ()} )
539
- assign (n .Value , valOrTyp { typ : xTyp .Elem ()} )
548
+ assign (n .Key , xTyp .Key ())
549
+ assign (n .Value , xTyp .Elem ())
540
550
} else if xTyp .IsInteger () || xTyp .IsString () {
541
551
// Range over int/string, results do not have methods, so nothing to devirtualize.
542
- assign (n .Key , valOrTyp {} )
543
- assign (n .Value , valOrTyp {} )
552
+ assign (n .Key , nil )
553
+ assign (n .Value , nil )
544
554
} else {
545
555
// We will not reach here in case of an range-over-func, as it is
546
556
// rewrtten to function calls in the noder package.
@@ -554,7 +564,7 @@ func (s *State) analyze(nodes ir.Nodes) {
554
564
base .Assert (guard .Tag == nil )
555
565
continue
556
566
}
557
- assign (v .Var , valOrTyp { node : guard .X } )
567
+ assign (v .Var , guard .X )
558
568
}
559
569
}
560
570
case ir .OCLOSURE :
0 commit comments