@@ -762,6 +762,7 @@ def visit_Call(self, node: ast.Call) -> None:
762762 self .check_for_b028 (node )
763763 self .check_for_b034 (node )
764764 self .check_for_b039 (node )
765+ self .check_for_b046 (node )
765766 self .check_for_b905 (node )
766767 self .check_for_b910 (node )
767768 self .check_for_b911 (node )
@@ -2615,6 +2616,31 @@ def check_for_b044(self, node: ast.Assert) -> None:
26152616 if isinstance (node .test , ast .GeneratorExp ):
26162617 self .add_error ("B044" , node )
26172618
2619+ def check_for_b046 (self , node : ast .Call ) -> None :
2620+ if not (
2621+ isinstance (node .func , ast .Attribute )
2622+ and node .func .attr == "fromkeys"
2623+ and "." .join (compose_call_path (node .func .value )) in B046_DICT_TYPES
2624+ ):
2625+ return
2626+
2627+ if len (node .args ) == 2 :
2628+ value = node .args [1 ]
2629+ elif len (node .args ) == 1 :
2630+ # OrderedDict.fromkeys() also takes the value as a keyword argument
2631+ value = next ((kw .value for kw in node .keywords if kw .arg == "value" ), None )
2632+ else :
2633+ return
2634+
2635+ if isinstance (
2636+ value ,
2637+ (ast .List , ast .Dict , ast .Set , ast .ListComp , ast .DictComp , ast .SetComp ),
2638+ ) or (
2639+ isinstance (value , ast .Call )
2640+ and "." .join (compose_call_path (value .func )) in B006_MUTABLE_CALLS
2641+ ):
2642+ self .add_error ("B046" , value )
2643+
26182644 def check_for_b909 (self , node : ast .For ) -> None :
26192645 if isinstance (node .iter , ast .Name ):
26202646 name = _to_name_str (node .iter )
@@ -3127,6 +3153,11 @@ def visit_Lambda(self, node: ast.Lambda) -> None:
31273153 "async_lru.alru_cache" ,
31283154 "alru_cache" ,
31293155}
3156+ B046_DICT_TYPES = {
3157+ "dict" ,
3158+ "OrderedDict" ,
3159+ "collections.OrderedDict" ,
3160+ }
31303161B902_IMPLICIT_CLASSMETHODS = {"__new__" , "__init_subclass__" , "__class_getitem__" }
31313162B902_SELF = ["self" ] # it's a list because the first is preferred
31323163B902_CLS = ["cls" , "klass" ] # ditto.
@@ -3419,6 +3450,13 @@ def __call__(self, lineno: int, col: int, vars: tuple[object, ...] = ()) -> erro
34193450 "Use a different name for the inner loop variable."
34203451 )
34213452 ),
3453+ "B046" : Error (
3454+ message = (
3455+ "B046 Mutable value passed to `dict.fromkeys()`. The same object is "
3456+ "shared by every key, so changing it for one key changes it for all of "
3457+ "them. Use a dict comprehension instead, e.g. `{{key: [] for key in keys}}`."
3458+ )
3459+ ),
34223460 # Warnings disabled by default.
34233461 "B901" : Error (
34243462 message = (
0 commit comments