From 80b59618ac4bca437909986524db7a87dbdc8619 Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Sun, 14 Jan 2024 20:07:13 -0800 Subject: [PATCH] Added support for @no_type_check decorator. This addresses #1448. (#6987) --- .../src/analyzer/decorators.ts | 2 + .../src/analyzer/typeEvaluator.ts | 39 +++++++++++++------ .../pyright-internal/src/analyzer/types.ts | 3 ++ .../src/tests/samples/noTypeCheck1.py | 28 +++++++++++++ .../src/tests/typeEvaluator5.test.ts | 5 +++ 5 files changed, 66 insertions(+), 11 deletions(-) create mode 100644 packages/pyright-internal/src/tests/samples/noTypeCheck1.py diff --git a/packages/pyright-internal/src/analyzer/decorators.ts b/packages/pyright-internal/src/analyzer/decorators.ts index 2c5f706cd3..7adf631c67 100644 --- a/packages/pyright-internal/src/analyzer/decorators.ts +++ b/packages/pyright-internal/src/analyzer/decorators.ts @@ -113,6 +113,8 @@ export function getFunctionInfoFromDecorators( flags |= FunctionTypeFlags.Overridden; } else if (decoratorType.details.builtInName === 'type_check_only') { flags |= FunctionTypeFlags.TypeCheckOnly; + } else if (decoratorType.details.builtInName === 'no_type_check') { + flags |= FunctionTypeFlags.NoTypeCheck; } else if (decoratorType.details.builtInName === 'overload') { flags |= FunctionTypeFlags.Overloaded; } diff --git a/packages/pyright-internal/src/analyzer/typeEvaluator.ts b/packages/pyright-internal/src/analyzer/typeEvaluator.ts index 9a21598255..41fca15d6e 100644 --- a/packages/pyright-internal/src/analyzer/typeEvaluator.ts +++ b/packages/pyright-internal/src/analyzer/typeEvaluator.ts @@ -3163,17 +3163,30 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions return undefined; } - // Should we suppress this diagnostic because it's within an unannotated function? - const fileInfo = AnalyzerNodeInfo.getFileInfo(node); - if (!fileInfo.diagnosticRuleSet.analyzeUnannotatedFunctions) { - const containingFunction = ParseTreeUtils.getEnclosingFunction(node); + const containingFunction = ParseTreeUtils.getEnclosingFunction(node); - // Is the target node within the body of the function? If so, suppress the diagnostic. - if ( - containingFunction && - ParseTreeUtils.isUnannotatedFunction(containingFunction) && - ParseTreeUtils.isNodeContainedWithin(node, containingFunction.suite) - ) { + if (containingFunction) { + // Should we suppress this diagnostic because it's within an unannotated function? + const fileInfo = AnalyzerNodeInfo.getFileInfo(node); + if (!fileInfo.diagnosticRuleSet.analyzeUnannotatedFunctions) { + // Is the target node within the body of the function? If so, suppress the diagnostic. + if ( + ParseTreeUtils.isUnannotatedFunction(containingFunction) && + ParseTreeUtils.isNodeContainedWithin(node, containingFunction.suite) + ) { + return undefined; + } + } + + // Should we suppress this diagnostic because it's within a no_type_check function? + const containingClassNode = ParseTreeUtils.getEnclosingClass(containingFunction, /* stopAtFunction */ true); + const functionInfo = getFunctionInfoFromDecorators( + evaluatorInterface, + containingFunction, + !!containingClassNode + ); + + if ((functionInfo.flags & FunctionTypeFlags.NoTypeCheck) !== 0) { return undefined; } } @@ -17630,7 +17643,11 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions } if (paramTypeNode) { - annotatedType = getTypeOfParameterAnnotation(paramTypeNode, param.category); + if ((functionInfo.flags & FunctionTypeFlags.NoTypeCheck) !== 0) { + annotatedType = UnknownType.create(); + } else { + annotatedType = getTypeOfParameterAnnotation(paramTypeNode, param.category); + } if (isVariadicTypeVar(annotatedType) && !annotatedType.isVariadicUnpacked) { addError( diff --git a/packages/pyright-internal/src/analyzer/types.ts b/packages/pyright-internal/src/analyzer/types.ts index 07d68142d5..eb90a5a9ed 100644 --- a/packages/pyright-internal/src/analyzer/types.ts +++ b/packages/pyright-internal/src/analyzer/types.ts @@ -1399,6 +1399,9 @@ export const enum FunctionTypeFlags { // Decorated with @override as defined in PEP 698. Overridden = 1 << 18, + + // Decorated with @no_type_check. + NoTypeCheck = 1 << 19, } interface FunctionDetails { diff --git a/packages/pyright-internal/src/tests/samples/noTypeCheck1.py b/packages/pyright-internal/src/tests/samples/noTypeCheck1.py new file mode 100644 index 0000000000..f82ea703d5 --- /dev/null +++ b/packages/pyright-internal/src/tests/samples/noTypeCheck1.py @@ -0,0 +1,28 @@ +# This sample tests the handling of the @no_type_check decorator. + + +from typing import no_type_check + + +@no_type_check +class A: + # This should generate an error because no_type_check has + # no effect when applied to a class. + x: int = "" + + +@no_type_check +def func1(a: int, b: int(), *args, c: int = 3) -> dummy: + x: int = "" + + +reveal_type( + func1, + expected_text="(a: Unknown, b: Unknown, *args: Unknown, c: Unknown = 3) -> Unknown", +) + + +# This should generate an error. +func1() + +func1("", "", c="") diff --git a/packages/pyright-internal/src/tests/typeEvaluator5.test.ts b/packages/pyright-internal/src/tests/typeEvaluator5.test.ts index 38e5f2ee2d..1cc6145dd4 100644 --- a/packages/pyright-internal/src/tests/typeEvaluator5.test.ts +++ b/packages/pyright-internal/src/tests/typeEvaluator5.test.ts @@ -369,3 +369,8 @@ test('TypeCheckOnly1', () => { const analysisResults = TestUtils.typeAnalyzeSampleFiles(['typeCheckOnly1.py']); TestUtils.validateResults(analysisResults, 4); }); + +test('NoTypeCheck1', () => { + const analysisResults = TestUtils.typeAnalyzeSampleFiles(['noTypeCheck1.py']); + TestUtils.validateResults(analysisResults, 2); +});