@@ -1921,3 +1921,95 @@ private module OutNodes {
19211921 * `kind`.
19221922 */
19231923OutNode getAnOutNode ( DataFlowCall call , ReturnKind kind ) { call = result .getCall ( kind ) }
1924+
1925+ /**
1926+ * Provides predicates for approximating type properties of user-defined classes
1927+ * based on their structure (method declarations, base classes).
1928+ *
1929+ * This module should _not_ be used in the call graph computation itself, as parts of it may depend
1930+ * on layers that themselves build upon the call graph (e.g. API graphs).
1931+ */
1932+ module DuckTyping {
1933+ private import semmle.python.ApiGraphs
1934+
1935+ /**
1936+ * Holds if `cls` or any of its resolved superclasses declares a method with the given `name`.
1937+ */
1938+ predicate hasMethod ( Class cls , string name ) {
1939+ cls .getAMethod ( ) .getName ( ) = name
1940+ or
1941+ hasMethod ( getADirectSuperclass ( cls ) , name )
1942+ }
1943+
1944+ /**
1945+ * Holds if `cls` has a base class that cannot be resolved to a user-defined class
1946+ * and is not just `object`, meaning it may inherit methods from an unknown class.
1947+ */
1948+ predicate hasUnresolvedBase ( Class cls ) {
1949+ exists ( Expr base | base = cls .getABase ( ) |
1950+ not base = classTracker ( _) .asExpr ( ) and
1951+ not base = API:: builtin ( "object" ) .getAValueReachableFromSource ( ) .asExpr ( )
1952+ )
1953+ }
1954+
1955+ /**
1956+ * Holds if `cls` supports the container protocol, i.e. it declares
1957+ * `__contains__`, `__iter__`, or `__getitem__`.
1958+ */
1959+ predicate isContainer ( Class cls ) {
1960+ hasMethod ( cls , "__contains__" ) or
1961+ hasMethod ( cls , "__iter__" ) or
1962+ hasMethod ( cls , "__getitem__" )
1963+ }
1964+
1965+ /**
1966+ * Holds if `cls` supports the iterable protocol, i.e. it declares
1967+ * `__iter__` or `__getitem__`.
1968+ */
1969+ predicate isIterable ( Class cls ) {
1970+ hasMethod ( cls , "__iter__" ) or
1971+ hasMethod ( cls , "__getitem__" )
1972+ }
1973+
1974+ /**
1975+ * Holds if `cls` supports the iterator protocol, i.e. it declares
1976+ * both `__iter__` and `__next__`.
1977+ */
1978+ predicate isIterator ( Class cls ) {
1979+ hasMethod ( cls , "__iter__" ) and
1980+ hasMethod ( cls , "__next__" )
1981+ }
1982+
1983+ /**
1984+ * Holds if `cls` supports the context manager protocol, i.e. it declares
1985+ * both `__enter__` and `__exit__`.
1986+ */
1987+ predicate isContextManager ( Class cls ) {
1988+ hasMethod ( cls , "__enter__" ) and
1989+ hasMethod ( cls , "__exit__" )
1990+ }
1991+
1992+ /**
1993+ * Holds if `cls` supports the descriptor protocol, i.e. it declares
1994+ * `__get__`, `__set__`, or `__delete__`.
1995+ */
1996+ predicate isDescriptor ( Class cls ) {
1997+ hasMethod ( cls , "__get__" ) or
1998+ hasMethod ( cls , "__set__" ) or
1999+ hasMethod ( cls , "__delete__" )
2000+ }
2001+
2002+ /**
2003+ * Holds if `cls` is callable, i.e. it declares `__call__`.
2004+ */
2005+ predicate isCallable ( Class cls ) { hasMethod ( cls , "__call__" ) }
2006+
2007+ /**
2008+ * Holds if `cls` supports the mapping protocol, i.e. it declares
2009+ * `__getitem__` and `__keys__`, or `__getitem__` and `__iter__`.
2010+ */
2011+ predicate isMapping ( Class cls ) {
2012+ hasMethod ( cls , "__getitem__" ) and
2013+ ( hasMethod ( cls , "keys" ) or hasMethod ( cls , "__iter__" ) )
2014+ }
2015+ }
0 commit comments