@@ -1956,3 +1956,95 @@ private module OutNodes {
19561956 * `kind`.
19571957 */
19581958OutNode getAnOutNode ( DataFlowCall call , ReturnKind kind ) { call = result .getCall ( kind ) }
1959+
1960+ /**
1961+ * Provides predicates for approximating type properties of user-defined classes
1962+ * based on their structure (method declarations, base classes).
1963+ *
1964+ * This module should _not_ be used in the call graph computation itself, as parts of it may depend
1965+ * on layers that themselves build upon the call graph (e.g. API graphs).
1966+ */
1967+ module DuckTyping {
1968+ private import semmle.python.ApiGraphs
1969+
1970+ /**
1971+ * Holds if `cls` or any of its resolved superclasses declares a method with the given `name`.
1972+ */
1973+ predicate hasMethod ( Class cls , string name ) {
1974+ cls .getAMethod ( ) .getName ( ) = name
1975+ or
1976+ hasMethod ( getADirectSuperclass ( cls ) , name )
1977+ }
1978+
1979+ /**
1980+ * Holds if `cls` has a base class that cannot be resolved to a user-defined class
1981+ * and is not just `object`, meaning it may inherit methods from an unknown class.
1982+ */
1983+ predicate hasUnresolvedBase ( Class cls ) {
1984+ exists ( Expr base | base = cls .getABase ( ) |
1985+ not base = classTracker ( _) .asExpr ( ) and
1986+ not base = API:: builtin ( "object" ) .getAValueReachableFromSource ( ) .asExpr ( )
1987+ )
1988+ }
1989+
1990+ /**
1991+ * Holds if `cls` supports the container protocol, i.e. it declares
1992+ * `__contains__`, `__iter__`, or `__getitem__`.
1993+ */
1994+ predicate isContainer ( Class cls ) {
1995+ hasMethod ( cls , "__contains__" ) or
1996+ hasMethod ( cls , "__iter__" ) or
1997+ hasMethod ( cls , "__getitem__" )
1998+ }
1999+
2000+ /**
2001+ * Holds if `cls` supports the iterable protocol, i.e. it declares
2002+ * `__iter__` or `__getitem__`.
2003+ */
2004+ predicate isIterable ( Class cls ) {
2005+ hasMethod ( cls , "__iter__" ) or
2006+ hasMethod ( cls , "__getitem__" )
2007+ }
2008+
2009+ /**
2010+ * Holds if `cls` supports the iterator protocol, i.e. it declares
2011+ * both `__iter__` and `__next__`.
2012+ */
2013+ predicate isIterator ( Class cls ) {
2014+ hasMethod ( cls , "__iter__" ) and
2015+ hasMethod ( cls , "__next__" )
2016+ }
2017+
2018+ /**
2019+ * Holds if `cls` supports the context manager protocol, i.e. it declares
2020+ * both `__enter__` and `__exit__`.
2021+ */
2022+ predicate isContextManager ( Class cls ) {
2023+ hasMethod ( cls , "__enter__" ) and
2024+ hasMethod ( cls , "__exit__" )
2025+ }
2026+
2027+ /**
2028+ * Holds if `cls` supports the descriptor protocol, i.e. it declares
2029+ * `__get__`, `__set__`, or `__delete__`.
2030+ */
2031+ predicate isDescriptor ( Class cls ) {
2032+ hasMethod ( cls , "__get__" ) or
2033+ hasMethod ( cls , "__set__" ) or
2034+ hasMethod ( cls , "__delete__" )
2035+ }
2036+
2037+ /**
2038+ * Holds if `cls` is callable, i.e. it declares `__call__`.
2039+ */
2040+ predicate isCallable ( Class cls ) { hasMethod ( cls , "__call__" ) }
2041+
2042+ /**
2043+ * Holds if `cls` supports the mapping protocol, i.e. it declares
2044+ * `__getitem__` and `__keys__`, or `__getitem__` and `__iter__`.
2045+ */
2046+ predicate isMapping ( Class cls ) {
2047+ hasMethod ( cls , "__getitem__" ) and
2048+ ( hasMethod ( cls , "keys" ) or hasMethod ( cls , "__iter__" ) )
2049+ }
2050+ }
0 commit comments