Skip to content

Commit

Permalink
Merge pull request #34 from replit/dstewart/chore/extend-pending-2.0.…
Browse files Browse the repository at this point in the history
…9-release

chore/extend pending 2.0.9 release
  • Loading branch information
blast-hardcheese authored Mar 18, 2024
2 parents fe6c3e4 + 54abc90 commit 2cf4350
Show file tree
Hide file tree
Showing 69 changed files with 1,055 additions and 671 deletions.
3 changes: 3 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,8 @@ The following settings control pyright’s diagnostic output (warnings or errors

<a name="reportUnboundVariable"></a> **reportUnboundVariable** [boolean or string, optional]: Generate or suppress diagnostics for unbound variables. The default value for this setting is `"error"`.

<a name="reportUnhashable"></a> **reportUnhashable** [boolean or string, optional]: Generate or suppress diagnostics for the use of an unhashable object in a container that requires hashability.

<a name="reportInvalidStubStatement"></a> **reportInvalidStubStatement** [boolean or string, optional]: Generate or suppress diagnostics for statements that are syntactically correct but have no purpose within a type stub file. The default value for this setting is `"none"`.

<a name="reportIncompleteStub"></a> **reportIncompleteStub** [boolean or string, optional]: Generate or suppress diagnostics for a module-level `__getattr__` call in a type stub file, indicating that it is incomplete. The default value for this setting is `"none"`.
Expand Down Expand Up @@ -376,6 +378,7 @@ The following table lists the default severity levels for each diagnostic rule w
| reportTypedDictNotRequiredAccess | "none" | "error" | "error" | "error" |
| reportPrivateImportUsage | "none" | "error" | "error" | "error" |
| reportUnboundVariable | "none" | "error" | "error" | "error" |
| reportUnhashable | "none" | "error" | "error" | "error" |
| reportUnusedCoroutine | "none" | "error" | "error" | "error" |
| reportUnusedExcept | "none" | "error" | "error" | "error" |
| reportFunctionMemberAccess | "none" | "none" | "error" | "error" |
Expand Down
2 changes: 1 addition & 1 deletion lerna.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"packages": [
"packages/*"
],
"version": "1.1.353",
"version": "1.1.354",
"command": {
"version": {
"push": false,
Expand Down
4 changes: 2 additions & 2 deletions packages/pyright-internal/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/pyright-internal/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "pyright-internal",
"displayName": "pyright",
"description": "Type checker for the Python language",
"version": "1.1.353",
"version": "1.1.354",
"license": "MIT",
"private": true,
"files": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,8 @@ export class BackgroundAnalysisProgram {
return this._program.analyzeFile(fileUri, token);
}

libraryUpdated() {
// empty
libraryUpdated(): boolean {
return false;
}

async getDiagnosticsForRange(fileUri: Uri, range: Range, token: CancellationToken): Promise<Diagnostic[]> {
Expand Down
61 changes: 42 additions & 19 deletions packages/pyright-internal/src/analyzer/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1704,7 +1704,12 @@ export class Checker extends ParseTreeWalker {
const moduleName =
moduleNameNode.leadingDots === 0
? this._importResolver.getModuleNameForImport(uri, execEnv).moduleName
: getRelativeModuleName(this._importResolver.fileSystem, this._fileInfo.fileUri, uri);
: getRelativeModuleName(
this._importResolver.fileSystem,
this._fileInfo.fileUri,
uri,
this._importResolver.getConfigOptions()
);

if (!moduleName) {
return undefined;
Expand Down Expand Up @@ -1747,7 +1752,7 @@ export class Checker extends ParseTreeWalker {

let isTypeBool = true;
const diag = new DiagnosticAddendum();
this._evaluator.mapSubtypesExpandTypeVars(operandType, /* conditionFilter */ undefined, (expandedSubtype) => {
this._evaluator.mapSubtypesExpandTypeVars(operandType, /* options */ undefined, (expandedSubtype) => {
if (isAnyOrUnknown(expandedSubtype)) {
return undefined;
}
Expand Down Expand Up @@ -3135,21 +3140,31 @@ export class Checker extends ParseTreeWalker {
sawFinal = true;
}

if (decl.type === DeclarationType.Variable && decl.inferredTypeSource) {
if (sawAssignment) {
// We check for assignment of Final instance and class variables
// the type evaluator because we need to take into account whether
// the assignment is within an `__init__` method, so ignore class
// scopes here.
if (scopeType !== ScopeType.Class) {
this._evaluator.addDiagnostic(
DiagnosticRule.reportGeneralTypeIssues,
LocMessage.finalReassigned().format({ name }),
decl.node
);
let reportRedeclaration = false;

if (decl.type === DeclarationType.Variable) {
if (decl.inferredTypeSource) {
if (sawAssignment) {
// We check for assignment of Final instance and class variables
// the type evaluator because we need to take into account whether
// the assignment is within an `__init__` method, so ignore class
// scopes here.
if (scopeType !== ScopeType.Class) {
reportRedeclaration = true;
}
}
sawAssignment = true;
}
sawAssignment = true;
} else {
reportRedeclaration = true;
}

if (reportRedeclaration) {
this._evaluator.addDiagnostic(
DiagnosticRule.reportGeneralTypeIssues,
LocMessage.finalReassigned().format({ name }),
getNameNodeForDeclaration(decl) ?? decl.node
);
}
});

Expand Down Expand Up @@ -6117,9 +6132,11 @@ export class Checker extends ParseTreeWalker {
}

// If the symbol has no declaration, and the type is inferred,
// skip this check.
if (!symbol.hasTypedDeclarations() && !this._evaluator.isFinalVariable(symbol)) {
return;
// skip the type validation but still check for other issues like
// Final overrides and class/instance variable mismatches.
let validateType = true;
if (!symbol.hasTypedDeclarations()) {
validateType = false;
}

// Get the symbol type defined in this class.
Expand Down Expand Up @@ -6154,7 +6171,13 @@ export class Checker extends ParseTreeWalker {

firstOverride = firstOverride ?? baseClassAndSymbol;

this._validateBaseClassOverride(baseClassAndSymbol, symbol, typeOfSymbol, classType, name);
this._validateBaseClassOverride(
baseClassAndSymbol,
symbol,
validateType ? typeOfSymbol : AnyType.create(),
classType,
name
);
}

if (!firstOverride) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { ModuleNameAndType } from './importResolver';
import { ImportResult, ImportType } from './importResult';
import { findTokenAfter, getTokenAt } from './parseTreeUtils';
import * as SymbolNameUtils from './symbolNameUtils';
import { ConfigOptions } from '../common/configOptions';

export interface ImportStatement {
node: ImportNode | ImportFromNode;
Expand Down Expand Up @@ -849,6 +850,7 @@ export function getRelativeModuleName(
fs: ReadOnlyFileSystem,
sourcePath: Uri,
targetPath: Uri,
configOptions: ConfigOptions,
ignoreFolderStructure = false,
sourceIsFile?: boolean
) {
Expand All @@ -860,6 +862,13 @@ export function getRelativeModuleName(

let symbolName: string | undefined;
let destPath = targetPath;
if (
(configOptions.stubPath && destPath.isChild(configOptions.stubPath)) ||
(configOptions.typeshedPath && destPath.isChild(configOptions.typeshedPath))
) {
// Always use absolute imports for files in these library-like directories.
return undefined;
}
if (sourceIsFile) {
destPath = targetPath.getDirectory();

Expand Down
38 changes: 28 additions & 10 deletions packages/pyright-internal/src/analyzer/operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
import { OperatorType } from '../parser/tokenizerTypes';
import { getFileInfo } from './analyzerNodeInfo';
import { getEnclosingLambda, isWithinLoop, operatorSupportsChaining, printOperator } from './parseTreeUtils';
import { getScopeForNode } from './scopeUtils';
import { evaluateStaticBoolExpression } from './staticExpressions';
import { EvaluatorFlags, TypeEvaluator, TypeResult } from './typeEvaluatorTypes';
import {
Expand Down Expand Up @@ -169,11 +170,11 @@ export function validateBinaryOperation(
if (operator === OperatorType.In || operator === OperatorType.NotIn) {
type = evaluator.mapSubtypesExpandTypeVars(
rightType,
/* conditionFilter */ undefined,
/* options */ undefined,
(rightSubtypeExpanded, rightSubtypeUnexpanded) => {
return evaluator.mapSubtypesExpandTypeVars(
concreteLeftType,
getTypeCondition(rightSubtypeExpanded),
{ conditionFilter: getTypeCondition(rightSubtypeExpanded) },
(leftSubtype) => {
if (isAnyOrUnknown(leftSubtype) || isAnyOrUnknown(rightSubtypeUnexpanded)) {
return preserveUnknown(leftSubtype, rightSubtypeExpanded);
Expand Down Expand Up @@ -225,11 +226,11 @@ export function validateBinaryOperation(
} else {
type = evaluator.mapSubtypesExpandTypeVars(
concreteLeftType,
/* conditionFilter */ undefined,
/* options */ undefined,
(leftSubtypeExpanded, leftSubtypeUnexpanded) => {
return evaluator.mapSubtypesExpandTypeVars(
rightType,
getTypeCondition(leftSubtypeExpanded),
{ conditionFilter: getTypeCondition(leftSubtypeExpanded) },
(rightSubtypeExpanded, rightSubtypeUnexpanded) => {
// If the operator is an AND or OR, we need to combine the two types.
if (operator === OperatorType.And || operator === OperatorType.Or) {
Expand Down Expand Up @@ -351,11 +352,11 @@ export function validateBinaryOperation(
if (!type) {
type = evaluator.mapSubtypesExpandTypeVars(
leftType,
/* conditionFilter */ undefined,
/* options */ undefined,
(leftSubtypeExpanded, leftSubtypeUnexpanded) => {
return evaluator.mapSubtypesExpandTypeVars(
rightType,
getTypeCondition(leftSubtypeExpanded),
{ conditionFilter: getTypeCondition(leftSubtypeExpanded) },
(rightSubtypeExpanded, rightSubtypeUnexpanded) => {
if (isAnyOrUnknown(leftSubtypeUnexpanded) || isAnyOrUnknown(rightSubtypeUnexpanded)) {
return preserveUnknown(leftSubtypeUnexpanded, rightSubtypeUnexpanded);
Expand Down Expand Up @@ -731,7 +732,7 @@ export function getTypeOfBinaryOperation(

const diag = new DiagnosticAddendum();

// Don't use literal math if either of the operation is within a loop
// Don't use literal math if the operation is within a loop
// because the literal values may change each time. We also don't want to
// apply literal math within the body of a lambda because they are often
// used as callbacks where the value changes each time they are called.
Expand Down Expand Up @@ -847,11 +848,11 @@ export function getTypeOfAugmentedAssignment(
} else {
type = evaluator.mapSubtypesExpandTypeVars(
leftType,
/* conditionFilter */ undefined,
/* options */ undefined,
(leftSubtypeExpanded, leftSubtypeUnexpanded) => {
return evaluator.mapSubtypesExpandTypeVars(
rightType,
getTypeCondition(leftSubtypeExpanded),
{ conditionFilter: getTypeCondition(leftSubtypeExpanded) },
(rightSubtypeExpanded, rightSubtypeUnexpanded) => {
if (isAnyOrUnknown(leftSubtypeUnexpanded) || isAnyOrUnknown(rightSubtypeUnexpanded)) {
return preserveUnknown(leftSubtypeUnexpanded, rightSubtypeUnexpanded);
Expand Down Expand Up @@ -893,10 +894,11 @@ export function getTypeOfAugmentedAssignment(
// assignment, fall back on the normal binary expression evaluator.
const binaryOperator = operatorMap[node.operator][1];

// Don't use literal math if either of the operation is within a loop
// Don't use literal math if the operation is within a loop
// because the literal values may change each time.
const isLiteralMathAllowed =
!isWithinLoop(node) &&
isExpressionLocalVariable(evaluator, node.leftExpression) &&
getUnionSubtypeCount(leftType) * getUnionSubtypeCount(rightType) <
maxLiteralMathSubtypeCount;

Expand Down Expand Up @@ -1162,3 +1164,19 @@ function convertFunctionToObject(evaluator: TypeEvaluator, type: Type) {

return type;
}

// Determines whether the expression refers to a variable that
// is defined within the current scope or some outer scope.
function isExpressionLocalVariable(evaluator: TypeEvaluator, node: ExpressionNode): boolean {
if (node.nodeType !== ParseNodeType.Name) {
return false;
}

const symbolWithScope = evaluator.lookUpSymbolRecursive(node, node.value, /* honorCodeFlow */ false);
if (!symbolWithScope) {
return false;
}

const currentScope = getScopeForNode(node);
return currentScope === symbolWithScope.scope;
}
34 changes: 31 additions & 3 deletions packages/pyright-internal/src/analyzer/parseTreeUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ export function printExpression(node: ExpressionNode, flags = PrintExpressionFla
let escapedString = node.token.escapedValue;
if ((flags & PrintExpressionFlags.DoNotLimitStringLength) === 0) {
const maxStringLength = 32;
escapedString = escapedString.substring(0, maxStringLength);
escapedString = escapedString.slice(0, maxStringLength);
}

if (node.token.flags & StringTokenFlags.Triplicate) {
Expand Down Expand Up @@ -1840,13 +1840,19 @@ export function getTokenAt(tokens: TextRangeCollection<Token>, position: number)
}

export function getTokenOverlapping(tokens: TextRangeCollection<Token>, position: number) {
const index = getIndexOfTokenOverlapping(tokens, position);
return getTokenAtIndex(tokens, index);
}

export function getIndexOfTokenOverlapping(tokens: TextRangeCollection<Token>, position: number) {
const index = tokens.getItemAtPosition(position);
if (index < 0) {
return undefined;
return -1;
}

const token = tokens.getItemAt(index);
return TextRange.overlaps(token, position) ? token : undefined;

return TextRange.overlaps(token, position) ? index : -1;
}

export function findTokenAfter(parseResults: ParseResults, offset: number, predicate: (t: Token) => boolean) {
Expand All @@ -1867,6 +1873,28 @@ export function findTokenAfter(parseResults: ParseResults, offset: number, predi
return undefined;
}

export function getCommentsAtTokenIndex(tokens: TextRangeCollection<Token>, index: number) {
let token = getTokenAtIndex(tokens, index);
if (!token) {
return undefined;
}

// If the preceding token has the same start offset
// (in other words, when tokens have zero length and they're piled on top of each other)
// look back through the tokens until we find the first token with that start offset.
// That's where the comments (if any) will be.
for (let precedingIndex = index - 1; precedingIndex >= 0; --precedingIndex) {
const precedingToken = getTokenAtIndex(tokens, precedingIndex);
if (precedingToken && precedingToken.start === token.start) {
token = precedingToken;
} else {
break;
}
}

return token.comments;
}

export function printParseNodeType(type: ParseNodeType) {
switch (type) {
case ParseNodeType.Error:
Expand Down
Loading

0 comments on commit 2cf4350

Please sign in to comment.