Python: Account for non-evaluation of annotations in cyclic imports.

Should fix #2426.

Essentially, we disregard expressions used inside annotations, if these
annotations occur in a file that has `from __future__ import annotations`, as
this prevents the annotations from being evaluated.
This commit is contained in:
Taus Brock-Nannestad
2019-11-25 15:32:52 +01:00
parent 77c869f528
commit 036e0f75c8
7 changed files with 67 additions and 1 deletions

View File

@@ -60,7 +60,8 @@ predicate import_time_module_use(ModuleValue m, ModuleValue enclosing, Expr use,
exists(Expr mod |
use.getEnclosingModule() = enclosing.getScope() and
not use.getScope+() instanceof Function and
mod.pointsTo(m)
mod.pointsTo(m) and
not is_annotation_with_from_future_import_annotations(use)
|
// either 'M.foo'
use.(Attribute).getObject() = mod and use.(Attribute).getName() = attr
@@ -70,6 +71,30 @@ predicate import_time_module_use(ModuleValue m, ModuleValue enclosing, Expr use,
)
}
/**
* Holds if `use` appears inside an annotation.
*/
predicate is_used_in_annotation(Expr use) {
exists(FunctionExpr f |
f.getReturns().getASubExpression*() = use or
f.getArgs().getAnAnnotation().getASubExpression*() = use
)
or
exists(AnnAssign a | a.getAnnotation().getASubExpression*() = use)
}
/**
* Holds if `use` appears as a subexpression of an annotation, _and_ if the
* postponed evaluation of annotations presented in PEP 563 is in effect.
* See https://www.python.org/dev/peps/pep-0563/
*/
predicate is_annotation_with_from_future_import_annotations(Expr use) {
exists(ImportMember i | i.getScope() = use.getEnclosingModule() |
i.getModule().pointsTo().getName() = "__future__" and i.getName() = "annotations"
) and
is_used_in_annotation(use)
}
/**
* Whether importing module 'first' before importing module 'other' will fail at runtime, due to an
* AttributeError at 'use' (in module 'other') caused by 'first.attr' not being defined as its definition can