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