@@ -1138,51 +1138,68 @@ func (b *blockWalker) checkListExprNullSafety(arg ir.Node, fn meta.FuncInfo, par
11381138 }
11391139}
11401140
1141+ func (b * blockWalker ) getPropertyComputedType (expr * ir.PropertyFetchExpr ) (meta.ClassInfo , types.Map ) {
1142+ baseCall , ok := expr .Variable .(* ir.SimpleVar )
1143+ if ! ok {
1144+ return meta.ClassInfo {}, types.Map {}
1145+ }
1146+
1147+ varInfo , ok := b .ctx .sc .GetVar (baseCall )
1148+ if ! ok {
1149+ return meta.ClassInfo {}, types.Map {}
1150+ }
1151+
1152+ classInfo , ok := b .r .ctx .st .Info .GetClass (varInfo .Type .String ())
1153+ if ! ok {
1154+ return meta.ClassInfo {}, types.Map {}
1155+ }
1156+
1157+ property , ok := expr .Property .(* ir.Identifier )
1158+ if ! ok {
1159+ return meta.ClassInfo {}, types.Map {}
1160+ }
1161+
1162+ propertyInfoFromClass := classInfo .Properties [property .Value ]
1163+ return classInfo , propertyInfoFromClass .Typ
1164+ }
1165+
11411166func (b * blockWalker ) checkPropertyFetchNullSafety (expr * ir.PropertyFetchExpr , fn meta.FuncInfo , paramIndex int , haveVariadic bool ) {
1142- objVar , ok := expr .Variable .(* ir.SimpleVar )
1167+ // If the left part of the chain is also a property call, we check it recursively
1168+ if nested , ok := expr .Variable .(* ir.PropertyFetchExpr ); ok {
1169+ b .checkPropertyFetchNullSafety (nested , fn , paramIndex , haveVariadic )
1170+ }
11431171
1144- if ok {
1145- varInfo , _ := b .ctx .sc .GetVar (objVar )
1146- classInfo , _ := b .r .ctx .st .Info .GetClass (varInfo .Type .String ())
1147-
1148- prp , okPrp := expr .Property .(* ir.Identifier )
1149-
1150- if okPrp {
1151- property := classInfo .Properties [prp .Value ]
1152-
1153- isPrpNullable := types .IsTypeNullable (property .Typ )
1154- if haveVariadic {
1155- // If the parameter is outside the declared parameters, we check the latter as a variable
1156- if paramIndex >= len (fn .Params )- 1 {
1157- lastParam := fn .Params [len (fn .Params )- 1 ] // last param (variadic ...args)
1158- if types .IsTypeMixed (lastParam .Typ ) {
1159- return
1160- }
1161-
1162- paramAllowsNull := types .IsTypeNullable (lastParam .Typ )
1163- if isPrpNullable && ! paramAllowsNull {
1164- b .report (expr , LevelError , "notNullSafety" ,
1165- "potential null dereference when accessing property '%s'" , prp .Value )
1166- }
1167- return
1172+ classInfo , propType := b .getPropertyComputedType (expr )
1173+ if classInfo .Name == "" || propType .Empty () {
1174+ return
1175+ }
11681176
1169- }
1170- }
1177+ prp , ok := expr .Property .(* ir.Identifier )
1178+ if ! ok {
1179+ return
1180+ }
11711181
1172- paramAllowsNull := types .IsTypeNullable (fn . Params [ paramIndex ]. Typ )
1182+ isPrpNullable := types .IsTypeNullable (propType )
11731183
1184+ if haveVariadic {
1185+ if paramIndex >= len (fn .Params )- 1 {
1186+ lastParam := fn .Params [len (fn .Params )- 1 ]
1187+ if types .IsTypeMixed (lastParam .Typ ) {
1188+ return
1189+ }
1190+ paramAllowsNull := types .IsTypeNullable (lastParam .Typ )
11741191 if isPrpNullable && ! paramAllowsNull {
11751192 b .report (expr , LevelError , "notNullSafety" ,
11761193 "potential null dereference when accessing property '%s'" , prp .Value )
11771194 }
1178-
1179- println (classInfo .Name )
1195+ return
11801196 }
1197+ }
11811198
1182- // TODO: check difficult chains like $a->b->c->d
1183- /* if nestedProp, ok := expr.Variable.(*ir.PropertyFetchExpr); ok {
1184- b.checkPropertyFetchNullSafety(nestedProp)
1185- }*/
1199+ paramAllowsNull := types . IsTypeNullable ( fn . Params [ paramIndex ]. Typ )
1200+ if isPrpNullable && ! paramAllowsNull {
1201+ b . report ( expr , LevelError , "notNullSafety" ,
1202+ "potential null dereference when accessing property '%s'" , prp . Value )
11861203 }
11871204}
11881205
0 commit comments