Skip to content

Commit 0d4b693

Browse files
committed
SA4023, analysis/facts/typedness: with generics, MakeInterface can return untyped nil
Closes gh-1242 (cherry picked from commit e88e689)
1 parent 5322218 commit 0d4b693

File tree

4 files changed

+53
-2
lines changed

4 files changed

+53
-2
lines changed

analysis/facts/typedness/typedness.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,15 @@ func impl(pass *analysis.Pass, fn *ir.Function, seenFns map[*ir.Function]struct{
212212
}
213213
return true
214214
case *ir.MakeInterface:
215+
terms, err := typeparams.NormalTerms(v.X.Type())
216+
if len(terms) == 0 || err != nil {
217+
// Type is a type parameter with no type terms (or we couldn't determine the terms). Such a type
218+
// _can_ be nil when put in an interface value.
219+
//
220+
// There is no instruction that can create a guaranteed non-nil instance of a type parameter without
221+
// type constraints, so we return false right away, without checking v.X's typedness.
222+
return false
223+
}
215224
return true
216225
case *ir.TypeAssert:
217226
// type assertions fail for untyped nils. Either we have a

staticcheck/lint.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4592,6 +4592,14 @@ func CheckTypedNilInterface(pass *analysis.Pass) (interface{}, error) {
45924592
default:
45934593
panic("unreachable")
45944594
}
4595+
4596+
terms, err := typeparams.NormalTerms(x.X.Type())
4597+
if len(terms) == 0 || err != nil {
4598+
// Type is a type parameter with no type terms (or we couldn't determine the terms). Such a type
4599+
// _can_ be nil when put in an interface value.
4600+
continue
4601+
}
4602+
45954603
if report.HasRange(x.X) {
45964604
report.Report(pass, binop, fmt.Sprintf("this comparison is %s true", qualifier),
45974605
report.Related(x.X, "the lhs of the comparison gets its value from here and has a concrete type"))

staticcheck/testdata/src/CheckTypedNilInterface/CheckTypedNilInterface.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,4 +207,7 @@ func test() {
207207

208208
var v1 interface{} = 0
209209
_ = v1 == nil // want `never true; the lhs`
210+
211+
_ = any((*int)(nil)) == nil // want `never true`
212+
_ = any((error)(nil)) == nil
210213
}

staticcheck/testdata/src/CheckTypedNilInterface/generics.go

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,42 @@
22

33
package pkg
44

5-
func foo[T *int,]() T {
5+
func tpgen1[T *int,]() T {
66
return (T)(nil)
77
}
88

99
func bar() {
10-
if foo() == nil {
10+
if tpgen1() == nil {
1111
}
1212
}
13+
14+
func tpfn1[T any](x T) {
15+
if any(x) == nil {
16+
// this is entirely possible if baz is instantiated with an interface type for T. For example: baz[error](nil)
17+
}
18+
}
19+
20+
func tpfn2[T ~int](x T) {
21+
if any(x) == nil { // want `this comparison is never true`
22+
// this is not possible, because T only accepts concrete types
23+
}
24+
}
25+
26+
func tpgen3[T any](x T) any {
27+
return any(x)
28+
}
29+
30+
func tpgen4[T ~*int](x T) any {
31+
return any(x)
32+
}
33+
34+
func tptest() {
35+
_ = tpgen1() == nil
36+
37+
_ = tpgen3[error](nil) == nil
38+
39+
// ideally we'd flag this, but the analysis is generic-insensitive at the moment.
40+
_ = tpgen3[*int](nil) == nil
41+
42+
_ = tpgen4[*int](nil) == nil // want `never true`
43+
}

0 commit comments

Comments
 (0)