diff --git a/.github/workflows/build_and_release.yml b/.github/workflows/build_and_release.yml index ebc24bb543..0c7721a027 100644 --- a/.github/workflows/build_and_release.yml +++ b/.github/workflows/build_and_release.yml @@ -19,7 +19,7 @@ jobs: pull-requests: read steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 0595d43414..4a8e29420e 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -32,7 +32,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/mypy_primer_comment.yaml b/.github/workflows/mypy_primer_comment.yaml index 43db369f74..d9754a76d8 100644 --- a/.github/workflows/mypy_primer_comment.yaml +++ b/.github/workflows/mypy_primer_comment.yaml @@ -21,7 +21,7 @@ jobs: if: ${{ github.event.workflow_run.conclusion == 'success' }} steps: - name: Download diffs - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: script: | const fs = require('fs'); @@ -30,24 +30,27 @@ jobs: repo: context.repo.repo, run_id: ${{ github.event.workflow_run.id }}, }); - const [matchArtifact] = artifacts.data.artifacts.filter((artifact) => - artifact.name == "mypy_primer_diffs"); + const matchedArtifacts = artifacts.data.artifacts.filter((artifact) => + artifact.name.startsWith("mypy_primer_diffs")); - const download = await github.rest.actions.downloadArtifact({ - owner: context.repo.owner, - repo: context.repo.repo, - artifact_id: matchArtifact.id, - archive_format: "zip", - }); - fs.writeFileSync("diff.zip", Buffer.from(download.data)); + for (let i = 0; i < matchedArtifacts.length; i++) { + const matchArtifact = matchedArtifacts[i]; + const download = await github.rest.actions.downloadArtifact({ + owner: context.repo.owner, + repo: context.repo.repo, + artifact_id: matchArtifact.id, + archive_format: "zip", + }); + fs.writeFileSync(`diff_${i}.zip`, Buffer.from(download.data)); + } - - run: unzip diff.zip + - run: unzip diff_\*.zip - run: | cat diff_*.txt | tee fulldiff.txt - name: Post comment id: post-comment - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/mypy_primer_pr.yaml b/.github/workflows/mypy_primer_pr.yaml index 6078cedde8..60f234a744 100644 --- a/.github/workflows/mypy_primer_pr.yaml +++ b/.github/workflows/mypy_primer_pr.yaml @@ -41,11 +41,11 @@ jobs: shard-index: [0, 1] fail-fast: false steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: path: pyright_to_test fetch-depth: 0 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: '3.10' - name: Install dependencies @@ -78,17 +78,17 @@ jobs: | tee diff_${{ matrix.shard-index }}.txt ) || [ $? -eq 1 ] - name: Upload mypy_primer diff - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: mypy_primer_diffs + name: mypy_primer_diffs_${{ matrix.shard-index }} path: diff_${{ matrix.shard-index }}.txt - - if: ${{ matrix.shard-index }} == 0 + - if: ${{ matrix.shard-index == 0 }} name: Save PR number run: | echo ${{ github.event.pull_request.number }} | tee pr_number.txt - - if: ${{ matrix.shard-index }} == 0 + - if: ${{ matrix.shard-index == 0 }} name: Upload PR number - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: mypy_primer_diffs + name: mypy_primer_diffs_pr_number path: pr_number.txt diff --git a/.github/workflows/mypy_primer_push.yaml b/.github/workflows/mypy_primer_push.yaml index 2d2d34578e..2bd9df09a4 100644 --- a/.github/workflows/mypy_primer_push.yaml +++ b/.github/workflows/mypy_primer_push.yaml @@ -32,11 +32,11 @@ jobs: strategy: fail-fast: false steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: path: pyright_to_test fetch-depth: 0 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: '3.10' - name: Install dependencies @@ -62,7 +62,7 @@ jobs: | tee diff.txt ) || [ $? -eq 1 ] - name: Upload mypy_primer diff - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: mypy_primer_diffs path: diff.txt diff --git a/.github/workflows/validation.yml b/.github/workflows/validation.yml index d769ee0f8b..d65f1d26da 100644 --- a/.github/workflows/validation.yml +++ b/.github/workflows/validation.yml @@ -13,7 +13,7 @@ jobs: static_checks: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - run: ./pw pdm use ${{ env.PYTHON_VERSION }} @@ -51,9 +51,9 @@ jobs: name: Test ${{ matrix.os }} runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} @@ -81,7 +81,7 @@ jobs: # Install python so we can create a VENV for tests - name: Use Python ${{env.PYTHON_VERSION}} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 id: install_python with: python-version: ${{env.PYTHON_VERSION}} @@ -127,9 +127,9 @@ jobs: needs: static_checks steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} diff --git a/docs/configuration.md b/docs/configuration.md index 084a39dab3..56fc372a98 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -8,14 +8,16 @@ Relative paths specified within the config file are relative to the config file ## Main Configuration Options -- **include** [array of paths, optional]: Paths of directories or files that should be included. If no paths are specified, pyright defaults to the directory that contains the config file. Paths may contain wildcard characters ** (a directory or multiple levels of directories), * (a sequence of zero or more characters), or ? (a single character). If no include paths are specified, the root path for the workspace is assumed. +- **include** [array of paths, optional]: Paths of directories or files that should be considered part of the project. If no paths are specified, pyright defaults to the directory that contains the config file. Paths may contain wildcard characters ** (a directory or multiple levels of directories), * (a sequence of zero or more characters), or ? (a single character). If no include paths are specified, the root path for the workspace is assumed. -- **exclude** [array of paths, optional]: Paths of directories or files that should not be included. These override the includes directories and files, allowing specific subdirectories to be ignored. Note that files in the exclude paths may still be included in the analysis if they are referenced (imported) by source files that are not excluded. Paths may contain wildcard characters ** (a directory or multiple levels of directories), * (a sequence of zero or more characters), or ? (a single character). If no exclude paths are specified, Pyright automatically excludes the following: `**/node_modules`, `**/__pycache__`, `**/.*`. Pylance also excludes any virtual environment directories regardless of the exclude paths specified. For more detail on Python environment specification and discovery, refer to the [import resolution](import-resolution.md#configuring-your-python-environment) documentation. +- **exclude** [array of paths, optional]: Paths of directories or files that should not be considered part of the project. These override the includes directories and files, allowing specific subdirectories to be excluded. Note that files in the exclude paths may still be included in the analysis if they are referenced (imported) by source files that are not excluded. Paths may contain wildcard characters ** (a directory or multiple levels of directories), * (a sequence of zero or more characters), or ? (a single character). If no exclude paths are specified, Pyright automatically excludes the following: `**/node_modules`, `**/__pycache__`, `**/.*`. Pylance also excludes any virtual environment directories regardless of the exclude paths specified. For more detail on Python environment specification and discovery, refer to the [import resolution](import-resolution.md#configuring-your-python-environment) documentation. - **ignore** [array of paths, optional]: Paths of directories or files whose diagnostic output (errors and warnings) should be suppressed even if they are an included file or within the transitive closure of an included file. Paths may contain wildcard characters ** (a directory or multiple levels of directories), * (a sequence of zero or more characters), or ? (a single character). - **strict** [array of paths, optional]: Paths of directories or files that should use “strict” analysis if they are included. This is the same as manually adding a “# pyright: strict” comment. In strict mode, most type-checking rules are enabled. Refer to [this table](configuration.md#diagnostic-settings-defaults) for details about which rules are enabled in strict mode. Paths may contain wildcard characters ** (a directory or multiple levels of directories), * (a sequence of zero or more characters), or ? (a single character). +- **extends** [path, optional]: Path to another `.json` or `.toml` file that is used as a “base configuration”, allowing this configuration to inherit configuration settings. Top-level keys within this configuration overwrite top-level keys in the base configuration. Multiple levels of inheritance are supported. Relative paths specified in a configuration file are resolved relative to the location of that configuration file. + - **defineConstant** [map of constants to values (boolean or string), optional]: Set of identifiers that should be assumed to contain a constant value wherever used within this program. For example, `{ "DEBUG": true }` indicates that pyright should assume that the identifier `DEBUG` will always be equal to `True`. If this identifier is used within a conditional expression (such as `if not DEBUG:`) pyright will use the indicated value to determine whether the guarded block is reachable or not. Member expressions that reference one of these constants (e.g. `my_module.DEBUG`) are also supported. - **typeshedPath** [path, optional]: Path to a directory that contains typeshed type stub files. Pyright ships with a bundled copy of typeshed type stubs. If you want to use a different version of typeshed stubs, you can clone the [typeshed github repo](https://github.com/python/typeshed) to a local directory and reference the location with this path. This option is useful if you’re actively contributing updates to typeshed. diff --git a/docs/type-inference.md b/docs/type-inference.md index 2f9833a4ed..8d5b456f6a 100644 --- a/docs/type-inference.md +++ b/docs/type-inference.md @@ -261,11 +261,11 @@ var1 = [4] When inferring the type of a tuple expression (in the absence of bidirectional inference hints), Pyright assumes that the tuple has a fixed length, and each tuple element is typed as specifically as possible. ```python -# The inferred type is tuple[Literal[1], Literal["a"], Literal[True]] +# The inferred type is tuple[Literal[1], Literal["a"], Literal[True]]. var1 = (1, "a", True) def func1(a: int): - # The inferred type is tuple[int, int] + # The inferred type is tuple[int, int]. var2 = (a, a) # If you want the type to be tuple[int, ...] @@ -274,6 +274,13 @@ def func1(a: int): var3: tuple[int, ...] = (a, a) ``` +Because tuples are typed as specifically as possible, literal types are normally retained. However, as an exception to this inference rule, if the tuple expression is nested within another tuple, set, list or dictionary expression, literal types are not retained. This is done to avoid the inference of complex types (e.g. unions with many subtypes) when evaluating tuple statements with many entries. + +```python +# The inferred type is list[tuple[int, str, bool]]. +var4 = [(1, "a", True), (2, "b", False), (3, "c", False)] +``` + #### List Expressions When inferring the type of a list expression (in the absence of bidirectional inference hints), Pyright uses the following heuristics: diff --git a/lerna.json b/lerna.json index 0b55dd71f0..32cd9b3541 100644 --- a/lerna.json +++ b/lerna.json @@ -2,7 +2,7 @@ "packages": [ "packages/*" ], - "version": "1.1.364", + "version": "1.1.365", "command": { "version": { "push": false, diff --git a/packages/pyright-internal/package-lock.json b/packages/pyright-internal/package-lock.json index 521930fdea..08360917ec 100644 --- a/packages/pyright-internal/package-lock.json +++ b/packages/pyright-internal/package-lock.json @@ -1,12 +1,12 @@ { "name": "pyright-internal", - "version": "1.1.364", + "version": "1.1.365", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "pyright-internal", - "version": "1.1.364", + "version": "1.1.365", "license": "MIT", "dependencies": { "@actions/core": "^1.10.1", diff --git a/packages/pyright-internal/package.json b/packages/pyright-internal/package.json index 668c58c4c2..25b08ff375 100644 --- a/packages/pyright-internal/package.json +++ b/packages/pyright-internal/package.json @@ -2,7 +2,7 @@ "name": "pyright-internal", "displayName": "pyright", "description": "Type checker for the Python language", - "version": "1.1.364", + "version": "1.1.365", "license": "MIT", "private": true, "files": [ diff --git a/packages/pyright-internal/src/analyzer/analysis.ts b/packages/pyright-internal/src/analyzer/analysis.ts index 5fa84c82fc..21be06fe50 100644 --- a/packages/pyright-internal/src/analyzer/analysis.ts +++ b/packages/pyright-internal/src/analyzer/analysis.ts @@ -24,13 +24,18 @@ export interface AnalysisResults { diagnostics: FileDiagnostics[]; filesInProgram: number; checkingOnlyOpenFiles: boolean; - filesRequiringAnalysis: number; + requiringAnalysisCount: RequiringAnalysisCount; fatalErrorOccurred: boolean; configParseErrorOccurred: boolean; elapsedTime: number; error?: Error | undefined; } +export interface RequiringAnalysisCount { + files: number; + cells: number; +} + export type AnalysisCompleteCallback = (results: AnalysisResults) => void; export function analyzeProgram( @@ -51,7 +56,7 @@ export function analyzeProgram( const duration = new Duration(); moreToAnalyze = program.analyze(maxTime, token); - const filesLeftToAnalyze = program.getFilesToAnalyzeCount(); + const requiringAnalysisCount = program.getFilesToAnalyzeCount(); // If we're using command-line mode, the maxTime will be undefined, and we'll // want to report all diagnostics rather than just the ones that have changed. @@ -66,7 +71,7 @@ export function analyzeProgram( callback({ diagnostics, filesInProgram: program.getFileCount(), - filesRequiringAnalysis: filesLeftToAnalyze, + requiringAnalysisCount: requiringAnalysisCount, checkingOnlyOpenFiles: program.isCheckingOnlyOpenFiles(), fatalErrorOccurred: false, configParseErrorOccurred: false, @@ -84,7 +89,7 @@ export function analyzeProgram( callback({ diagnostics: [], filesInProgram: 0, - filesRequiringAnalysis: 0, + requiringAnalysisCount: { files: 0, cells: 0 }, checkingOnlyOpenFiles: true, fatalErrorOccurred: true, configParseErrorOccurred: false, diff --git a/packages/pyright-internal/src/analyzer/backgroundAnalysisProgram.ts b/packages/pyright-internal/src/analyzer/backgroundAnalysisProgram.ts index 159c80cb07..3d1f8f2f0b 100644 --- a/packages/pyright-internal/src/analyzer/backgroundAnalysisProgram.ts +++ b/packages/pyright-internal/src/analyzer/backgroundAnalysisProgram.ts @@ -262,7 +262,7 @@ export class BackgroundAnalysisProgram { this._onAnalysisCompletion({ diagnostics: fileDiags, filesInProgram: this._program.getFileCount(), - filesRequiringAnalysis: this._program.getFilesToAnalyzeCount(), + requiringAnalysisCount: this._program.getFilesToAnalyzeCount(), checkingOnlyOpenFiles: this._program.isCheckingOnlyOpenFiles(), fatalErrorOccurred: false, configParseErrorOccurred: false, diff --git a/packages/pyright-internal/src/analyzer/constructorTransform.ts b/packages/pyright-internal/src/analyzer/constructorTransform.ts index 233d4c2376..22ad1bb009 100644 --- a/packages/pyright-internal/src/analyzer/constructorTransform.ts +++ b/packages/pyright-internal/src/analyzer/constructorTransform.ts @@ -16,7 +16,7 @@ import { DiagnosticRule } from '../common/diagnosticRules'; import { LocMessage } from '../localization/localize'; import { ArgumentCategory, ExpressionNode, ParameterCategory } from '../parser/parseNodes'; import { createFunctionFromConstructor } from './constructors'; -import { getParameterListDetails, ParameterSource } from './parameterUtils'; +import { getParameterListDetails, ParameterKind } from './parameterUtils'; import { Symbol, SymbolFlags } from './symbol'; import { FunctionArgument, FunctionResult, TypeEvaluator } from './typeEvaluatorTypes'; import { @@ -246,7 +246,7 @@ function applyPartialTransformToFunction( // Does this positional argument map to a positional parameter? if ( argIndex >= paramListDetails.params.length || - paramListDetails.params[argIndex].source === ParameterSource.KeywordOnly + paramListDetails.params[argIndex].kind === ParameterKind.Keyword ) { if (paramListDetails.argsIndex !== undefined) { const paramType = FunctionType.getEffectiveParameterType( @@ -329,8 +329,7 @@ function applyPartialTransformToFunction( } } else { const matchingParam = paramListDetails.params.find( - (paramInfo) => - paramInfo.param.name === arg.name?.value && paramInfo.source !== ParameterSource.PositionOnly + (paramInfo) => paramInfo.param.name === arg.name?.value && paramInfo.kind !== ParameterKind.Positional ); if (!matchingParam) { diff --git a/packages/pyright-internal/src/analyzer/enums.ts b/packages/pyright-internal/src/analyzer/enums.ts index e1b0ee04ca..88f34dd8b8 100644 --- a/packages/pyright-internal/src/analyzer/enums.ts +++ b/packages/pyright-internal/src/analyzer/enums.ts @@ -587,9 +587,10 @@ export function getTypeOfEnumMember( if (memberName === 'value' || memberName === '_value_') { // Does the class explicitly override this member? Or it it using the - // standard behavior provided by the "Enum" class? + // standard behavior provided by the "Enum" class and other built-in + // subclasses like "StrEnum" and "IntEnum"? const memberInfo = lookUpClassMember(classType, memberName); - if (memberInfo && isClass(memberInfo.classType) && !ClassType.isBuiltIn(memberInfo.classType, 'Enum')) { + if (memberInfo && isClass(memberInfo.classType) && !ClassType.isBuiltIn(memberInfo.classType)) { return undefined; } diff --git a/packages/pyright-internal/src/analyzer/importResolver.ts b/packages/pyright-internal/src/analyzer/importResolver.ts index 245541b7ce..9d1bca05f7 100644 --- a/packages/pyright-internal/src/analyzer/importResolver.ts +++ b/packages/pyright-internal/src/analyzer/importResolver.ts @@ -338,7 +338,7 @@ export class ImportResolver { isStdlibModule(module: ImportedModuleDescriptor, execEnv: ExecutionEnvironment): boolean { if (!this._stdlibModules) { - this._stdlibModules = this._buildStdlibCache(this.getTypeshedStdLibPath(execEnv)); + this._stdlibModules = this._buildStdlibCache(this.getTypeshedStdLibPath(execEnv), execEnv); } return this._stdlibModules.has(module.nameParts.join('.')); @@ -564,6 +564,7 @@ export class ImportResolver { return importResult; } + const localImportFailureInfo: string[] = [`Attempting to resolve using local imports: ${importName}`]; const importPath: ImportPath = { importPath: undefined }; // Going up the given folder one by one until we can resolve the import. @@ -575,7 +576,7 @@ export class ImportResolver { execEnv, moduleDescriptor, importName, - [], + localImportFailureInfo, /* allowPartial */ undefined, /* allowNativeLib */ undefined, /* useStubPackage */ false, @@ -610,6 +611,12 @@ export class ImportResolver { if (current) { this.cachedParentImportResults.checked(current, importName, importPath); } + + if (this._configOptions.verboseOutput) { + const console = this.serviceProvider.console(); + localImportFailureInfo.forEach((diag) => console.log(diag)); + } + return importResult; } @@ -1897,7 +1904,7 @@ export class ImportResolver { } // Finds all of the stdlib modules and returns a Set containing all of their names. - private _buildStdlibCache(stdlibRoot: Uri | undefined): Set { + private _buildStdlibCache(stdlibRoot: Uri | undefined, executionEnvironment: ExecutionEnvironment): Set { const cache = new Set(); if (stdlibRoot) { @@ -1910,7 +1917,17 @@ export class ImportResolver { const stripped = stripFileExtension(entry.name); // Skip anything starting with an underscore. if (!stripped.startsWith('_')) { - cache.add(prefix ? `${prefix}.${stripped}` : stripped); + if ( + this._isStdlibTypeshedStubValidForVersion( + createImportedModuleDescriptor(stripped), + root, + executionEnvironment.pythonVersion, + executionEnvironment.pythonPlatform, + [] + ) + ) { + cache.add(prefix ? `${prefix}.${stripped}` : stripped); + } } } }); diff --git a/packages/pyright-internal/src/analyzer/parameterUtils.ts b/packages/pyright-internal/src/analyzer/parameterUtils.ts index f23aa90822..ab90b052b8 100644 --- a/packages/pyright-internal/src/analyzer/parameterUtils.ts +++ b/packages/pyright-internal/src/analyzer/parameterUtils.ts @@ -35,10 +35,10 @@ export function isTypedKwargs(param: FunctionParameter): boolean { ); } -export enum ParameterSource { - PositionOnly, - PositionOrKeyword, - KeywordOnly, +export enum ParameterKind { + Positional, + Standard, + Keyword, } export interface VirtualParameterDetails { @@ -46,7 +46,7 @@ export interface VirtualParameterDetails { type: Type; defaultArgType?: Type | undefined; index: number; - source: ParameterSource; + kind: ParameterKind; } export interface ParameterListDetails { @@ -131,20 +131,20 @@ export function getParameterListDetails(type: FunctionType): ParameterListDetail index: number, typeOverride?: Type, defaultArgTypeOverride?: Type, - sourceOverride?: ParameterSource + sourceOverride?: ParameterKind ) => { if (param.name) { - let source: ParameterSource; + let kind: ParameterKind; if (sourceOverride !== undefined) { - source = sourceOverride; + kind = sourceOverride; } else if (param.category === ParameterCategory.ArgsList) { - source = ParameterSource.PositionOnly; + kind = ParameterKind.Positional; } else if (sawKeywordOnlySeparator) { - source = ParameterSource.KeywordOnly; + kind = ParameterKind.Keyword; } else if (positionOnlyIndex >= 0 && index < positionOnlyIndex) { - source = ParameterSource.PositionOnly; + kind = ParameterKind.Positional; } else { - source = ParameterSource.PositionOrKeyword; + kind = ParameterKind.Standard; } result.params.push({ @@ -152,7 +152,7 @@ export function getParameterListDetails(type: FunctionType): ParameterListDetail index, type: typeOverride ?? FunctionType.getEffectiveParameterType(type, index), defaultArgType: defaultArgTypeOverride, - source, + kind, }); } }; @@ -189,7 +189,7 @@ export function getParameterListDetails(type: FunctionType): ParameterListDetail index, tupleArg.type, /* defaultArgTypeOverride */ undefined, - ParameterSource.PositionOnly + ParameterKind.Positional ); if (category === ParameterCategory.Simple) { @@ -323,7 +323,7 @@ export function getParameterListDetails(type: FunctionType): ParameterListDetail } } - result.firstPositionOrKeywordIndex = result.params.findIndex((p) => p.source !== ParameterSource.PositionOnly); + result.firstPositionOrKeywordIndex = result.params.findIndex((p) => p.kind !== ParameterKind.Positional); if (result.firstPositionOrKeywordIndex < 0) { result.firstPositionOrKeywordIndex = result.params.length; } diff --git a/packages/pyright-internal/src/analyzer/parentDirectoryCache.ts b/packages/pyright-internal/src/analyzer/parentDirectoryCache.ts index 04e9fd42e5..7fe3e53618 100644 --- a/packages/pyright-internal/src/analyzer/parentDirectoryCache.ts +++ b/packages/pyright-internal/src/analyzer/parentDirectoryCache.ts @@ -30,8 +30,7 @@ export class ParentDirectoryCache { const result = this._cachedResults.get(importName)?.get(path.key); if (result) { // We already checked for the importName at the path. - // Return the result if succeeded otherwise, return regular import result given. - return result ?? importResult; + return result; } const checked = this._importChecked.get(importName)?.get(path.key); diff --git a/packages/pyright-internal/src/analyzer/program.ts b/packages/pyright-internal/src/analyzer/program.ts index 2ed39faaad..03037efdff 100644 --- a/packages/pyright-internal/src/analyzer/program.ts +++ b/packages/pyright-internal/src/analyzer/program.ts @@ -50,6 +50,7 @@ import { createTypeEvaluatorWithTracker } from './typeEvaluatorWithTracker'; import { PrintTypeFlags } from './typePrinter'; import { TypeStubWriter } from './typeStubWriter'; import { Type } from './types'; +import { RequiringAnalysisCount } from './analysis'; const _maxImportDepth = 256; @@ -521,22 +522,29 @@ export class Program { return this._sourceFileList.filter((s) => s.isOpenByClient); } - getFilesToAnalyzeCount() { - let sourceFileCount = 0; + getFilesToAnalyzeCount(): RequiringAnalysisCount { + let filesToAnalyzeCount = 0; + let cellsToAnalyzeCount = 0; if (this._disableChecker) { - return sourceFileCount; + return { files: 0, cells: 0 }; } this._sourceFileList.forEach((fileInfo) => { - if (fileInfo.sourceFile.isCheckingRequired()) { + const sourceFile = fileInfo.sourceFile; + if (sourceFile.isCheckingRequired()) { if (this._shouldCheckFile(fileInfo)) { - sourceFileCount++; + sourceFile.getIPythonMode() === IPythonMode.CellDocs + ? cellsToAnalyzeCount++ + : filesToAnalyzeCount++; } } }); - return sourceFileCount; + return { + files: filesToAnalyzeCount, + cells: cellsToAnalyzeCount, + }; } isCheckingOnlyOpenFiles() { @@ -1505,7 +1513,7 @@ export class Program { assert(!this._sourceFileMap.has(fileUri.key)); // We should never have an empty URI for a source file. - assert(!fileInfo.sourceFile.getUri().isEmpty()); + assert(!fileUri.isEmpty()); this._sourceFileList.push(fileInfo); this._sourceFileMap.set(fileUri.key, fileInfo); diff --git a/packages/pyright-internal/src/analyzer/service.ts b/packages/pyright-internal/src/analyzer/service.ts index 637e3ebc2a..34cc8ecd87 100644 --- a/packages/pyright-internal/src/analyzer/service.ts +++ b/packages/pyright-internal/src/analyzer/service.ts @@ -55,7 +55,7 @@ import { findPythonSearchPaths } from './pythonPathUtils'; import { IPythonMode } from './sourceFile'; import { githubRepo } from '../constants'; -export const configFileNames = ['pyrightconfig.json']; +export const configFileName = 'pyrightconfig.json'; export const pyprojectTomlName = 'pyproject.toml'; // How long since the last user activity should we wait until running @@ -85,6 +85,11 @@ export interface AnalyzerServiceOptions { fileSystem?: FileSystem; } +interface ConfigFileContents { + configFileDirUri: Uri; + configFileJsonObj: object; +} + // Hold uniqueId for this service. It can be used to distinguish each service later. let _nextServiceId = 1; @@ -104,7 +109,8 @@ export class AnalyzerService { private _sourceFileWatcher: FileWatcher | undefined; private _reloadConfigTimer: any; private _libraryReanalysisTimer: any; - private _configFileUri: Uri | undefined; + private _primaryConfigFileUri: Uri | undefined; + private _extendedConfigFileUris: Uri[] = []; private _configFileWatcher: FileWatcher | undefined; private _libraryFileWatcher: FileWatcher | undefined; private _librarySearchUrisToWatch: Uri[] | undefined; @@ -535,11 +541,12 @@ export class AnalyzerService { ? Uri.file(commandLineOptions.configFilePath, this.serviceProvider, /* checkRelative */ true) : projectRoot.resolvePaths(commandLineOptions.configFilePath) ); + if (!this.fs.existsSync(configFilePath)) { this._console.info(`Configuration file not found at ${configFilePath.toUserVisibleString()}.`); configFilePath = projectRoot; } else { - if (configFilePath.lastExtension.endsWith('.json')) { + if (configFilePath.lastExtension.endsWith('.json') || configFilePath.lastExtension.endsWith('.toml')) { projectRoot = configFilePath.getDirectory(); } else { projectRoot = configFilePath; @@ -585,7 +592,11 @@ export class AnalyzerService { } } - const configOptions = new BasedConfigOptions(projectRoot, this._typeCheckingMode); + const configOptions = new BasedConfigOptions(projectRoot); + configOptions.initializeTypeCheckingMode( + this._typeCheckingMode, + commandLineOptions.diagnosticSeverityOverrides + ); const defaultExcludes = ['**/node_modules', '**/__pycache__', '**/.*']; if (commandLineOptions.pythonPath) { @@ -649,27 +660,21 @@ export class AnalyzerService { } } - this._configFileUri = configFilePath || pyprojectFilePath; - configOptions.disableTaggedHints = !!commandLineOptions.disableTaggedHints; - // If we found a config file, parse it to compute the effective options. - let configJsonObj: object | undefined; - if (configFilePath) { - this._console.info(`Loading configuration file at ${configFilePath.toUserVisibleString()}`); - configJsonObj = this._parseJsonConfigFile(configFilePath); - } else if (pyprojectFilePath) { - this._console.info(`Loading pyproject.toml file at ${pyprojectFilePath.toUserVisibleString()}`); - configJsonObj = this._parsePyprojectTomlFile(pyprojectFilePath); - } - - if (configJsonObj) { - const errors = configOptions.initializeFromJson( - configJsonObj, - this._typeCheckingMode, - this.serviceProvider, - host, - commandLineOptions + configOptions.initializeTypeCheckingMode(commandLineOptions.typeCheckingMode ?? 'standard'); + + const configs = this._getExtendedConfigurations(configFilePath ?? pyprojectFilePath); + + if (configs) { + const errors = configs.flatMap((config) => + configOptions.initializeFromJson( + config.configFileJsonObj, + config.configFileDirUri, + this.serviceProvider, + host, + commandLineOptions + ) ); if (errors.length > 0) { for (const error of errors) { @@ -678,7 +683,7 @@ export class AnalyzerService { this._reportConfigParseError(); } - const configFileDir = this._configFileUri!.getDirectory(); + const configFileDir = this._primaryConfigFileUri!.getDirectory(); // If no include paths were provided, assume that all files within // the project should be included. @@ -867,6 +872,61 @@ export class AnalyzerService { return configOptions; } + // Loads the config JSON object from the specified config file along with any + // chained config files specified in the "extends" property (recursively). + private _getExtendedConfigurations(primaryConfigFileUri: Uri | undefined): ConfigFileContents[] | undefined { + this._primaryConfigFileUri = primaryConfigFileUri; + this._extendedConfigFileUris = []; + + if (!primaryConfigFileUri) { + return undefined; + } + + let curConfigFileUri = primaryConfigFileUri; + + const configJsonObjs: ConfigFileContents[] = []; + + while (true) { + this._extendedConfigFileUris.push(curConfigFileUri); + + let configFileJsonObj: object | undefined; + + // Is this a TOML or JSON file? + if (curConfigFileUri.lastExtension.endsWith('.toml')) { + this._console.info(`Loading pyproject.toml file at ${curConfigFileUri.toUserVisibleString()}`); + configFileJsonObj = this._parsePyprojectTomlFile(curConfigFileUri); + } else { + this._console.info(`Loading configuration file at ${curConfigFileUri.toUserVisibleString()}`); + configFileJsonObj = this._parseJsonConfigFile(curConfigFileUri); + } + + if (!configFileJsonObj) { + break; + } + + // Push onto the start of the array so base configs are processed first. + configJsonObjs.unshift({ configFileJsonObj, configFileDirUri: curConfigFileUri.getDirectory() }); + + const baseConfigUri = ConfigOptions.resolveExtends(configFileJsonObj, curConfigFileUri.getDirectory()); + if (!baseConfigUri) { + break; + } + + // Check for circular references. + if (this._extendedConfigFileUris.some((uri) => uri.equals(baseConfigUri))) { + this._console.error( + `Circular reference in configuration file "extends" setting: ${curConfigFileUri.toUserVisibleString()} ` + + `extends ${baseConfigUri.toUserVisibleString()}` + ); + break; + } + + curConfigFileUri = baseConfigUri; + } + + return configJsonObjs; + } + private _getTypeStubFolder() { const stubPath = this._configOptions.stubPath ?? @@ -921,12 +981,11 @@ export class AnalyzerService { } private _findConfigFile(searchPath: Uri): Uri | undefined { - for (const name of configFileNames) { - const fileName = searchPath.resolvePaths(name); - if (this.fs.existsSync(fileName)) { - return this.fs.realCasePath(fileName); - } + const fileName = searchPath.resolvePaths(configFileName); + if (this.fs.existsSync(fileName)) { + return this.fs.realCasePath(fileName); } + return undefined; } @@ -1103,7 +1162,7 @@ export class AnalyzerService { this._typeStubTargetUri = rootPackagePath.getDirectory(); } - if (!finalResolvedPath) { + if (finalResolvedPath.isEmpty()) { this._typeStubTargetIsSingleFile = false; } else { filesToImport.push(finalResolvedPath); @@ -1590,8 +1649,8 @@ export class AnalyzerService { return; } - if (this._configFileUri) { - this._configFileWatcher = this.fs.createFileSystemWatcher([this._configFileUri], (event) => { + if (this._primaryConfigFileUri) { + this._configFileWatcher = this.fs.createFileSystemWatcher(this._extendedConfigFileUris, (event) => { if (this._verboseOutput) { this._console.info(`Received fs event '${event}' for config file`); } @@ -1605,7 +1664,7 @@ export class AnalyzerService { if (event === 'add' || event === 'change') { const fileName = getFileName(path); - if (fileName && configFileNames.some((name) => name === fileName)) { + if (fileName === configFileName) { if (this._verboseOutput) { this._console.info(`Received fs event '${event}' for config file`); } @@ -1641,8 +1700,8 @@ export class AnalyzerService { private _reloadConfigFile() { this._updateConfigFileWatcher(); - if (this._configFileUri) { - this._console.info(`Reloading configuration file at ${this._configFileUri.toUserVisibleString()}`); + if (this._primaryConfigFileUri) { + this._console.info(`Reloading configuration file at ${this._primaryConfigFileUri.toUserVisibleString()}`); const host = this._backgroundAnalysisProgram.host; @@ -1747,7 +1806,7 @@ export class AnalyzerService { this._onCompletionCallback({ diagnostics: [], filesInProgram: 0, - filesRequiringAnalysis: 0, + requiringAnalysisCount: { files: 0, cells: 0 }, checkingOnlyOpenFiles: true, fatalErrorOccurred: false, configParseErrorOccurred: true, diff --git a/packages/pyright-internal/src/analyzer/sourceFile.ts b/packages/pyright-internal/src/analyzer/sourceFile.ts index 212cbef016..ecb4a5d7f5 100644 --- a/packages/pyright-internal/src/analyzer/sourceFile.ts +++ b/packages/pyright-internal/src/analyzer/sourceFile.ts @@ -12,7 +12,7 @@ import { isMainThread } from 'worker_threads'; import { OperationCanceledException } from '../common/cancellationUtils'; import { appendArray } from '../common/collectionUtils'; import { ConfigOptions, ExecutionEnvironment, getBasicDiagnosticRuleSet } from '../common/configOptions'; -import { ConsoleInterface, StandardConsole } from '../common/console'; +import { ConsoleInterface, LogLevel, StandardConsole } from '../common/console'; import { assert } from '../common/debug'; import { Diagnostic, DiagnosticCategory, TaskListToken, convertLevelToCategory } from '../common/diagnostic'; import { DiagnosticRule } from '../common/diagnosticRules'; @@ -95,7 +95,6 @@ class WriteableData { // the binder information hanging from it? parseTreeNeedsCleaning = false; - parserOutput: ParserOutput | undefined; parsedFileContents: string | undefined; tokenizerLines: TextRangeCollection | undefined; tokenizerOutput: TokenizerOutput | undefined; @@ -142,6 +141,30 @@ class WriteableData { // True if the file appears to have been deleted. isFileDeleted = false; + private _lastCallstack: string | undefined; + private _parserOutput: ParserOutput | undefined; + + private readonly _consoleWithLevel?: ConsoleInterface & { level: LogLevel }; + + constructor(console: ConsoleInterface) { + if (ConsoleInterface.hasLevel(console)) { + this._consoleWithLevel = console; + } + } + + get parserOutput() { + return this._parserOutput; + } + + set parserOutput(value: ParserOutput | undefined) { + this._lastCallstack = + this._consoleWithLevel?.level === LogLevel.Log && value === undefined && this._parserOutput !== undefined + ? new Error().stack + : undefined; + + this._parserOutput = value; + } + debugPrint() { return `WritableData: diagnosticVersion=${this.diagnosticVersion}, @@ -171,7 +194,8 @@ class WriteableData { pyrightIgnoreLines=${this.pyrightIgnoreLines?.size}, checkTime=${this.checkTime}, clientDocumentContents=${this.clientDocumentContents?.length}, - parseResults=${this.parserOutput?.parseTree.length}`; + parseResults=${this.parserOutput?.parseTree.length}, + parseResultsDropCallstack=${this._lastCallstack}`; } } @@ -180,9 +204,6 @@ export interface SourceFileEditMode { } export class SourceFile { - // Data that changes when the source file changes. - private _writableData = new WriteableData(); - // Console interface to use for debugging. private _console: ConsoleInterface; @@ -232,6 +253,10 @@ export class SourceFile { private _ipythonMode = IPythonMode.None; private _logTracker: LogTracker; private _preEditData: WriteableData | undefined; + + // Data that changes when the source file changes. + private _writableData: WriteableData; + readonly fileSystem: FileSystem; constructor( @@ -247,6 +272,8 @@ export class SourceFile { ) { this.fileSystem = serviceProvider.get(ServiceKeys.fs); this._console = console || new StandardConsole(); + this._writableData = new WriteableData(this._console); + this._editMode = editMode; this._uri = uri; this._moduleName = moduleName; @@ -864,7 +891,7 @@ export class SourceFile { sourceMapper: SourceMapper, dependentFiles?: ParserOutput[] ) { - assert(!this.isParseRequired(), 'Check called before parsing'); + assert(!this.isParseRequired(), `Check called before parsing: state=${this._writableData.debugPrint()}`); assert(!this.isBindingRequired(), `Check called before binding: state=${this._writableData.debugPrint()}`); assert(!this._writableData.isBindingInProgress, 'Check called while binding in progress'); assert(this.isCheckingRequired(), 'Check called unnecessarily'); @@ -1233,7 +1260,7 @@ export class SourceFile { this._preEditData = this._writableData; // Recreate all the writable data from scratch. - this._writableData = new WriteableData(); + this._writableData = new WriteableData(this._console); } // Get all task list diagnostics for the current file and add them diff --git a/packages/pyright-internal/src/analyzer/typeDocStringUtils.ts b/packages/pyright-internal/src/analyzer/typeDocStringUtils.ts index 972f9b3db6..e6b1402a5d 100644 --- a/packages/pyright-internal/src/analyzer/typeDocStringUtils.ts +++ b/packages/pyright-internal/src/analyzer/typeDocStringUtils.ts @@ -12,11 +12,12 @@ import { ClassDeclaration, Declaration, DeclarationBase, - DeclarationType, FunctionDeclaration, isClassDeclaration, isFunctionDeclaration, + isSpecialBuiltInClassDeclaration, isVariableDeclaration, + SpecialBuiltInClassDeclaration, VariableDeclaration, } from '../analyzer/declaration'; import * as ParseTreeUtils from '../analyzer/parseTreeUtils'; @@ -227,9 +228,9 @@ export function getClassDocString( sourceMapper: SourceMapper ) { let docString = classType.details.docString; - if (!docString && resolvedDecl && isClassDeclaration(resolvedDecl)) { - docString = _getFunctionOrClassDeclsDocString([resolvedDecl]); - if (!docString && resolvedDecl && isStubFile(resolvedDecl.uri) && resolvedDecl.type === DeclarationType.Class) { + if (!docString && resolvedDecl && _isAnyClassDeclaration(resolvedDecl)) { + docString = isClassDeclaration(resolvedDecl) ? _getFunctionOrClassDeclsDocString([resolvedDecl]) : undefined; + if (!docString && resolvedDecl && isStubFile(resolvedDecl.uri)) { for (const implDecl of sourceMapper.findDeclarations(resolvedDecl)) { if (isVariableDeclaration(implDecl) && !!implDecl.docString) { docString = implDecl.docString; @@ -386,3 +387,7 @@ function _getFunctionOrClassDeclsDocString(decls: FunctionDeclaration[] | ClassD return undefined; } + +function _isAnyClassDeclaration(decl: Declaration): decl is ClassDeclaration | SpecialBuiltInClassDeclaration { + return isClassDeclaration(decl) || isSpecialBuiltInClassDeclaration(decl); +} diff --git a/packages/pyright-internal/src/analyzer/typeEvaluator.ts b/packages/pyright-internal/src/analyzer/typeEvaluator.ts index 75cb811832..43e47bf7fe 100644 --- a/packages/pyright-internal/src/analyzer/typeEvaluator.ts +++ b/packages/pyright-internal/src/analyzer/typeEvaluator.ts @@ -116,6 +116,7 @@ import { FunctionDeclaration, ModuleLoaderActions, SpecialBuiltInClassDeclaration, + VariableDeclaration, } from './declaration'; import { ResolvedAliasInfo, @@ -148,8 +149,8 @@ import { getTypeOfUnaryOperation, } from './operations'; import { + ParameterKind, ParameterListDetails, - ParameterSource, VirtualParameterDetails, getParameterListDetails, isParamSpecArgsArgument, @@ -1054,9 +1055,85 @@ export function createTypeEvaluator( // at that point. initializedBasicTypes(node); + let typeResult = getTypeOfExpressionCore(node, flags, inferenceContext, signatureTracker); + + // Should we disable type promotions for bytes? + if ( + isInstantiableClass(typeResult.type) && + typeResult.type.includePromotions && + !typeResult.type.includeSubclasses && + ClassType.isBuiltIn(typeResult.type, 'bytes') + ) { + if (AnalyzerNodeInfo.getFileInfo(node).diagnosticRuleSet.disableBytesTypePromotions) { + typeResult = { + ...typeResult, + type: ClassType.cloneRemoveTypePromotions(typeResult.type), + }; + } + } + + // Don't allow speculative caching for assignment expressions because + // the target name node won't have a corresponding type cached speculatively. + const allowSpeculativeCaching = node.nodeType !== ParseNodeType.AssignmentExpression; + + writeTypeCache(node, typeResult, flags, inferenceContext, allowSpeculativeCaching); + + // If there was an expected type, make sure that the result type is compatible. + if ( + inferenceContext && + !isAnyOrUnknown(inferenceContext.expectedType) && + !isNever(inferenceContext.expectedType) + ) { + expectedTypeCache.set(node.id, inferenceContext.expectedType); + + // If this is a generic function and there is a signature tracker, + // make sure the signature is unique. + if (signatureTracker && isFunction(typeResult.type)) { + typeResult.type = ensureFunctionSignaturesAreUnique(typeResult.type, signatureTracker, node.start); + } + + if (!typeResult.isIncomplete && !typeResult.expectedTypeDiagAddendum) { + const diag = new DiagnosticAddendum(); + + // Make sure the resulting type is assignable to the expected type. + if ( + !assignType( + inferenceContext.expectedType, + typeResult.type, + diag, + /* destTypeVarContext */ undefined, + /* srcTypeVarContext */ undefined, + AssignTypeFlags.IgnoreTypeVarScope + ) + ) { + typeResult.typeErrors = true; + typeResult.expectedTypeDiagAddendum = diag; + diag.addTextRange(node); + } + } + } + + if (printExpressionTypes) { + printExpressionSpaceCount--; + console.log( + `${getPrintExpressionTypesSpaces()}${ParseTreeUtils.printExpression(node)} (${getLineNum( + node + )}): Post ${printType(typeResult.type)}${typeResult.isIncomplete ? ' Incomplete' : ''}` + ); + } + + return typeResult; + } + + // This is a helper function that implements the core of getTypeOfExpression. + function getTypeOfExpressionCore( + node: ExpressionNode, + flags = EvaluatorFlags.None, + inferenceContext?: InferenceContext, + signatureTracker?: UniqueSignatureTracker + ): TypeResult { let typeResult: TypeResult | undefined; let expectingInstantiable = (flags & EvaluatorFlags.ExpectingInstantiableType) !== 0; - let allowSpeculativeCaching = true; switch (node.nodeType) { case ParseNodeType.Name: { @@ -1158,7 +1235,7 @@ export function createTypeEvaluator( } case ParseNodeType.ListComprehension: { - typeResult = getTypeOfListComprehension(node, inferenceContext); + typeResult = getTypeOfListComprehension(node, flags, inferenceContext); break; } @@ -1198,10 +1275,6 @@ export function createTypeEvaluator( node.rightExpression, /* ignoreEmptyContainers */ true ); - - // Don't allow speculative caching for assignment expressions because - // the target name node won't have a corresponding type cached speculatively. - allowSpeculativeCaching = false; break; } @@ -1265,67 +1338,6 @@ export function createTypeEvaluator( validateTypeIsInstantiable(typeResult, flags, node); } - // Should we disable type promotions for bytes? - if ( - isInstantiableClass(typeResult.type) && - typeResult.type.includePromotions && - !typeResult.type.includeSubclasses && - ClassType.isBuiltIn(typeResult.type, 'bytes') - ) { - if (AnalyzerNodeInfo.getFileInfo(node).diagnosticRuleSet.disableBytesTypePromotions) { - typeResult = { - ...typeResult, - type: ClassType.cloneRemoveTypePromotions(typeResult.type), - }; - } - } - - writeTypeCache(node, typeResult, flags, inferenceContext, allowSpeculativeCaching); - - // If there was an expected type, make sure that the result type is compatible. - if ( - inferenceContext && - !isAnyOrUnknown(inferenceContext.expectedType) && - !isNever(inferenceContext.expectedType) - ) { - expectedTypeCache.set(node.id, inferenceContext.expectedType); - - // If this is a generic function and there is a signature tracker, - // make sure the signature is unique. - if (signatureTracker && isFunction(typeResult.type)) { - typeResult.type = ensureFunctionSignaturesAreUnique(typeResult.type, signatureTracker, node.start); - } - - if (!typeResult.isIncomplete && !typeResult.expectedTypeDiagAddendum) { - const diag = new DiagnosticAddendum(); - - // Make sure the resulting type is assignable to the expected type. - if ( - !assignType( - inferenceContext.expectedType, - typeResult.type, - diag, - /* destTypeVarContext */ undefined, - /* srcTypeVarContext */ undefined, - AssignTypeFlags.IgnoreTypeVarScope - ) - ) { - typeResult.typeErrors = true; - typeResult.expectedTypeDiagAddendum = diag; - diag.addTextRange(node); - } - } - } - - if (printExpressionTypes) { - printExpressionSpaceCount--; - console.log( - `${getPrintExpressionTypesSpaces()}${ParseTreeUtils.printExpression(node)} (${getLineNum( - node - )}): Post ${printType(typeResult.type)}${typeResult.isIncomplete ? ' Incomplete' : ''}` - ); - } - return typeResult; } @@ -5970,7 +5982,12 @@ export function createTypeEvaluator( // If a Final instance variable is declared in the class body but is // being assigned within an __init__ method, it's allowed. const enclosingFunctionNode = ParseTreeUtils.getEnclosingFunction(errorNode); - if (!enclosingFunctionNode || enclosingFunctionNode.name.value !== '__init__') { + if ( + !enclosingFunctionNode || + enclosingFunctionNode.name.value !== '__init__' || + (finalVarTypeDecl as VariableDeclaration).inferredTypeSource !== undefined || + isInstantiableClass(classType) + ) { diag?.addMessage(LocMessage.finalReassigned().format({ name: memberName })); isDescriptorError = true; } @@ -7931,6 +7948,12 @@ export function createTypeEvaluator( return { type: makeTupleObject([]), isEmptyTupleShorthand: true }; } + flags &= ~( + EvaluatorFlags.ExpectingTypeAnnotation | + EvaluatorFlags.EvaluateStringLiteralAsType | + EvaluatorFlags.ExpectingInstantiableType + ); + // If the expected type is a union, recursively call for each of the subtypes // to find one that matches. let effectiveExpectedType = inferenceContext?.expectedType; @@ -7950,6 +7973,7 @@ export function createTypeEvaluator( const subtypeResult = useSpeculativeMode(node, () => { return getTypeOfTupleWithContext( node, + flags, makeInferenceContext(subtype), /* signatureTracker */ undefined ); @@ -7970,6 +7994,7 @@ export function createTypeEvaluator( if (effectiveExpectedType) { const result = getTypeOfTupleWithContext( node, + flags, makeInferenceContext(effectiveExpectedType), signatureTracker ); @@ -7981,7 +8006,7 @@ export function createTypeEvaluator( expectedTypeDiagAddendum = result?.expectedTypeDiagAddendum; } - const typeResult = getTypeOfTupleInferred(node); + const typeResult = getTypeOfTupleInferred(node, flags); // If there was an expected type of Any, replace the resulting type // with Any rather than return a type with unknowns. @@ -7994,6 +8019,7 @@ export function createTypeEvaluator( function getTypeOfTupleWithContext( node: TupleNode, + flags: EvaluatorFlags, inferenceContext: InferenceContext, signatureTracker: UniqueSignatureTracker | undefined ): TypeResult | undefined { @@ -8052,7 +8078,7 @@ export function createTypeEvaluator( const entryTypeResults = node.expressions.map((expr, index) => getTypeOfExpression( expr, - /* flags */ undefined, + flags | EvaluatorFlags.StripLiteralTypeForTuple, makeInferenceContext( index < expectedTypes.length ? expectedTypes[index] : undefined, inferenceContext.isTypeIncomplete @@ -8061,7 +8087,7 @@ export function createTypeEvaluator( ) ); const isIncomplete = entryTypeResults.some((result) => result.isIncomplete); - const type = makeTupleObject(buildTupleTypesList(entryTypeResults)); + const type = makeTupleObject(buildTupleTypesList(entryTypeResults, /* stripLiterals */ false)); // Copy any expected type diag addenda for precision error reporting. let expectedTypeDiagAddendum: DiagnosticAddendum | undefined; @@ -8077,11 +8103,15 @@ export function createTypeEvaluator( return { type, expectedTypeDiagAddendum, isIncomplete }; } - function getTypeOfTupleInferred(node: TupleNode): TypeResult { - const entryTypeResults = node.expressions.map((expr) => getTypeOfExpression(expr)); + function getTypeOfTupleInferred(node: TupleNode, flags: EvaluatorFlags): TypeResult { + const entryTypeResults = node.expressions.map((expr) => + getTypeOfExpression(expr, flags | EvaluatorFlags.StripLiteralTypeForTuple) + ); const isIncomplete = entryTypeResults.some((result) => result.isIncomplete); - const type = makeTupleObject(buildTupleTypesList(entryTypeResults)); + const type = makeTupleObject( + buildTupleTypesList(entryTypeResults, (flags & EvaluatorFlags.StripLiteralTypeForTuple) !== 0) + ); if (isIncomplete) { if (getContainerDepth(type) > maxInferredContainerDepth) { @@ -8092,7 +8122,7 @@ export function createTypeEvaluator( return { type, isIncomplete }; } - function buildTupleTypesList(entryTypeResults: TypeResult[]): TupleTypeArgument[] { + function buildTupleTypesList(entryTypeResults: TypeResult[], stripLiterals: boolean): TupleTypeArgument[] { const entryTypes: TupleTypeArgument[] = []; for (const typeResult of entryTypeResults) { @@ -8122,7 +8152,8 @@ export function createTypeEvaluator( } else if (isNever(typeResult.type) && typeResult.isIncomplete && !typeResult.unpackedType) { entryTypes.push({ type: UnknownType.create(/* isIncomplete */ true), isUnbounded: false }); } else { - entryTypes.push({ type: typeResult.type, isUnbounded: !!typeResult.unpackedType }); + const entryType = stripLiterals ? stripLiteralValue(typeResult.type) : typeResult.type; + entryTypes.push({ type: entryType, isUnbounded: !!typeResult.unpackedType }); } } @@ -10479,7 +10510,7 @@ export function createTypeEvaluator( paramMap.set(param.name, { argsNeeded: param.category === ParameterCategory.Simple && !param.hasDefault ? 1 : 0, argsReceived: 0, - isPositionalOnly: paramInfo.source === ParameterSource.PositionOnly, + isPositionalOnly: paramInfo.kind === ParameterKind.Positional, }); } }); @@ -13578,7 +13609,7 @@ export function createTypeEvaluator( } const subtypeResult = useSpeculativeMode(node, () => { - return getTypeOfDictionaryWithContext(node, makeInferenceContext(subtype)); + return getTypeOfDictionaryWithContext(node, flags, makeInferenceContext(subtype)); }); if (subtypeResult && assignType(subtype, subtypeResult.type)) { @@ -13601,6 +13632,7 @@ export function createTypeEvaluator( expectedTypeDiagAddendum = new DiagnosticAddendum(); const result = getTypeOfDictionaryWithContext( node, + flags, makeInferenceContext(effectiveExpectedType), expectedTypeDiagAddendum ); @@ -13609,12 +13641,13 @@ export function createTypeEvaluator( } } - const result = getTypeOfDictionaryInferred(node, /* hasExpectedType */ !!inferenceContext); + const result = getTypeOfDictionaryInferred(node, flags, /* hasExpectedType */ !!inferenceContext); return { ...result, expectedTypeDiagAddendum }; } function getTypeOfDictionaryWithContext( node: DictionaryNode, + flags: EvaluatorFlags, inferenceContext: InferenceContext, expectedDiagAddendum?: DiagnosticAddendum ): TypeResult | undefined { @@ -13641,6 +13674,7 @@ export function createTypeEvaluator( // Infer the key and value types if possible. const keyValueTypeResult = getKeyAndValueTypesFromDictionary( node, + flags, keyTypes, valueTypes, /* forceStrictInference */ true, @@ -13732,6 +13766,7 @@ export function createTypeEvaluator( // Infer the key and value types if possible. const keyValueResult = getKeyAndValueTypesFromDictionary( node, + flags, keyTypes, valueTypes, /* forceStrictInference */ true, @@ -13770,7 +13805,11 @@ export function createTypeEvaluator( // Attempts to infer the type of a dictionary statement. If hasExpectedType // is true, strict inference is used for the subexpressions. - function getTypeOfDictionaryInferred(node: DictionaryNode, hasExpectedType: boolean): TypeResult { + function getTypeOfDictionaryInferred( + node: DictionaryNode, + flags: EvaluatorFlags, + hasExpectedType: boolean + ): TypeResult { const fallbackType = hasExpectedType ? AnyType.create() : UnknownType.create(); let keyType: Type = fallbackType; let valueType: Type = fallbackType; @@ -13785,6 +13824,7 @@ export function createTypeEvaluator( // Infer the key and value types if possible. const keyValueResult = getKeyAndValueTypesFromDictionary( node, + flags, keyTypeResults, valueTypeResults, /* forceStrictInference */ hasExpectedType, @@ -13846,6 +13886,7 @@ export function createTypeEvaluator( function getKeyAndValueTypesFromDictionary( node: DictionaryNode, + flags: EvaluatorFlags, keyTypes: TypeResultWithNode[], valueTypes: TypeResultWithNode[], forceStrictInference: boolean, @@ -13858,6 +13899,16 @@ export function createTypeEvaluator( let isIncomplete = false; let typeErrors = false; + // Mask out some of the flags that are not applicable for a dictionary key + // even if it appears within an inlined TypedDict annotation. + const keyFlags = + flags & + ~( + EvaluatorFlags.ExpectingTypeAnnotation | + EvaluatorFlags.EvaluateStringLiteralAsType | + EvaluatorFlags.ExpectingInstantiableType + ); + // Infer the key and value types if possible. node.entries.forEach((entryNode, index) => { let addUnknown = true; @@ -13865,7 +13916,7 @@ export function createTypeEvaluator( if (entryNode.nodeType === ParseNodeType.DictionaryKeyEntry) { const keyTypeResult = getTypeOfExpression( entryNode.keyExpression, - /* flags */ undefined, + keyFlags | EvaluatorFlags.StripLiteralTypeForTuple, makeInferenceContext( expectedKeyType ?? (forceStrictInference ? NeverType.createNever() : undefined) ) @@ -13905,7 +13956,7 @@ export function createTypeEvaluator( entryInferenceContext = makeInferenceContext(effectiveValueType); valueTypeResult = getTypeOfExpression( entryNode.valueExpression, - /* flags */ undefined, + flags | EvaluatorFlags.StripLiteralTypeForTuple, entryInferenceContext ); } else { @@ -13914,7 +13965,7 @@ export function createTypeEvaluator( entryInferenceContext = makeInferenceContext(effectiveValueType); valueTypeResult = getTypeOfExpression( entryNode.valueExpression, - /* flags */ undefined, + flags | EvaluatorFlags.StripLiteralTypeForTuple, entryInferenceContext ); } @@ -13977,7 +14028,7 @@ export function createTypeEvaluator( const entryInferenceContext = makeInferenceContext(expectedType); let unexpandedTypeResult = getTypeOfExpression( entryNode.expandExpression, - /* flags */ undefined, + flags | EvaluatorFlags.StripLiteralTypeForTuple, entryInferenceContext ); @@ -14077,6 +14128,7 @@ export function createTypeEvaluator( } else if (entryNode.nodeType === ParseNodeType.ListComprehension) { const dictEntryTypeResult = getElementTypeFromListComprehension( entryNode, + flags, expectedValueType, expectedKeyType ); @@ -14128,6 +14180,12 @@ export function createTypeEvaluator( addDiagnostic(DiagnosticRule.reportInvalidTypeForm, LocMessage.listInAnnotation() + diag.getString(), node); } + flags &= ~( + EvaluatorFlags.ExpectingTypeAnnotation | + EvaluatorFlags.EvaluateStringLiteralAsType | + EvaluatorFlags.ExpectingInstantiableType + ); + // If the expected type is a union, recursively call for each of the subtypes // to find one that matches. let effectiveExpectedType = inferenceContext?.expectedType; @@ -14145,7 +14203,7 @@ export function createTypeEvaluator( } const subtypeResult = useSpeculativeMode(node, () => { - return getTypeOfListOrSetWithContext(node, makeInferenceContext(subtype)); + return getTypeOfListOrSetWithContext(node, flags, makeInferenceContext(subtype)); }); if (subtypeResult && assignType(subtype, subtypeResult.type)) { @@ -14165,7 +14223,7 @@ export function createTypeEvaluator( let expectedTypeDiagAddendum: DiagnosticAddendum | undefined; if (effectiveExpectedType) { - const result = getTypeOfListOrSetWithContext(node, makeInferenceContext(effectiveExpectedType)); + const result = getTypeOfListOrSetWithContext(node, flags, makeInferenceContext(effectiveExpectedType)); if (result && !result.typeErrors) { return result; } @@ -14173,7 +14231,11 @@ export function createTypeEvaluator( expectedTypeDiagAddendum = result?.expectedTypeDiagAddendum; } - const typeResult = getTypeOfListOrSetInferred(node, /* hasExpectedType */ inferenceContext !== undefined); + const typeResult = getTypeOfListOrSetInferred( + node, + flags, + /* hasExpectedType */ inferenceContext !== undefined + ); return { ...typeResult, expectedTypeDiagAddendum }; } @@ -14181,6 +14243,7 @@ export function createTypeEvaluator( // Returns undefined if that type cannot be honored. function getTypeOfListOrSetWithContext( node: ListNode | SetNode, + flags: EvaluatorFlags, inferenceContext: InferenceContext ): TypeResult | undefined { const builtInClassName = node.nodeType === ParseNodeType.List ? 'list' : 'set'; @@ -14205,11 +14268,11 @@ export function createTypeEvaluator( let entryTypeResult: TypeResult; if (entry.nodeType === ParseNodeType.ListComprehension) { - entryTypeResult = getElementTypeFromListComprehension(entry, expectedEntryType); + entryTypeResult = getElementTypeFromListComprehension(entry, flags, expectedEntryType); } else { entryTypeResult = getTypeOfExpression( entry, - /* flags */ undefined, + flags | EvaluatorFlags.StripLiteralTypeForTuple, makeInferenceContext(expectedEntryType) ); } @@ -14304,7 +14367,11 @@ export function createTypeEvaluator( } // Attempts to infer the type of a list or set statement with no "expected type". - function getTypeOfListOrSetInferred(node: ListNode | SetNode, hasExpectedType: boolean): TypeResult { + function getTypeOfListOrSetInferred( + node: ListNode | SetNode, + flags: EvaluatorFlags, + hasExpectedType: boolean + ): TypeResult { const builtInClassName = node.nodeType === ParseNodeType.List ? 'list' : 'set'; const verifyHashable = node.nodeType === ParseNodeType.Set; let isEmptyContainer = false; @@ -14316,9 +14383,9 @@ export function createTypeEvaluator( let entryTypeResult: TypeResult; if (entry.nodeType === ParseNodeType.ListComprehension && !entry.isGenerator) { - entryTypeResult = getElementTypeFromListComprehension(entry); + entryTypeResult = getElementTypeFromListComprehension(entry, flags); } else { - entryTypeResult = getTypeOfExpression(entry); + entryTypeResult = getTypeOfExpression(entry, flags | EvaluatorFlags.StripLiteralTypeForTuple); } if (entryTypeResult.isIncomplete) { @@ -14739,7 +14806,11 @@ export function createTypeEvaluator( return { type: functionType, isIncomplete, typeErrors }; } - function getTypeOfListComprehension(node: ListComprehensionNode, inferenceContext?: InferenceContext): TypeResult { + function getTypeOfListComprehension( + node: ListComprehensionNode, + flags: EvaluatorFlags, + inferenceContext?: InferenceContext + ): TypeResult { let isIncomplete = false; let typeErrors = false; @@ -14761,7 +14832,7 @@ export function createTypeEvaluator( const builtInIteratorType = getTypingType(node, isAsync ? 'AsyncGenerator' : 'Generator'); const expectedEntryType = getExpectedEntryTypeForIterable(node, builtInIteratorType, inferenceContext); - const elementTypeResult = getElementTypeFromListComprehension(node, expectedEntryType); + const elementTypeResult = getElementTypeFromListComprehension(node, flags, expectedEntryType); if (elementTypeResult.isIncomplete) { isIncomplete = true; } @@ -14871,6 +14942,7 @@ export function createTypeEvaluator( // as opposed to the entire list. function getElementTypeFromListComprehension( node: ListComprehensionNode, + flags: EvaluatorFlags, expectedValueOrElementType?: Type, expectedKeyType?: Type ): TypeResult { @@ -14889,7 +14961,7 @@ export function createTypeEvaluator( // Create a tuple with the key/value types. const keyTypeResult = getTypeOfExpression( node.expression.keyExpression, - /* flags */ undefined, + flags | EvaluatorFlags.StripLiteralTypeForTuple, makeInferenceContext(expectedKeyType) ); if (keyTypeResult.isIncomplete) { @@ -14905,7 +14977,7 @@ export function createTypeEvaluator( const valueTypeResult = getTypeOfExpression( node.expression.valueExpression, - /* flags */ undefined, + flags | EvaluatorFlags.StripLiteralTypeForTuple, makeInferenceContext(expectedValueOrElementType) ); if (valueTypeResult.isIncomplete) { @@ -14927,13 +14999,13 @@ export function createTypeEvaluator( // The parser should have reported an error in this case because it's not allowed. getTypeOfExpression( node.expression.expandExpression, - /* flags */ undefined, + flags | EvaluatorFlags.StripLiteralTypeForTuple, makeInferenceContext(expectedValueOrElementType) ); } else if (isExpressionNode(node)) { const exprTypeResult = getTypeOfExpression( node.expression as ExpressionNode, - /* flags */ undefined, + flags | EvaluatorFlags.StripLiteralTypeForTuple, makeInferenceContext(expectedValueOrElementType) ); if (exprTypeResult.isIncomplete) { @@ -25276,7 +25348,7 @@ export function createTypeEvaluator( let srcLastToPackIndex = srcDetails.params.findIndex((p, i) => { assert(destDetails.argsIndex !== undefined); - return i >= destDetails.argsIndex && p.source === ParameterSource.KeywordOnly; + return i >= destDetails.argsIndex && p.kind === ParameterKind.Keyword; }); if (srcLastToPackIndex < 0) { srcLastToPackIndex = srcDetails.params.length; @@ -25325,7 +25397,7 @@ export function createTypeEvaluator( }, type: srcPositionalsType, index: -1, - source: ParameterSource.PositionOnly, + kind: ParameterKind.Positional, }, ...srcDetails.params.slice( destDetails.argsIndex + srcPositionalsToPack.length, @@ -25343,16 +25415,14 @@ export function createTypeEvaluator( ); srcDetails.kwargsIndex = kwargsIndex >= 0 ? kwargsIndex : undefined; - const firstKeywordOnlyIndex = srcDetails.params.findIndex( - (param) => param.source === ParameterSource.KeywordOnly - ); + const firstKeywordOnlyIndex = srcDetails.params.findIndex((param) => param.kind === ParameterKind.Keyword); srcDetails.firstKeywordOnlyIndex = firstKeywordOnlyIndex >= 0 ? firstKeywordOnlyIndex : undefined; srcDetails.positionOnlyParamCount = Math.max( 0, srcDetails.params.findIndex( (p) => - p.source !== ParameterSource.PositionOnly || + p.kind !== ParameterKind.Positional || p.param.category !== ParameterCategory.Simple || p.param.hasDefault ) @@ -25418,13 +25488,13 @@ export function createTypeEvaluator( const destParamName = destParam.param.name ?? ''; const srcParamName = srcParam.param.name ?? ''; if (destParamName) { - const isDestPositionalOnly = destParam.source === ParameterSource.PositionOnly; + const isDestPositionalOnly = destParam.kind === ParameterKind.Positional; if ( !isDestPositionalOnly && destParam.param.category !== ParameterCategory.ArgsList && srcParam.param.category !== ParameterCategory.ArgsList ) { - if (srcParam.source === ParameterSource.PositionOnly) { + if (srcParam.kind === ParameterKind.Positional) { diag?.createAddendum().addMessage( LocAddendum.functionParamPositionOnly().format({ name: destParamName, @@ -25494,12 +25564,12 @@ export function createTypeEvaluator( canAssign = false; } } else if ( - destParam.source !== ParameterSource.PositionOnly && - srcParam.source === ParameterSource.PositionOnly && + destParam.kind !== ParameterKind.Positional && + srcParam.kind === ParameterKind.Positional && srcParamDetails.kwargsIndex === undefined && !srcParamDetails.params.some( (p) => - p.source === ParameterSource.KeywordOnly && + p.kind === ParameterKind.Keyword && p.param.category === ParameterCategory.Simple && p.param.name === destParam.param.name ) @@ -25527,75 +25597,83 @@ export function createTypeEvaluator( canAssign = false; } - if (destPositionalCount < srcPositionalCount) { - // If the dest type includes a ParamSpec, the additional parameters - // can be assigned to it, so no need to report an error here. - if (!targetIncludesParamSpec) { - const nonDefaultSrcParamCount = srcParamDetails.params.filter( - (p) => !!p.param.name && !p.param.hasDefault && p.param.category === ParameterCategory.Simple - ).length; + if (destPositionalCount < srcPositionalCount && !targetIncludesParamSpec) { + for (let i = destPositionalCount; i < srcPositionalCount; i++) { + // If the dest has an *args parameter, make sure it can accept the remaining + // positional arguments in the source. + if (destParamDetails.argsIndex !== undefined) { + const destArgsType = destParamDetails.params[destParamDetails.argsIndex].type; + const srcParamType = srcParamDetails.params[i].type; + if ( + !assignFunctionParameter( + destArgsType, + srcParamType, + i, + diag?.createAddendum(), + destTypeVarContext, + srcTypeVarContext, + flags, + recursionCount + ) + ) { + canAssign = false; + } - if (destParamDetails.argsIndex === undefined) { - if (destPositionalCount < nonDefaultSrcParamCount) { - if ( - destParamDetails.firstPositionOrKeywordIndex > 0 && - destParamDetails.firstPositionOrKeywordIndex < srcPositionalCount - ) { - diag?.createAddendum().addMessage( - LocAddendum.functionTooFewParams().format({ - expected: nonDefaultSrcParamCount, - received: destPositionalCount, - }) - ); - canAssign = false; - } - } else { - // Assign default arg values in case they are needed for - // populating TypeVar constraints. - for (let i = destParamDetails.firstPositionOrKeywordIndex; i < srcPositionalCount; i++) { - const paramInfo = srcParamDetails.params[i]; - const defaultArgType = paramInfo.defaultArgType ?? paramInfo.param.defaultType; + continue; + } - if ( - defaultArgType && - !assignType( - paramInfo.type, - defaultArgType, - diag?.createAddendum(), - srcTypeVarContext, - /* destTypeVarContext */ undefined, - flags, - recursionCount - ) - ) { - canAssign = false; - } - } - } - } else { - // Make sure the remaining positional arguments are of the - // correct type for the *args parameter. - const destArgsType = destParamDetails.params[destParamDetails.argsIndex].type; - if (!isAnyOrUnknown(destArgsType)) { - for (let paramIndex = destPositionalCount; paramIndex < srcPositionalCount; paramIndex++) { - const srcParamType = srcParamDetails.params[paramIndex].type; - if ( - !assignFunctionParameter( - destArgsType, - srcParamType, - paramIndex, - diag?.createAddendum(), - destTypeVarContext, - srcTypeVarContext, - flags, - recursionCount - ) - ) { - canAssign = false; - } - } + // If The source parameter has a default value, it is OK for the + // corresponding dest parameter to be missing. + const srcParam = srcParamDetails.params[i]; + + if (srcParam.param.hasDefault) { + // Assign default arg value in case it is needed for + // populating TypeVar constraints. + const paramInfo = srcParamDetails.params[i]; + const defaultArgType = paramInfo.defaultArgType ?? paramInfo.param.defaultType; + + if ( + defaultArgType && + !assignType( + paramInfo.type, + defaultArgType, + diag?.createAddendum(), + srcTypeVarContext, + /* destTypeVarContext */ undefined, + flags, + recursionCount + ) + ) { + canAssign = false; } + + continue; } + + // If the source parameter is also addressible by keyword, it is OK + // that there is no matching positional parameter in the dest. + if (srcParam.kind === ParameterKind.Standard) { + continue; + } + + // If the source parameter is a variadic, it is OK that there is no + // matching positional parameter in the dest. + if (srcParam.param.category === ParameterCategory.ArgsList) { + continue; + } + + const nonDefaultSrcParamCount = srcParamDetails.params.filter( + (p) => !!p.param.name && !p.param.hasDefault && p.param.category === ParameterCategory.Simple + ).length; + + diag?.createAddendum().addMessage( + LocAddendum.functionTooFewParams().format({ + expected: nonDefaultSrcParamCount, + received: destPositionalCount, + }) + ); + canAssign = false; + break; } } else if (srcPositionalCount < destPositionalCount) { if (srcParamDetails.argsIndex !== undefined) { @@ -25628,7 +25706,7 @@ export function createTypeEvaluator( } if ( - destParamDetails.params[paramIndex].source !== ParameterSource.PositionOnly && + destParamDetails.params[paramIndex].kind !== ParameterKind.Positional && srcParamDetails.kwargsIndex === undefined ) { diag?.addMessage( @@ -25742,7 +25820,7 @@ export function createTypeEvaluator( if ( srcParamInfo.param.name && srcParamInfo.param.category === ParameterCategory.Simple && - srcParamInfo.source !== ParameterSource.PositionOnly + srcParamInfo.kind !== ParameterKind.Positional ) { const destParamInfo = destParamMap.get(srcParamInfo.param.name); const paramDiag = diag?.createAddendum(); @@ -25750,7 +25828,7 @@ export function createTypeEvaluator( if (!destParamInfo) { if (destParamDetails.kwargsIndex === undefined && !srcParamInfo.param.hasDefault) { - if (paramDiag && srcParamDetails.firstKeywordOnlyIndex !== undefined) { + if (paramDiag) { paramDiag.addMessage( LocAddendum.namedParamMissingInDest().format({ name: srcParamInfo.param.name, @@ -26162,7 +26240,9 @@ export function createTypeEvaluator( if (isAnyOrUnknown(expectedTypeArgType) || isAnyOrUnknown(typeArg)) { replacedTypeArg = true; return expectedTypeArgType; - } else if (isClassInstance(expectedTypeArgType) && isClassInstance(typeArg)) { + } + + if (isClassInstance(expectedTypeArgType) && isClassInstance(typeArg)) { // Recursively replace Any in the type argument. const recursiveReplacement = replaceTypeArgsWithAny( node, @@ -26170,6 +26250,7 @@ export function createTypeEvaluator( typeArg, recursionCount ); + if (recursiveReplacement) { replacedTypeArg = true; return recursiveReplacement; @@ -26214,6 +26295,12 @@ export function createTypeEvaluator( // When a value is assigned to a variable with a declared type, // we may be able to narrow the type based on the assignment. function narrowTypeBasedOnAssignment(node: ExpressionNode, declaredType: Type, assignedType: Type): Type { + // TODO: The rules for narrowing types on assignment are defined in + // the typing spec. Pyright's current logic is currently not even internally + // consistent and probably not sound from a type theory perspective. It + // should be completely reworked once there has been a public discussion + // about the correct behavior. + const narrowedType = mapSubtypes(assignedType, (assignedSubtype) => { // Handle the special case where the assigned type is a literal type. // Some types include very large unions of literal types, and we don't @@ -26255,8 +26342,18 @@ export function createTypeEvaluator( } // If the declared type doesn't contain any `Any` but the assigned - // type does, stick with the declared type. - if (containsAnyRecursive(assignedSubtype) && !containsAnyRecursive(declaredSubtype)) { + // type does, stick with the declared type. We don't include unknowns + // in the assigned subtype check here so unknowns are preserved so + // reportUnknownVariableType assignment diagnostics are reported. + + // TODO - this is an inconsistency because Any and Unknown should + // always be treated the same for purposes of type narrowing. This + // should be revisited once the narrowing-on-assignment behavior + // is properly specified in the typing spec. + if ( + containsAnyRecursive(assignedSubtype, /* includeUnknown */ false) && + !containsAnyRecursive(declaredSubtype) + ) { return declaredSubtype; } @@ -26587,14 +26684,14 @@ export function createTypeEvaluator( if ( i >= baseParamDetails.positionOnlyParamCount && !isPrivateOrProtectedName(baseParam.name || '') && - baseParamDetails.params[i].source !== ParameterSource.PositionOnly && + baseParamDetails.params[i].kind !== ParameterKind.Positional && baseParam.category === ParameterCategory.Simple && enforceParamNames && baseParam.name !== overrideParam.name ) { if (overrideParam.category === ParameterCategory.Simple) { if (!baseParam.isNameSynthesized) { - if (overrideParamDetails.params[i].source === ParameterSource.PositionOnly) { + if (overrideParamDetails.params[i].kind === ParameterKind.Positional) { diag?.addMessage( LocAddendum.overrideParamNamePositionOnly().format({ index: i + 1, @@ -26617,10 +26714,7 @@ export function createTypeEvaluator( i < overrideParamDetails.positionOnlyParamCount && i >= baseParamDetails.positionOnlyParamCount ) { - if ( - !baseParam.isNameSynthesized && - baseParamDetails.params[i].source !== ParameterSource.PositionOnly - ) { + if (!baseParam.isNameSynthesized && baseParamDetails.params[i].kind !== ParameterKind.Positional) { diag?.addMessage( LocAddendum.overrideParamNamePositionOnly().format({ index: i + 1, @@ -26680,7 +26774,7 @@ export function createTypeEvaluator( const baseParam = baseParamDetails.params[i]; if ( - baseParam.source === ParameterSource.PositionOrKeyword && + baseParam.kind === ParameterKind.Standard && baseParam.param.category === ParameterCategory.Simple ) { diag?.addMessage( @@ -26732,13 +26826,11 @@ export function createTypeEvaluator( // Now check any keyword-only parameters. const baseKwOnlyParams = baseParamDetails.params.filter( (paramInfo) => - paramInfo.source === ParameterSource.KeywordOnly && - paramInfo.param.category === ParameterCategory.Simple + paramInfo.kind === ParameterKind.Keyword && paramInfo.param.category === ParameterCategory.Simple ); const overrideKwOnlyParams = overrideParamDetails.params.filter( (paramInfo) => - paramInfo.source === ParameterSource.KeywordOnly && - paramInfo.param.category === ParameterCategory.Simple + paramInfo.kind === ParameterKind.Keyword && paramInfo.param.category === ParameterCategory.Simple ); baseKwOnlyParams.forEach((paramInfo) => { diff --git a/packages/pyright-internal/src/analyzer/typeEvaluatorTypes.ts b/packages/pyright-internal/src/analyzer/typeEvaluatorTypes.ts index da0d5bd824..62b9d44218 100644 --- a/packages/pyright-internal/src/analyzer/typeEvaluatorTypes.ts +++ b/packages/pyright-internal/src/analyzer/typeEvaluatorTypes.ts @@ -154,6 +154,10 @@ export const enum EvaluatorFlags { // Allow use of the Concatenate special form. AllowConcatenate = 1 << 27, + // Do not infer literal types within a tuple (used for tuples nested within + // other container classes). + StripLiteralTypeForTuple = 1 << 28, + // Defaults used for evaluating the LHS of a call expression. CallBaseDefaults = DoNotSpecialize, diff --git a/packages/pyright-internal/src/analyzer/typeUtils.ts b/packages/pyright-internal/src/analyzer/typeUtils.ts index e91ac5efdf..43c5ce4857 100644 --- a/packages/pyright-internal/src/analyzer/typeUtils.ts +++ b/packages/pyright-internal/src/analyzer/typeUtils.ts @@ -2551,17 +2551,28 @@ export function getMembersForModule(moduleType: ModuleType, symbolTable: SymbolT } // Determines if the type contains an Any recursively. -export function containsAnyRecursive(type: Type): boolean { +export function containsAnyRecursive(type: Type, includeUnknown = true): boolean { class AnyWalker extends TypeWalker { foundAny = false; + constructor(private _includeUnknown: boolean) { + super(); + } + override visitAny(type: AnyType) { this.foundAny = true; this.cancelWalk(); } + + override visitUnknown(type: UnknownType): void { + if (this._includeUnknown) { + this.foundAny = true; + this.cancelWalk(); + } + } } - const walker = new AnyWalker(); + const walker = new AnyWalker(includeUnknown); walker.walk(type); return walker.foundAny; } @@ -4184,7 +4195,9 @@ class ApplySolvedTypeVarsTransformer extends TypeVarTransformer { return TypeVarType.cloneForUnpacked(replacement, typeVar.isVariadicInUnion); } - return replacement; + if (!isTypeVar(replacement) || !replacement.isInScopePlaceholder || !this._options.unknownIfNotFound) { + return replacement; + } } // If this typeVar is in scope for what we're solving but the type diff --git a/packages/pyright-internal/src/backgroundAnalysisBase.ts b/packages/pyright-internal/src/backgroundAnalysisBase.ts index e951791398..6a8f9c33d8 100644 --- a/packages/pyright-internal/src/backgroundAnalysisBase.ts +++ b/packages/pyright-internal/src/backgroundAnalysisBase.ts @@ -9,7 +9,13 @@ import { CancellationToken } from 'vscode-languageserver'; import { MessageChannel, MessagePort, Worker, parentPort, threadId, workerData } from 'worker_threads'; -import { AnalysisCompleteCallback, AnalysisResults, analyzeProgram, nullCallback } from './analyzer/analysis'; +import { + AnalysisCompleteCallback, + AnalysisResults, + RequiringAnalysisCount, + analyzeProgram, + nullCallback, +} from './analyzer/analysis'; import { BackgroundAnalysisProgram, InvalidatedReason } from './analyzer/backgroundAnalysisProgram'; import { ImportResolver } from './analyzer/importResolver'; import { OpenFileOptions, Program } from './analyzer/program'; @@ -473,12 +479,12 @@ export abstract class BackgroundAnalysisRunnerBase extends BackgroundThreadBase protected handleAnalyze(port: MessagePort, cancellationId: string, token: CancellationToken) { // Report files to analyze first. - const filesLeftToAnalyze = this.program.getFilesToAnalyzeCount(); + const requiringAnalysisCount = this.program.getFilesToAnalyzeCount(); this.onAnalysisCompletion(port, { diagnostics: [], filesInProgram: this.program.getFileCount(), - filesRequiringAnalysis: filesLeftToAnalyze, + requiringAnalysisCount: requiringAnalysisCount, checkingOnlyOpenFiles: this.program.isCheckingOnlyOpenFiles(), fatalErrorOccurred: false, configParseErrorOccurred: false, @@ -671,12 +677,16 @@ export abstract class BackgroundAnalysisRunnerBase extends BackgroundThreadBase } } - private _reportDiagnostics(diagnostics: FileDiagnostics[], filesLeftToAnalyze: number, elapsedTime: number) { + private _reportDiagnostics( + diagnostics: FileDiagnostics[], + requiringAnalysisCount: RequiringAnalysisCount, + elapsedTime: number + ) { if (parentPort) { this.onAnalysisCompletion(parentPort, { diagnostics, filesInProgram: this.program.getFileCount(), - filesRequiringAnalysis: filesLeftToAnalyze, + requiringAnalysisCount: requiringAnalysisCount, checkingOnlyOpenFiles: this.program.isCheckingOnlyOpenFiles(), fatalErrorOccurred: false, configParseErrorOccurred: false, diff --git a/packages/pyright-internal/src/backgroundThreadBase.ts b/packages/pyright-internal/src/backgroundThreadBase.ts index 7eba0a33d0..d6e7cf9e3b 100644 --- a/packages/pyright-internal/src/backgroundThreadBase.ts +++ b/packages/pyright-internal/src/backgroundThreadBase.ts @@ -22,24 +22,32 @@ import './common/serviceProviderExtensions'; import { Uri } from './common/uri/uri'; export class BackgroundConsole implements ConsoleInterface { - // We always generate logs in the background. For the foreground, - // we'll decide based on user setting whether. + private _level = LogLevel.Log; + get level() { - return LogLevel.Log; + return this._level; + } + + set level(value: LogLevel) { + this._level = value; } log(msg: string) { this.post(LogLevel.Log, msg); } + info(msg: string) { this.post(LogLevel.Info, msg); } + warn(msg: string) { this.post(LogLevel.Warn, msg); } + error(msg: string) { this.post(LogLevel.Error, msg); } + protected post(level: LogLevel, msg: string) { parentPort?.postMessage({ requestType: 'log', data: serialize({ level: level, message: msg }) }); } diff --git a/packages/pyright-internal/src/commands/createTypeStub.ts b/packages/pyright-internal/src/commands/createTypeStub.ts index 6729bae42b..91f93435d6 100644 --- a/packages/pyright-internal/src/commands/createTypeStub.ts +++ b/packages/pyright-internal/src/commands/createTypeStub.ts @@ -9,53 +9,64 @@ import { CancellationToken, ExecuteCommandParams } from 'vscode-languageserver'; import { OperationCanceledException } from '../common/cancellationUtils'; -import { LanguageServerInterface } from '../common/languageServerInterface'; +import { LanguageServerBaseInterface, LanguageServerInterface } from '../common/languageServerInterface'; import { AnalyzerServiceExecutor } from '../languageService/analyzerServiceExecutor'; import { ServerCommand } from './commandController'; import { Uri } from '../common/uri/uri'; +import { Workspace } from '../workspaceFactory'; export class CreateTypeStubCommand implements ServerCommand { - constructor(private _ls: LanguageServerInterface) {} + constructor(private _ls: LanguageServerInterface) { + // Empty + } async execute(cmdParams: ExecuteCommandParams, token: CancellationToken): Promise { - if (cmdParams.arguments && cmdParams.arguments.length >= 2) { - const workspaceRoot = Uri.parse(cmdParams.arguments[0] as string, this._ls.serviceProvider); - const importName = cmdParams.arguments[1] as string; - const callingFile = Uri.parse(cmdParams.arguments[2] as string, this._ls.serviceProvider); - - const service = await AnalyzerServiceExecutor.cloneService( - this._ls, - await this._ls.getWorkspaceForFile(callingFile ?? workspaceRoot), - { - typeStubTargetImportName: importName, - } - ); - - try { - await service.writeTypeStubInBackground(token); - service.dispose(); - - const infoMessage = `Type stub was successfully created for '${importName}'.`; - this._ls.window.showInformationMessage(infoMessage); - - // This is called after a new type stub has been created. It allows - // us to invalidate caches and force reanalysis of files that potentially - // are affected by the appearance of a new type stub. - this._ls.reanalyze(); - } catch (err) { - const isCancellation = OperationCanceledException.is(err); - if (isCancellation) { - const errMessage = `Type stub creation for '${importName}' was canceled`; - this._ls.console.error(errMessage); - } else { - let errMessage = ''; - if (err instanceof Error) { - errMessage = ': ' + err.message; - } - errMessage = `An error occurred when creating type stub for '${importName}'` + errMessage; - this._ls.console.error(errMessage); - this._ls.window.showErrorMessage(errMessage); + if (!cmdParams.arguments || cmdParams.arguments.length < 2) { + return undefined; + } + + const workspaceRoot = Uri.parse(cmdParams.arguments[0] as string, this._ls.serviceProvider); + const importName = cmdParams.arguments[1] as string; + const callingFile = Uri.parse(cmdParams.arguments[2] as string, this._ls.serviceProvider); + + const workspace = await this._ls.getWorkspaceForFile(callingFile ?? workspaceRoot); + return await new TypeStubCreator(this._ls).create(workspace, importName, token); + } +} + +export class TypeStubCreator { + constructor(private _ls: LanguageServerBaseInterface) {} + + async create(workspace: Workspace, importName: string, token: CancellationToken): Promise { + const service = await AnalyzerServiceExecutor.cloneService(this._ls, workspace, { + typeStubTargetImportName: importName, + useBackgroundAnalysis: true, + }); + + try { + await service.writeTypeStubInBackground(token); + service.dispose(); + + const infoMessage = `Type stub was successfully created for '${importName}'.`; + this._ls.window.showInformationMessage(infoMessage); + + // This is called after a new type stub has been created. It allows + // us to invalidate caches and force reanalysis of files that potentially + // are affected by the appearance of a new type stub. + this._ls.reanalyze(); + } catch (err) { + const isCancellation = OperationCanceledException.is(err); + if (isCancellation) { + const errMessage = `Type stub creation for '${importName}' was canceled`; + this._ls.console.error(errMessage); + } else { + let errMessage = ''; + if (err instanceof Error) { + errMessage = ': ' + err.message; } + errMessage = `An error occurred when creating type stub for '${importName}'` + errMessage; + this._ls.console.error(errMessage); + this._ls.window.showErrorMessage(errMessage); } } } diff --git a/packages/pyright-internal/src/common/configOptions.ts b/packages/pyright-internal/src/common/configOptions.ts index cb2bf97fb1..636f4192c4 100644 --- a/packages/pyright-internal/src/common/configOptions.ts +++ b/packages/pyright-internal/src/common/configOptions.ts @@ -19,8 +19,8 @@ import { DiagnosticRule } from './diagnosticRules'; import { FileSystem } from './fileSystem'; import { Host } from './host'; import { PythonVersion, latestStablePythonVersion } from './pythonVersion'; -import { ServiceProvider } from './serviceProvider'; import { ServiceKeys } from './serviceKeys'; +import { ServiceProvider } from './serviceProvider'; import { Uri } from './uri/uri'; import { FileSpec, getFileSpec, isDirectory } from './uri/uriUtils'; import { userFacingOptionsList } from './stringUtils'; @@ -1180,9 +1180,6 @@ export class ConfigOptions { // Minimum threshold for type eval logging typeEvaluationTimeThreshold = 50; - // Current type checking mode. - typeCheckingMode?: string; - // Was this config initialized from JSON (pyrightconfig/pyproject)? initializedFromJson = false; @@ -1243,10 +1240,9 @@ export class ConfigOptions { // https://github.com/microsoft/TypeScript/issues/3841 declare ['constructor']: typeof ConfigOptions; - constructor(projectRoot: Uri, typeCheckingMode?: string) { + constructor(projectRoot: Uri) { this.projectRoot = projectRoot; - this.typeCheckingMode = typeCheckingMode; - this.diagnosticRuleSet = this.constructor.getDiagnosticRuleSet(typeCheckingMode); + this.diagnosticRuleSet = this.constructor.getDiagnosticRuleSet(); this.functionSignatureDisplay = SignatureDisplayType.formatted; } @@ -1301,10 +1297,21 @@ export class ConfigOptions { return [this.getDefaultExecEnvironment()]; } + initializeTypeCheckingMode( + typeCheckingMode: string | undefined, + severityOverrides?: DiagnosticSeverityOverridesMap + ) { + this.diagnosticRuleSet = ConfigOptions.getDiagnosticRuleSet(typeCheckingMode); + + if (severityOverrides) { + this.applyDiagnosticOverrides(severityOverrides); + } + } + // Initialize the structure from a JSON object. initializeFromJson( configObj: any, - typeCheckingMode: string | undefined, + configDirUri: Uri, serviceProvider: ServiceProvider, host: Host, commandLineOptions?: CommandLineOptions @@ -1341,7 +1348,7 @@ export class ConfigOptions { } else if (isAbsolute(fileSpec)) { errors.push(`path "${fileSpec}" in "${key}" array should be relative.`); } else { - this[key].push(getFileSpec(this.projectRoot, fileSpec)); + this[key].push(getFileSpec(configDirUri, fileSpec)); } }); } @@ -1349,11 +1356,10 @@ export class ConfigOptions { } // If there is a "typeCheckingMode", it can override the provided setting. - let configTypeCheckingMode: string | undefined; if (configObj.typeCheckingMode !== undefined) { const validTypeCheckingModes = ['off', 'basic', 'standard', 'strict', 'all']; if (validTypeCheckingModes.includes(configObj.typeCheckingMode)) { - configTypeCheckingMode = configObj.typeCheckingMode; + this.diagnosticRuleSet = { ...ConfigOptions.getDiagnosticRuleSet(configObj.typeCheckingMode) }; } else { errors.push( `Config "typeCheckingMode" entry must contain ${userFacingOptionsList(validTypeCheckingModes)}.` @@ -1369,15 +1375,6 @@ export class ConfigOptions { } } - this.typeCheckingMode = configTypeCheckingMode || typeCheckingMode; - const defaultSettings = ConfigOptions.getDiagnosticRuleSet(this.typeCheckingMode); - - // Start with the default values for all rules in the rule set. - this.diagnosticRuleSet = { ...defaultSettings }; - - // Apply host-provided overrides. - this.applyDiagnosticOverrides(commandLineOptions?.diagnosticSeverityOverrides); - // Apply overrides from the config file for the boolean rules. getBooleanDiagnosticRules(/* includeNonOverridable */ true).forEach((ruleName) => { (this.diagnosticRuleSet as any)[ruleName] = this._convertBoolean( @@ -1398,17 +1395,15 @@ export class ConfigOptions { }); // Read the "venvPath". - this.venvPath = undefined; if (configObj.venvPath !== undefined) { if (typeof configObj.venvPath !== 'string') { errors.push(`Config "venvPath" field must contain a string.`); } else { - this.venvPath = this.projectRoot.resolvePaths(configObj.venvPath); + this.venvPath = configDirUri.resolvePaths(configObj.venvPath); } } // Read the "venv" name. - this.venv = undefined; if (configObj.venv !== undefined) { if (typeof configObj.venv !== 'string') { errors.push(`Config "venv" field must contain a string.`); @@ -1428,7 +1423,7 @@ export class ConfigOptions { if (typeof path !== 'string') { errors.push(`Config "extraPaths" field ${pathIndex} must be a string.`); } else { - this.defaultExtraPaths!.push(this.projectRoot.resolvePaths(path)); + this.defaultExtraPaths!.push(configDirUri.resolvePaths(path)); } }); } @@ -1473,19 +1468,17 @@ export class ConfigOptions { this.ensureDefaultPythonPlatform(host, console); // Read the "typeshedPath" setting. - this.typeshedPath = undefined; if (configObj.typeshedPath !== undefined) { if (typeof configObj.typeshedPath !== 'string') { errors.push(`Config "typeshedPath" field must contain a string.`); } else { this.typeshedPath = configObj.typeshedPath - ? this.projectRoot.resolvePaths(configObj.typeshedPath) + ? configDirUri.resolvePaths(configObj.typeshedPath) : undefined; } } // Read the "stubPath" setting. - this.stubPath = undefined; // Keep this for backward compatibility if (configObj.typingsPath !== undefined) { @@ -1493,7 +1486,7 @@ export class ConfigOptions { errors.push(`Config "typingsPath" field must contain a string.`); } else { errors.push(`Config "typingsPath" is now deprecated. Please, use stubPath instead.`); - this.stubPath = this.projectRoot.resolvePaths(configObj.typingsPath); + this.stubPath = configDirUri.resolvePaths(configObj.typingsPath); } } @@ -1501,7 +1494,7 @@ export class ConfigOptions { if (typeof configObj.stubPath !== 'string') { errors.push(`Config "stubPath" field must contain a string.`); } else { - this.stubPath = this.projectRoot.resolvePaths(configObj.stubPath); + this.stubPath = configDirUri.resolvePaths(configObj.stubPath); } } @@ -1545,14 +1538,21 @@ export class ConfigOptions { // Read the "executionEnvironments" array. This should be done at the end // after we've established default values. - this.executionEnvironments = []; if (configObj.executionEnvironments !== undefined) { if (!Array.isArray(configObj.executionEnvironments)) { errors.push(`Config "executionEnvironments" field must contain an array.`); } else { + this.executionEnvironments = []; + const execEnvironments = configObj.executionEnvironments as ExecutionEnvironment[]; + execEnvironments.forEach((env, index) => { - const result = this._initExecutionEnvironmentFromJson(env, index, commandLineOptions); + const result = this._initExecutionEnvironmentFromJson( + env, + configDirUri, index, + commandLineOptions + ); + if (result instanceof ExecutionEnvironment) { this.executionEnvironments.push(result); } else { @@ -1604,6 +1604,18 @@ export class ConfigOptions { return errors; } + static resolveExtends(configObj: any, configDirUri: Uri): Uri | undefined { + if (configObj.extends !== undefined) { + if (typeof configObj.extends !== 'string') { + console.error(`Config "extends" field must contain a string.`); + } else { + return configDirUri.resolvePaths(configObj.extends); + } + } + + return undefined; + } + ensureDefaultPythonPlatform(host: Host, console: ConsoleInterface) { // If no default python platform was specified, assume that the // user wants to use all mode @@ -1723,6 +1735,7 @@ export class ConfigOptions { private _initExecutionEnvironmentFromJson( envObj: any, + configDirUri: Uri, index: number, commandLineOptions?: CommandLineOptions ): ExecutionEnvironment | string[] { @@ -1730,7 +1743,7 @@ export class ConfigOptions { try { const newExecEnv = new ExecutionEnvironment( this._getEnvironmentName(), - this.projectRoot, + configDirUri, this.defaultPythonVersion, this.defaultPythonPlatform, this.defaultExtraPaths @@ -1738,7 +1751,7 @@ export class ConfigOptions { // Validate the root. if (envObj.root && typeof envObj.root === 'string') { - newExecEnv.root = this.projectRoot.resolvePaths(envObj.root); + newExecEnv.root = configDirUri.resolvePaths(envObj.root); } else { errors.push(`Config executionEnvironments index ${index}: missing root value.`); } @@ -1756,7 +1769,7 @@ export class ConfigOptions { ` extraPaths field ${pathIndex} must be a string.` ); } else { - newExecEnv.extraPaths.push(this.projectRoot.resolvePaths(path)); + newExecEnv.extraPaths.push(configDirUri.resolvePaths(path)); } }); } diff --git a/packages/pyright-internal/src/common/console.ts b/packages/pyright-internal/src/common/console.ts index cddcf6eb60..c616897e51 100644 --- a/packages/pyright-internal/src/common/console.ts +++ b/packages/pyright-internal/src/common/console.ts @@ -29,6 +29,10 @@ export namespace ConsoleInterface { export function is(obj: any): obj is ConsoleInterface { return obj.error !== undefined && obj.warn !== undefined && obj.info !== undefined && obj.log !== undefined; } + + export function hasLevel(console: any): console is ConsoleInterface & { level: LogLevel } { + return is(console) && 'level' in console; + } } const levelMap = new Map([ diff --git a/packages/pyright-internal/src/common/languageServerInterface.ts b/packages/pyright-internal/src/common/languageServerInterface.ts index 1dc131a773..fbcac45985 100644 --- a/packages/pyright-internal/src/common/languageServerInterface.ts +++ b/packages/pyright-internal/src/common/languageServerInterface.ts @@ -6,13 +6,19 @@ * Interface for language server */ +import { MarkupKind } from 'vscode-languageserver'; +import { MaxAnalysisTime } from '../analyzer/program'; import { BackgroundAnalysisBase } from '../backgroundAnalysisBase'; import { Workspace } from '../workspaceFactory'; +import { CancellationProvider } from './cancellationUtils'; import { DiagnosticSeverityOverridesMap } from './commandLineOptions'; import { SignatureDisplayType } from './configOptions'; import { ConsoleInterface, LogLevel } from './console'; import { TaskListToken } from './diagnostic'; import * as ext from './extensibility'; +import { FileSystem } from './fileSystem'; +import { FileWatcherHandler } from './fileWatcher'; +import { ServiceProvider } from './serviceProvider'; import { Uri } from './uri/uri'; export interface ServerSettings { @@ -71,6 +77,49 @@ export namespace WindowInterface { } } +export interface WorkspaceServices { + fs: FileSystem | undefined; + backgroundAnalysis: BackgroundAnalysisBase | undefined; +} + +export interface ServerOptions { + productName: string; + rootDirectory: Uri; + version: string; + cancellationProvider: CancellationProvider; + serviceProvider: ServiceProvider; + fileWatcherHandler: FileWatcherHandler; + maxAnalysisTimeInForeground?: MaxAnalysisTime; + disableChecker?: boolean; + supportedCommands?: string[]; + supportedCodeActions?: string[]; + supportsTelemetry?: boolean; +} + +export interface ClientCapabilities { + hasConfigurationCapability: boolean; + hasVisualStudioExtensionsCapability: boolean; + hasWorkspaceFoldersCapability: boolean; + hasWatchFileCapability: boolean; + hasWatchFileRelativePathCapability: boolean; + hasActiveParameterCapability: boolean; + hasSignatureLabelOffsetCapability: boolean; + hasHierarchicalDocumentSymbolCapability: boolean; + hasWindowProgressCapability: boolean; + hasGoToDeclarationCapability: boolean; + hasDocumentChangeCapability: boolean; + hasDocumentAnnotationCapability: boolean; + hasCompletionCommitCharCapability: boolean; + hoverContentFormat: MarkupKind; + completionDocFormat: MarkupKind; + completionSupportsSnippet: boolean; + signatureDocFormat: MarkupKind; + supportsDeprecatedDiagnosticTag: boolean; + supportsUnnecessaryDiagnosticTag: boolean; + supportsTaskItemDiagnosticTag: boolean; + completionItemResolveSupportsAdditionalTextEdits: boolean; +} + export interface LanguageServerBaseInterface { readonly console: ConsoleInterface; readonly window: WindowInterface; diff --git a/packages/pyright-internal/src/common/uri/uri.ts b/packages/pyright-internal/src/common/uri/uri.ts index 56c313984d..11c5a2317f 100644 --- a/packages/pyright-internal/src/common/uri/uri.ts +++ b/packages/pyright-internal/src/common/uri/uri.ts @@ -234,6 +234,8 @@ export namespace Uri { return EmptyUri.instance; } + // Excel's copy of tests\harness\vfs\pathValidation.ts knows about this constant. + // If the value is changed, the Excel team should be told. export const DefaultWorkspaceRootComponent = ''; export const DefaultWorkspaceRootPath = `/${DefaultWorkspaceRootComponent}`; diff --git a/packages/pyright-internal/src/common/uri/uriUtils.ts b/packages/pyright-internal/src/common/uri/uriUtils.ts index 02483efb04..a366335c73 100644 --- a/packages/pyright-internal/src/common/uri/uriUtils.ts +++ b/packages/pyright-internal/src/common/uri/uriUtils.ts @@ -380,7 +380,7 @@ export function getRootUri(csdOrSp: CaseSensitivityDetector | ServiceProvider): return undefined; } -export function encodeUri(fs: ReadOnlyFileSystem, uri: Uri): string { +export function convertUriToLspUriString(fs: ReadOnlyFileSystem, uri: Uri): string { // Convert to a URI string that the LSP client understands (mapped files are only local to the server). return fs.getOriginalUri(uri).toString(); } diff --git a/packages/pyright-internal/src/common/workspaceEditUtils.ts b/packages/pyright-internal/src/common/workspaceEditUtils.ts index f1247340ab..3558a70557 100644 --- a/packages/pyright-internal/src/common/workspaceEditUtils.ts +++ b/packages/pyright-internal/src/common/workspaceEditUtils.ts @@ -28,7 +28,7 @@ import { convertRangeToTextRange, convertTextRangeToRange } from './positionUtil import { TextRange } from './textRange'; import { TextRangeCollection } from './textRangeCollection'; import { Uri } from './uri/uri'; -import { encodeUri } from './uri/uriUtils'; +import { convertUriToLspUriString } from './uri/uriUtils'; export function convertToTextEdits(editActions: TextEditAction[]): TextEdit[] { return editActions.map((editAction) => ({ @@ -74,7 +74,7 @@ export function convertToWorkspaceEdit( export function appendToWorkspaceEdit(fs: ReadOnlyFileSystem, edits: FileEditAction[], workspaceEdit: WorkspaceEdit) { edits.forEach((edit) => { - const uri = encodeUri(fs, edit.fileUri); + const uri = convertUriToLspUriString(fs, edit.fileUri); workspaceEdit.changes![uri] = workspaceEdit.changes![uri] || []; workspaceEdit.changes![uri].push({ range: edit.range, newText: edit.replacementText }); }); @@ -191,7 +191,7 @@ export function generateWorkspaceEdit( continue; } - edits.changes![encodeUri(fs, uri)] = [ + edits.changes![convertUriToLspUriString(fs, uri)] = [ { range: convertTextRangeToRange(parseResults.parserOutput.parseTree, parseResults.tokenizerOutput.lines), newText: final.getFileContent() ?? '', @@ -230,7 +230,11 @@ function _convertToWorkspaceEditWithDocumentChanges( switch (operation.kind) { case 'create': workspaceEdit.documentChanges!.push( - CreateFile.create(encodeUri(fs, operation.fileUri), /* options */ undefined, defaultAnnotationId) + CreateFile.create( + convertUriToLspUriString(fs, operation.fileUri), + /* options */ undefined, + defaultAnnotationId + ) ); break; case 'rename': @@ -242,7 +246,7 @@ function _convertToWorkspaceEditWithDocumentChanges( } // Text edit's file path must refer to original file paths unless it is a new file just created. - const mapPerFile = createMapFromItems(editActions.edits, (e) => encodeUri(fs, e.fileUri)); + const mapPerFile = createMapFromItems(editActions.edits, (e) => convertUriToLspUriString(fs, e.fileUri)); for (const [uri, value] of mapPerFile) { workspaceEdit.documentChanges!.push( TextDocumentEdit.create( @@ -265,8 +269,8 @@ function _convertToWorkspaceEditWithDocumentChanges( case 'rename': workspaceEdit.documentChanges!.push( RenameFile.create( - encodeUri(fs, operation.oldFileUri), - encodeUri(fs, operation.newFileUri), + convertUriToLspUriString(fs, operation.oldFileUri), + convertUriToLspUriString(fs, operation.newFileUri), /* options */ undefined, defaultAnnotationId ) @@ -274,7 +278,11 @@ function _convertToWorkspaceEditWithDocumentChanges( break; case 'delete': workspaceEdit.documentChanges!.push( - DeleteFile.create(encodeUri(fs, operation.fileUri), /* options */ undefined, defaultAnnotationId) + DeleteFile.create( + convertUriToLspUriString(fs, operation.fileUri), + /* options */ undefined, + defaultAnnotationId + ) ); break; default: diff --git a/packages/pyright-internal/src/languageServerBase.ts b/packages/pyright-internal/src/languageServerBase.ts index c26e298788..e2016a9b72 100644 --- a/packages/pyright-internal/src/languageServerBase.ts +++ b/packages/pyright-internal/src/languageServerBase.ts @@ -79,7 +79,7 @@ import { AnalyzerService, LibraryReanalysisTimeProvider, getNextServiceId } from import { IPythonMode } from './analyzer/sourceFile'; import type { BackgroundAnalysisBase } from './backgroundAnalysisBase'; import { CommandResult } from './commands/commandResult'; -import { CancelAfter, CancellationProvider } from './common/cancellationUtils'; +import { CancelAfter } from './common/cancellationUtils'; import { CaseSensitivityDetector } from './common/caseSensitivityDetector'; import { getNestedProperty } from './common/collectionUtils'; import { DiagnosticSeverityOverrides, getDiagnosticSeverityOverrides } from './common/commandLineOptions'; @@ -89,16 +89,22 @@ import { Diagnostic as AnalyzerDiagnostic, DiagnosticCategory, TaskListPriority import { DiagnosticRule } from './common/diagnosticRules'; import { FileDiagnostics } from './common/diagnosticSink'; import { FileSystem, ReadOnlyFileSystem } from './common/fileSystem'; -import { FileWatcherEventType, FileWatcherHandler } from './common/fileWatcher'; +import { FileWatcherEventType } from './common/fileWatcher'; import { Host } from './common/host'; -import { LanguageServerInterface, ServerSettings } from './common/languageServerInterface'; +import { + ClientCapabilities, + LanguageServerInterface, + ServerOptions, + ServerSettings, + WorkspaceServices, +} from './common/languageServerInterface'; import { fromLSPAny } from './common/lspUtils'; import { ProgressReportTracker, ProgressReporter } from './common/progressReporter'; import { ServiceKeys } from './common/serviceKeys'; import { ServiceProvider } from './common/serviceProvider'; import { DocumentRange, Position, Range } from './common/textRange'; import { Uri } from './common/uri/uri'; -import { encodeUri } from './common/uri/uriUtils'; +import { convertUriToLspUriString } from './common/uri/uriUtils'; import { AnalyzerServiceExecutor } from './languageService/analyzerServiceExecutor'; import { CallHierarchyProvider } from './languageService/callHierarchyProvider'; import { InlayHintsProvider } from './languageService/inlayHintsProvider'; @@ -121,49 +127,6 @@ import { FileWatcherDynamicFeature } from './languageService/fileWatcherDynamicF import { githubRepo } from './constants'; import { SemanticTokensProvider, SemanticTokensProviderLegend } from './languageService/semanticTokensProvider'; -export interface ServerOptions { - productName: string; - rootDirectory: Uri; - version: string; - cancellationProvider: CancellationProvider; - serviceProvider: ServiceProvider; - fileWatcherHandler: FileWatcherHandler; - maxAnalysisTimeInForeground?: MaxAnalysisTime; - disableChecker?: boolean; - supportedCommands?: string[]; - supportedCodeActions?: string[]; - supportsTelemetry?: boolean; -} - -export interface WorkspaceServices { - fs: FileSystem | undefined; - backgroundAnalysis: BackgroundAnalysisBase | undefined; -} - -export interface ClientCapabilities { - hasConfigurationCapability: boolean; - hasVisualStudioExtensionsCapability: boolean; - hasWorkspaceFoldersCapability: boolean; - hasWatchFileCapability: boolean; - hasWatchFileRelativePathCapability: boolean; - hasActiveParameterCapability: boolean; - hasSignatureLabelOffsetCapability: boolean; - hasHierarchicalDocumentSymbolCapability: boolean; - hasWindowProgressCapability: boolean; - hasGoToDeclarationCapability: boolean; - hasDocumentChangeCapability: boolean; - hasDocumentAnnotationCapability: boolean; - hasCompletionCommitCharCapability: boolean; - hoverContentFormat: MarkupKind; - completionDocFormat: MarkupKind; - completionSupportsSnippet: boolean; - signatureDocFormat: MarkupKind; - supportsDeprecatedDiagnosticTag: boolean; - supportsUnnecessaryDiagnosticTag: boolean; - supportsTaskItemDiagnosticTag: boolean; - completionItemResolveSupportsAdditionalTextEdits: boolean; -} - const nullProgressReporter = attachWorkDone(undefined as any, /* params */ undefined); /* @@ -814,7 +777,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis ) { this.recordUserInteractionTime(); - const uri = this.decodeUri(params.textDocument.uri); + const uri = this.convertLspUriStringToUri(params.textDocument.uri); const workspace = await this.getWorkspaceForFile(uri); if (workspace.disableLanguageServices) { @@ -827,7 +790,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis } return locations .filter((loc) => this.canNavigateToFile(loc.uri, workspace.service.fs)) - .map((loc) => Location.create(encodeUri(workspace.service.fs, loc.uri), loc.range)); + .map((loc) => Location.create(convertUriToLspUriString(workspace.service.fs, loc.uri), loc.range)); } protected async onReferences( @@ -856,7 +819,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis this._pendingFindAllRefsCancellationSource = source; try { - const uri = this.decodeUri(params.textDocument.uri); + const uri = this.convertLspUriStringToUri(params.textDocument.uri); const workspace = await this.getWorkspaceForFile(uri); if (workspace.disableLanguageServices) { @@ -883,7 +846,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis ): Promise { this.recordUserInteractionTime(); - const uri = this.decodeUri(params.textDocument.uri); + const uri = this.convertLspUriStringToUri(params.textDocument.uri); const workspace = await this.getWorkspaceForFile(uri); if (workspace.disableLanguageServices) { return undefined; @@ -916,7 +879,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis } protected async onHover(params: HoverParams, token: CancellationToken) { - const uri = this.decodeUri(params.textDocument.uri); + const uri = this.convertLspUriStringToUri(params.textDocument.uri); const workspace = await this.getWorkspaceForFile(uri); if (workspace.disableLanguageServices) { return; @@ -930,7 +893,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis params: DocumentHighlightParams, token: CancellationToken ): Promise { - const uri = this.decodeUri(params.textDocument.uri); + const uri = this.convertLspUriStringToUri(params.textDocument.uri); const workspace = await this.getWorkspaceForFile(uri); return workspace.service.run((program) => { @@ -942,7 +905,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis params: SignatureHelpParams, token: CancellationToken ): Promise { - const uri = this.decodeUri(params.textDocument.uri); + const uri = this.convertLspUriStringToUri(params.textDocument.uri); const workspace = await this.getWorkspaceForFile(uri); if (workspace.disableLanguageServices) { @@ -984,7 +947,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis } protected async onCompletion(params: CompletionParams, token: CancellationToken): Promise { - const uri = this.decodeUri(params.textDocument.uri); + const uri = this.convertLspUriStringToUri(params.textDocument.uri); const workspace = await this.getWorkspaceForFile(uri); if (workspace.disableLanguageServices) { return null; @@ -1018,7 +981,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis protected async onCompletionResolve(params: CompletionItem, token: CancellationToken): Promise { const completionItemData = fromLSPAny(params.data); if (completionItemData && completionItemData.uri) { - const uri = this.decodeUri(completionItemData.uri); + const uri = Uri.parse(completionItemData.uri, this.caseSensitiveDetector); const workspace = await this.getWorkspaceForFile(uri); workspace.service.run((program) => { return new CompletionProvider( @@ -1041,7 +1004,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis params: PrepareRenameParams, token: CancellationToken ): Promise { - const uri = this.decodeUri(params.textDocument.uri); + const uri = this.convertLspUriStringToUri(params.textDocument.uri); const isUntitled = uri.isUntitled(); const workspace = await this.getWorkspaceForFile(uri); @@ -1061,7 +1024,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis params: RenameParams, token: CancellationToken ): Promise { - const uri = this.decodeUri(params.textDocument.uri); + const uri = this.convertLspUriStringToUri(params.textDocument.uri); const isUntitled = uri.isUntitled(); const workspace = await this.getWorkspaceForFile(uri); @@ -1108,7 +1071,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis params: CallHierarchyPrepareParams, token: CancellationToken ): Promise { - const uri = this.decodeUri(params.textDocument.uri); + const uri = this.convertLspUriStringToUri(params.textDocument.uri); const workspace = await this.getWorkspaceForFile(uri); if (workspace.disableLanguageServices) { @@ -1121,7 +1084,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis } protected async onCallHierarchyIncomingCalls(params: CallHierarchyIncomingCallsParams, token: CancellationToken) { - const uri = this.decodeUri(params.item.uri); + const uri = this.convertLspUriStringToUri(params.item.uri); const workspace = await this.getWorkspaceForFile(uri); if (workspace.disableLanguageServices) { @@ -1137,7 +1100,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis params: CallHierarchyOutgoingCallsParams, token: CancellationToken ): Promise { - const uri = this.decodeUri(params.item.uri); + const uri = this.convertLspUriStringToUri(params.item.uri); const workspace = await this.getWorkspaceForFile(uri); if (workspace.disableLanguageServices) { @@ -1150,7 +1113,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis } protected async onDidOpenTextDocument(params: DidOpenTextDocumentParams, ipythonMode = IPythonMode.None) { - const uri = this.decodeUri(params.textDocument.uri); + const uri = this.convertLspUriStringToUri(params.textDocument.uri); let doc = this.openFileMap.get(uri.key); if (doc) { @@ -1177,7 +1140,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis protected async onDidChangeTextDocument(params: DidChangeTextDocumentParams, ipythonMode = IPythonMode.None) { this.recordUserInteractionTime(); - const uri = this.decodeUri(params.textDocument.uri); + const uri = this.convertLspUriStringToUri(params.textDocument.uri); const doc = this.openFileMap.get(uri.key); if (!doc) { // We shouldn't get a change text request for a closed doc. @@ -1196,7 +1159,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis } protected async onDidCloseTextDocument(params: DidCloseTextDocumentParams) { - const uri = this.decodeUri(params.textDocument.uri); + const uri = this.convertLspUriStringToUri(params.textDocument.uri); // Send this close to all the workspaces that might contain this file. const workspaces = await this.getContainingWorkspacesForFile(uri); @@ -1209,7 +1172,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis protected onDidChangeWatchedFiles(params: DidChangeWatchedFilesParams) { params.changes.forEach((change) => { - const filePath = this.fs.realCasePath(this.decodeUri(change.uri)); + const filePath = this.fs.realCasePath(this.convertLspUriStringToUri(change.uri)); const eventType: FileWatcherEventType = change.type === 1 ? 'add' : 'change'; this.serverOptions.fileWatcherHandler.onFileChange(eventType, filePath); }); @@ -1284,7 +1247,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis protected convertDiagnostics(fs: FileSystem, fileDiagnostics: FileDiagnostics): PublishDiagnosticsParams[] { return [ { - uri: encodeUri(fs, fileDiagnostics.fileUri), + uri: convertUriToLspUriString(fs, fileDiagnostics.fileUri), version: fileDiagnostics.version, diagnostics: this._convertDiagnostics(fs, fileDiagnostics.diagnostics), }, @@ -1315,21 +1278,31 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis } // Update progress. - if (results.filesRequiringAnalysis > 0) { + const progressMessage = this.getProgressMessage(results); + if (progressMessage) { this._progressReporter.begin(); - - const progressMessage = - results.filesRequiringAnalysis === 1 - ? Localizer.CodeAction.filesToAnalyzeOne() - : Localizer.CodeAction.filesToAnalyzeCount().format({ - count: results.filesRequiringAnalysis, - }); this._progressReporter.report(progressMessage); } else { this._progressReporter.end(); } } + protected getProgressMessage(results: AnalysisResults): string | undefined { + const fileCount = results.requiringAnalysisCount.files; + + if (fileCount === 0) { + return undefined; + } + + const progressMessage = + fileCount === 1 + ? Localizer.CodeAction.filesToAnalyzeOne() + : Localizer.CodeAction.filesToAnalyzeCount().format({ + count: fileCount, + }); + return progressMessage; + } + protected onWorkspaceCreated(workspace: Workspace) { // Update settings on this workspace (but only if initialize has happened) if (this._initialized) { @@ -1343,7 +1316,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis const otherWorkspaces = this.workspaceFactory.items().filter((w) => w !== workspace); for (const uri of documentsWithDiagnosticsList) { - const fileUri = this.decodeUri(uri); + const fileUri = this.convertLspUriStringToUri(uri); if (workspace.service.isTracked(fileUri)) { // Do not clean up diagnostics for files tracked by multiple workspaces @@ -1430,7 +1403,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis } } - protected decodeUri(uri: string) { + protected convertLspUriStringToUri(uri: string) { return Uri.parse(uri, this.serverOptions.serviceProvider); } @@ -1522,7 +1495,7 @@ export abstract class LanguageServerBase implements LanguageServerInterface, Dis .filter((info) => this.canNavigateToFile(info.uri, fs)) .map((info) => DiagnosticRelatedInformation.create( - Location.create(encodeUri(fs, info.uri), info.range), + Location.create(convertUriToLspUriString(fs, info.uri), info.range), info.message ) ); diff --git a/packages/pyright-internal/src/languageService/analyzerServiceExecutor.ts b/packages/pyright-internal/src/languageService/analyzerServiceExecutor.ts index d334f64591..d185c8fac6 100644 --- a/packages/pyright-internal/src/languageService/analyzerServiceExecutor.ts +++ b/packages/pyright-internal/src/languageService/analyzerServiceExecutor.ts @@ -13,7 +13,7 @@ import { AnalyzerService, getNextServiceId } from '../analyzer/service'; import { CommandLineOptions } from '../common/commandLineOptions'; import { LogLevel } from '../common/console'; import { FileSystem } from '../common/fileSystem'; -import { LanguageServerInterface, ServerSettings } from '../common/languageServerInterface'; +import { LanguageServerBaseInterface, ServerSettings } from '../common/languageServerInterface'; import { Uri } from '../common/uri/uri'; import { WellKnownWorkspaceKinds, Workspace, createInitStatus } from '../workspaceFactory'; @@ -44,7 +44,7 @@ export class AnalyzerServiceExecutor { } static async cloneService( - ls: LanguageServerInterface, + ls: LanguageServerBaseInterface, workspace: Workspace, options?: CloneOptions ): Promise { diff --git a/packages/pyright-internal/src/languageService/callHierarchyProvider.ts b/packages/pyright-internal/src/languageService/callHierarchyProvider.ts index a21f823868..920c2662e5 100644 --- a/packages/pyright-internal/src/languageService/callHierarchyProvider.ts +++ b/packages/pyright-internal/src/languageService/callHierarchyProvider.ts @@ -34,7 +34,7 @@ import { convertOffsetsToRange } from '../common/positionUtils'; import { ServiceKeys } from '../common/serviceKeys'; import { Position, rangesAreEqual } from '../common/textRange'; import { Uri } from '../common/uri/uri'; -import { encodeUri } from '../common/uri/uriUtils'; +import { convertUriToLspUriString } from '../common/uri/uriUtils'; import { ReferencesProvider, ReferencesResult } from '../languageService/referencesProvider'; import { CallNode, MemberAccessNode, NameNode, ParseNode, ParseNodeType } from '../parser/parseNodes'; import { ParseFileResults } from '../parser/parser'; @@ -88,7 +88,7 @@ export class CallHierarchyProvider { const callItem: CallHierarchyItem = { name: symbolName, kind: getSymbolKind(targetDecl, this._evaluator, symbolName) ?? SymbolKind.Module, - uri: encodeUri(this._program.fileSystem, callItemUri), + uri: convertUriToLspUriString(this._program.fileSystem, callItemUri), range: targetDecl.range, selectionRange: targetDecl.range, }; @@ -384,7 +384,7 @@ class FindOutgoingCallTreeWalker extends ParseTreeWalker { const callDest: CallHierarchyItem = { name: nameNode.value, kind: getSymbolKind(resolvedDecl, this._evaluator, nameNode.value) ?? SymbolKind.Module, - uri: encodeUri(this._fs, resolvedDecl.uri), + uri: convertUriToLspUriString(this._fs, resolvedDecl.uri), range: resolvedDecl.range, selectionRange: resolvedDecl.range, }; @@ -569,7 +569,7 @@ class FindIncomingCallTreeWalker extends ParseTreeWalker { callSource = { name: `(module) ${fileName}`, kind: SymbolKind.Module, - uri: encodeUri(this._program.fileSystem, this._fileUri), + uri: convertUriToLspUriString(this._program.fileSystem, this._fileUri), range: moduleRange, selectionRange: moduleRange, }; @@ -583,7 +583,7 @@ class FindIncomingCallTreeWalker extends ParseTreeWalker { callSource = { name: '(lambda)', kind: SymbolKind.Function, - uri: encodeUri(this._program.fileSystem, this._fileUri), + uri: convertUriToLspUriString(this._program.fileSystem, this._fileUri), range: lambdaRange, selectionRange: lambdaRange, }; @@ -597,7 +597,7 @@ class FindIncomingCallTreeWalker extends ParseTreeWalker { callSource = { name: executionNode.name.value, kind: SymbolKind.Function, - uri: encodeUri(this._program.fileSystem, this._fileUri), + uri: convertUriToLspUriString(this._program.fileSystem, this._fileUri), range: functionRange, selectionRange: functionRange, }; diff --git a/packages/pyright-internal/src/languageService/completionProvider.ts b/packages/pyright-internal/src/languageService/completionProvider.ts index 09a162a001..4e36d971d3 100644 --- a/packages/pyright-internal/src/languageService/completionProvider.ts +++ b/packages/pyright-internal/src/languageService/completionProvider.ts @@ -32,7 +32,7 @@ import { isDefinedInFile } from '../analyzer/declarationUtils'; import { convertDocStringToMarkdown, convertDocStringToPlainText } from '../analyzer/docStringConversion'; import { ImportedModuleDescriptor, ImportResolver } from '../analyzer/importResolver'; import { ImportResult } from '../analyzer/importResult'; -import { getParameterListDetails, ParameterSource } from '../analyzer/parameterUtils'; +import { getParameterListDetails, ParameterKind } from '../analyzer/parameterUtils'; import * as ParseTreeUtils from '../analyzer/parseTreeUtils'; import { getCallNodeAndActiveParameterIndex } from '../analyzer/parseTreeUtils'; import { getScopeForNode } from '../analyzer/scopeUtils'; @@ -2855,7 +2855,7 @@ export class CompletionProvider { const paramDetails = getParameterListDetails(type); paramDetails.params.forEach((paramInfo) => { - if (paramInfo.param.name && paramInfo.source !== ParameterSource.PositionOnly) { + if (paramInfo.param.name && paramInfo.kind !== ParameterKind.Positional) { if (!SymbolNameUtils.isPrivateOrProtectedName(paramInfo.param.name)) { names.add(paramInfo.param.name); } diff --git a/packages/pyright-internal/src/languageService/documentSymbolProvider.ts b/packages/pyright-internal/src/languageService/documentSymbolProvider.ts index ff6b6a36e2..203252e4a2 100644 --- a/packages/pyright-internal/src/languageService/documentSymbolProvider.ts +++ b/packages/pyright-internal/src/languageService/documentSymbolProvider.ts @@ -15,7 +15,7 @@ import { throwIfCancellationRequested } from '../common/cancellationUtils'; import { ProgramView } from '../common/extensibility'; import { ReadOnlyFileSystem } from '../common/fileSystem'; import { Uri } from '../common/uri/uri'; -import { encodeUri } from '../common/uri/uriUtils'; +import { convertUriToLspUriString } from '../common/uri/uriUtils'; import { ParseFileResults } from '../parser/parser'; import { IndexOptions, IndexSymbolData, SymbolIndexer } from './symbolIndexer'; @@ -125,7 +125,7 @@ function _appendToFlatSymbolsRecursive( const flatSymbol: SymbolInformation = { name: symbol.name, kind: symbol.kind, - location: Location.create(encodeUri(fs, documentUri), symbol.range), + location: Location.create(convertUriToLspUriString(fs, documentUri), symbol.range), }; if (symbol.tags) { diff --git a/packages/pyright-internal/src/languageService/fileWatcherDynamicFeature.ts b/packages/pyright-internal/src/languageService/fileWatcherDynamicFeature.ts index d2a7a6d396..b03d192e3b 100644 --- a/packages/pyright-internal/src/languageService/fileWatcherDynamicFeature.ts +++ b/packages/pyright-internal/src/languageService/fileWatcherDynamicFeature.ts @@ -12,11 +12,11 @@ import { FileSystemWatcher, WatchKind, } from 'vscode-languageserver'; -import { DynamicFeature } from './dynamicFeature'; -import { configFileNames } from '../analyzer/service'; +import { configFileName } from '../analyzer/service'; +import { FileSystem } from '../common/fileSystem'; import { deduplicateFolders, isFile } from '../common/uri/uriUtils'; import { WorkspaceFactory } from '../workspaceFactory'; -import { FileSystem } from '../common/fileSystem'; +import { DynamicFeature } from './dynamicFeature'; export class FileWatcherDynamicFeature extends DynamicFeature { constructor( @@ -33,7 +33,7 @@ export class FileWatcherDynamicFeature extends DynamicFeature { // Set default (config files and all workspace files) first. const watchers: FileSystemWatcher[] = [ - ...configFileNames.map((fileName) => ({ globPattern: `**/${fileName}`, kind: watchKind })), + { globPattern: `**/${configFileName}`, kind: watchKind }, { globPattern: '**', kind: watchKind }, ]; diff --git a/packages/pyright-internal/src/languageService/navigationUtils.ts b/packages/pyright-internal/src/languageService/navigationUtils.ts index 63e294cdfd..3081721a25 100644 --- a/packages/pyright-internal/src/languageService/navigationUtils.ts +++ b/packages/pyright-internal/src/languageService/navigationUtils.ts @@ -9,7 +9,7 @@ import { Location } from 'vscode-languageserver-types'; import { ReadOnlyFileSystem } from '../common/fileSystem'; import { DocumentRange } from '../common/textRange'; import { Uri } from '../common/uri/uri'; -import { encodeUri } from '../common/uri/uriUtils'; +import { convertUriToLspUriString } from '../common/uri/uriUtils'; export function canNavigateToFile(fs: ReadOnlyFileSystem, path: Uri): boolean { return !fs.isInZip(path); @@ -28,5 +28,5 @@ export function convertDocumentRangeToLocation(fs: ReadOnlyFileSystem, range: Do return undefined; } - return Location.create(encodeUri(fs, range.uri), range.range); + return Location.create(convertUriToLspUriString(fs, range.uri), range.range); } diff --git a/packages/pyright-internal/src/languageService/workspaceSymbolProvider.ts b/packages/pyright-internal/src/languageService/workspaceSymbolProvider.ts index 9f421170fa..609e9a6a27 100644 --- a/packages/pyright-internal/src/languageService/workspaceSymbolProvider.ts +++ b/packages/pyright-internal/src/languageService/workspaceSymbolProvider.ts @@ -14,7 +14,7 @@ import { appendArray } from '../common/collectionUtils'; import { ProgramView } from '../common/extensibility'; import * as StringUtils from '../common/stringUtils'; import { Uri } from '../common/uri/uri'; -import { encodeUri } from '../common/uri/uriUtils'; +import { convertUriToLspUriString } from '../common/uri/uriUtils'; import { Workspace } from '../workspaceFactory'; import { IndexSymbolData, SymbolIndexer } from './symbolIndexer'; @@ -100,7 +100,7 @@ export class WorkspaceSymbolProvider { if (StringUtils.isPatternInSymbol(this._query, symbolData.name)) { const location: Location = { - uri: encodeUri(program.fileSystem, fileUri), + uri: convertUriToLspUriString(program.fileSystem, fileUri), range: symbolData.selectionRange!, }; diff --git a/packages/pyright-internal/src/localization/package.nls.cs.json b/packages/pyright-internal/src/localization/package.nls.cs.json index c7bb278f89..d1e4a370f2 100644 --- a/packages/pyright-internal/src/localization/package.nls.cs.json +++ b/packages/pyright-internal/src/localization/package.nls.cs.json @@ -594,7 +594,7 @@ "unpackNotAllowed": "Rozbalení se v tomto kontextu nepovoluje", "unpackOperatorNotAllowed": "Operace rozbalení není v tomto kontextu povolená", "unpackTuplesIllegal": "Operace rozbalení není povolená v řazených kolekcích členů před Pythonem 3.8", - "unpackedArgInTypeArgument": "Rozbalené argumenty není možné použít v seznamech argumentů typu", + "unpackedArgInTypeArgument": "V tomto kontextu nelze použít rozbalené argumenty.", "unpackedArgWithVariadicParam": "Pro parametr TypeVarTuple nejde použít rozbalený argument", "unpackedDictArgumentNotMapping": "Výraz argumentu za ** musí být mapování s typem klíče str", "unpackedDictSubscriptIllegal": "Operátor rozbalení slovníku v dolním indexu není povolený", diff --git a/packages/pyright-internal/src/localization/package.nls.de.json b/packages/pyright-internal/src/localization/package.nls.de.json index 5aa999e37d..46bdf3580a 100644 --- a/packages/pyright-internal/src/localization/package.nls.de.json +++ b/packages/pyright-internal/src/localization/package.nls.de.json @@ -252,7 +252,7 @@ "incompatibleMethodOverride": "Die Methode \"{name}\" überschreibt die Klasse \"{className}\" auf inkompatible Weise.", "inconsistentIndent": "Der Betrag für Nichteinzug stimmt nicht mit dem vorherigen Einzug überein.", "inconsistentTabs": "Inkonsistente Verwendung von Tabulatoren und Leerzeichen im Einzug.", - "initMethodSelfParamTypeVar": "Die Typanmerkung für den Parameter „self“ der Methode „__init__“ darf keine klassenbezogenen Typvariablen enthalten", + "initMethodSelfParamTypeVar": "Typ-Anmerkung für \"self\"-Parameter der \"__init__\"-Methode kann keine klassenübergreifenden Typvariablen enthalten", "initMustReturnNone": "Der Rückgabetyp von \"__init__\" muss \"None\" sein.", "initSubclassCallFailed": "Falsche Schlüsselwortargumente für __init_subclass__ Methode.", "initSubclassClsParam": "__init_subclass__ Außerkraftsetzung sollte einen \"cls\"-Parameter annehmen.", @@ -594,7 +594,7 @@ "unpackNotAllowed": "\"Unpack\" ist in diesem Kontext nicht zulässig.", "unpackOperatorNotAllowed": "Der Entpackvorgang ist in diesem Kontext nicht zulässig.", "unpackTuplesIllegal": "Der Entpackvorgang ist in Tupeln vor Python 3.8 nicht zulässig.", - "unpackedArgInTypeArgument": "Ungepackte Argumente können nicht in Typargumentlisten verwendet werden.", + "unpackedArgInTypeArgument": "Nicht gepackte Argumente können in diesem Kontext nicht verwendet werden.", "unpackedArgWithVariadicParam": "Das nicht gepackte Argument kann nicht für den Parameter \"TypeVarTuple\" verwendet werden.", "unpackedDictArgumentNotMapping": "Der Argumentausdruck nach ** muss eine Zuordnung mit dem Schlüsseltyp \"str\" sein.", "unpackedDictSubscriptIllegal": "Der Operator zum Entpacken des Wörterbuchs in tiefgestellten Zeichen ist nicht zulässig.", diff --git a/packages/pyright-internal/src/localization/package.nls.en-us.json b/packages/pyright-internal/src/localization/package.nls.en-us.json index 425dbb1707..fbbe59f95b 100644 --- a/packages/pyright-internal/src/localization/package.nls.en-us.json +++ b/packages/pyright-internal/src/localization/package.nls.en-us.json @@ -692,8 +692,8 @@ "missingGetter": "Property getter method is missing", "missingSetter": "Property setter method is missing", "missingDeleter": "Property deleter method is missing", - "namedParamMissingInDest": "Keyword parameter \"{name}\" is missing in destination", - "namedParamMissingInSource": "Keyword parameter \"{name}\" is missing in source", + "namedParamMissingInDest": "Extra parameter \"{name}\"", + "namedParamMissingInSource": "Missing keyword parameter \"{name}\"", "namedParamTypeMismatch": "Keyword parameter \"{name}\" of type \"{sourceType}\" is incompatible with type \"{destType}\"", "namedTupleNotAllowed": "NamedTuple cannot be used for instance or class checks", "newMethodLocation": "The __new__ method is defined in class \"{type}\"", diff --git a/packages/pyright-internal/src/localization/package.nls.es.json b/packages/pyright-internal/src/localization/package.nls.es.json index 2cb2038e65..835a8237ec 100644 --- a/packages/pyright-internal/src/localization/package.nls.es.json +++ b/packages/pyright-internal/src/localization/package.nls.es.json @@ -252,7 +252,7 @@ "incompatibleMethodOverride": "El método \"{name}\" sobrescribe la clase \"{className}\" de forma incompatible", "inconsistentIndent": "La cantidad sin sangría no coincide con la sangría anterior", "inconsistentTabs": "Uso incoherente de tabuladores y espacios en la sangría", - "initMethodSelfParamTypeVar": "La anotación de tipo para el parámetro \"self\" del método \"__init__\" no puede contener tipos con ámbito de clase.", + "initMethodSelfParamTypeVar": "La anotación de tipo para el parámetro \"self\" del método \"__init__\" no puede contener variables de tipo con ámbito de clase", "initMustReturnNone": "El tipo de retorno de \"__init__\" debe ser None", "initSubclassCallFailed": "Argumentos de palabra clave incorrectos para el método __init_subclass__", "initSubclassClsParam": "__init_subclass__ debe tomar un parámetro \"cls\"", @@ -594,7 +594,7 @@ "unpackNotAllowed": "El desempaquetado no está permitido en este contexto", "unpackOperatorNotAllowed": "La operación de desempaquetado no está permitida en este contexto", "unpackTuplesIllegal": "Operación de desempaquetado no permitida en tuplas anteriores a Python 3.8", - "unpackedArgInTypeArgument": "Los argumentos sin empaquetar no pueden utilizarse en listas de argumentos de tipo", + "unpackedArgInTypeArgument": "No se pueden usar argumentos sin empaquetar en este contexto", "unpackedArgWithVariadicParam": "No se puede usar un argumento desempaquetado para el parámetro TypeVarTuple", "unpackedDictArgumentNotMapping": "La expresión del argumento después de ** debe ser un mapeo con un tipo de clave \"str\".", "unpackedDictSubscriptIllegal": "El operador de desempaquetado del diccionario en el subíndice no está permitido", diff --git a/packages/pyright-internal/src/localization/package.nls.fr.json b/packages/pyright-internal/src/localization/package.nls.fr.json index 6b203476c0..25e683ce47 100644 --- a/packages/pyright-internal/src/localization/package.nls.fr.json +++ b/packages/pyright-internal/src/localization/package.nls.fr.json @@ -252,7 +252,7 @@ "incompatibleMethodOverride": "La méthode « {name} » remplace la classe « {className} » de manière incompatible", "inconsistentIndent": "Le montant du retrait ne correspond pas au retrait précédent", "inconsistentTabs": "Utilisation incohérente des onglets et des espaces dans la mise en retrait", - "initMethodSelfParamTypeVar": "L’annotation de type pour le paramètre « self » de la méthode « __init__ » ne peut pas contenir de variables de type dont l’étendue est une classe", + "initMethodSelfParamTypeVar": "L’annotation de type pour le paramètre « self » de la méthode « __init__ » ne peut pas contenir de variables de type de portée de classe", "initMustReturnNone": "Le type de retour de « __init__ » doit être None", "initSubclassCallFailed": "Arguments de mot clé incorrects pour la méthode __init_subclass__", "initSubclassClsParam": "__init_subclass__ remplacement doit prendre un paramètre « cls »", @@ -594,7 +594,7 @@ "unpackNotAllowed": "Le décompression n’est pas autorisé dans ce contexte", "unpackOperatorNotAllowed": "L’opération de décompression n’est pas autorisée dans ce contexte", "unpackTuplesIllegal": "Opération de décompression non autorisée dans les tuples avant Python 3.8", - "unpackedArgInTypeArgument": "Les arguments décompressés ne peuvent pas être utilisés dans les listes d’arguments de type", + "unpackedArgInTypeArgument": "Les arguments décompressés ne peuvent pas être utilisés dans ce contexte", "unpackedArgWithVariadicParam": "L'argument décompressé ne peut pas être utilisé pour le paramètre TypeVarTuple", "unpackedDictArgumentNotMapping": "L’expression d’argument après ** doit être un mappage avec un type de clé « str »", "unpackedDictSubscriptIllegal": "L’opérateur de décompression de dictionnaire dans l’indice n’est pas autorisé", diff --git a/packages/pyright-internal/src/localization/package.nls.it.json b/packages/pyright-internal/src/localization/package.nls.it.json index 1f35f91f11..e22c82540e 100644 --- a/packages/pyright-internal/src/localization/package.nls.it.json +++ b/packages/pyright-internal/src/localization/package.nls.it.json @@ -594,7 +594,7 @@ "unpackNotAllowed": "La decompressione non è consentita in questo contesto", "unpackOperatorNotAllowed": "L’operazione di decompressione non è consentita in questo contesto", "unpackTuplesIllegal": "L'operazione di decompressione non è consentita nelle tuple precedenti a Python 3.8", - "unpackedArgInTypeArgument": "Gli argomenti non compressi non possono essere usati negli elenchi di argomenti tipo", + "unpackedArgInTypeArgument": "Non è possibile usare argomenti decompressi in questo contesto", "unpackedArgWithVariadicParam": "Non è possibile usare l'argomento decompresso per il parametro TypeVarTuple", "unpackedDictArgumentNotMapping": "L'espressione dell'argomento dopo ** deve essere un mapping con un tipo di chiave \"str\"", "unpackedDictSubscriptIllegal": "L'operatore di decompressione del dizionario nel pedice non è consentito", diff --git a/packages/pyright-internal/src/localization/package.nls.ja.json b/packages/pyright-internal/src/localization/package.nls.ja.json index 46cfc1ce7b..20d456aa69 100644 --- a/packages/pyright-internal/src/localization/package.nls.ja.json +++ b/packages/pyright-internal/src/localization/package.nls.ja.json @@ -252,7 +252,7 @@ "incompatibleMethodOverride": "メソッド \"{name}\" は互換性のない方法でクラス \"{className}\" をオーバーライドします", "inconsistentIndent": "元のサイズが前のインデントと一致しません", "inconsistentTabs": "インデントでのタブとスペースの一貫性のない使用", - "initMethodSelfParamTypeVar": "\"__init__\" メソッドの \"self\" パラメーターの型注釈にクラス スコープ型の変数を含めることはできません", + "initMethodSelfParamTypeVar": "\"__init__\" メソッドの \"self\" パラメーターの型注釈に、クラス スコープ型の変数を含めることはできません", "initMustReturnNone": "\"__init__\" の戻り値の型は None でなければなりません", "initSubclassCallFailed": "__init_subclass__ メソッドのキーワード引数が正しくありません", "initSubclassClsParam": "__class_getitem__ override は \"cls\" パラメーターを受け取る必要があります", @@ -594,7 +594,7 @@ "unpackNotAllowed": "アンパックはこのコンテキストでは許可されていません", "unpackOperatorNotAllowed": "このコンテキストではアンパック操作は許可されていません", "unpackTuplesIllegal": "Python 3.8 より前のタプルではアンパック操作は許可されていません", - "unpackedArgInTypeArgument": "アンパックされた引数は、型引数リストでは使用できません", + "unpackedArgInTypeArgument": "アンパックされた引数は、このコンテキストでは使用できません", "unpackedArgWithVariadicParam": "アンパックされた引数は TypeVarTuple パラメーターには使用できません", "unpackedDictArgumentNotMapping": "** の後の引数式は、\"str\" キー型のマッピングである必要があります", "unpackedDictSubscriptIllegal": "下付き文字の辞書アンパック演算子は使用できません", diff --git a/packages/pyright-internal/src/localization/package.nls.ko.json b/packages/pyright-internal/src/localization/package.nls.ko.json index 3dfb75e0c2..1676bae954 100644 --- a/packages/pyright-internal/src/localization/package.nls.ko.json +++ b/packages/pyright-internal/src/localization/package.nls.ko.json @@ -594,7 +594,7 @@ "unpackNotAllowed": "이 컨텍스트에서는 압축 풀기가 허용되지 않습니다.", "unpackOperatorNotAllowed": "이 컨텍스트에서는 압축 풀기 작업이 허용되지 않습니다.", "unpackTuplesIllegal": "Python 3.8 이전의 튜플에서는 압축 풀기 작업이 허용되지 않습니다.", - "unpackedArgInTypeArgument": "압축되지 않은 인수는 형식 인수 목록에 사용할 수 없습니다.", + "unpackedArgInTypeArgument": "압축을 푼 인수는 이 컨텍스트에서 사용할 수 없음", "unpackedArgWithVariadicParam": "압축을 푼 인수는 TypeVarTuple 매개 변수에 사용할 수 없습니다.", "unpackedDictArgumentNotMapping": "** 뒤의 인수 식은 \"str\" 키 형식의 매핑이어야 합니다.", "unpackedDictSubscriptIllegal": "아래 첨자에서 사전 압축 풀기 연산자는 사용할 수 없습니다.", diff --git a/packages/pyright-internal/src/localization/package.nls.pl.json b/packages/pyright-internal/src/localization/package.nls.pl.json index dbd50f6ba1..133ffb5b6a 100644 --- a/packages/pyright-internal/src/localization/package.nls.pl.json +++ b/packages/pyright-internal/src/localization/package.nls.pl.json @@ -594,7 +594,7 @@ "unpackNotAllowed": "Rozpakowywanie jest niedozwolone w tym kontekście", "unpackOperatorNotAllowed": "Operacja rozpakowywania jest niedozwolona w tym kontekście", "unpackTuplesIllegal": "Operacja rozpakowywania nie jest dozwolona w krotkach przed językiem Python w wersji 3.8", - "unpackedArgInTypeArgument": "Rozpakowanych argumentów nie można używać na listach argumentów typu", + "unpackedArgInTypeArgument": "Nie można użyć nierozpakowanych argumentów w tym kontekście", "unpackedArgWithVariadicParam": "Nie można użyć nierozpakowanego argumentu dla parametru TypeVarTuple", "unpackedDictArgumentNotMapping": "Wyrażenie argumentu po znakach ** musi być mapowaniem z typem klucza „str”", "unpackedDictSubscriptIllegal": "Operator rozpakowywania słownika w indeksie dolnym jest niedozwolony", diff --git a/packages/pyright-internal/src/localization/package.nls.pt-br.json b/packages/pyright-internal/src/localization/package.nls.pt-br.json index 0e168b2cc2..373c3cf779 100644 --- a/packages/pyright-internal/src/localization/package.nls.pt-br.json +++ b/packages/pyright-internal/src/localization/package.nls.pt-br.json @@ -594,7 +594,7 @@ "unpackNotAllowed": "Descompactar não é permitido neste contexto", "unpackOperatorNotAllowed": "A operação de descompactação não é permitida neste contexto", "unpackTuplesIllegal": "Operação de desempacotamento não permitida em tuplas anteriores ao Python 3.8", - "unpackedArgInTypeArgument": "Argumentos desempacotamento não podem ser usados em listas de argumentos de tipo", + "unpackedArgInTypeArgument": "Os argumentos descompactados não podem ser usados nesse contexto", "unpackedArgWithVariadicParam": "O argumento desempacotado não pode ser usado para o parâmetro TypeVarTuple", "unpackedDictArgumentNotMapping": "A expressão de argumento após ** deve ser um mapeamento com um tipo de chave \"str\"", "unpackedDictSubscriptIllegal": "O operador de desempacotamento de dicionário no subscrito não é permitido", diff --git a/packages/pyright-internal/src/localization/package.nls.qps-ploc.json b/packages/pyright-internal/src/localization/package.nls.qps-ploc.json index adc141cd75..1e9afc9bef 100644 --- a/packages/pyright-internal/src/localization/package.nls.qps-ploc.json +++ b/packages/pyright-internal/src/localization/package.nls.qps-ploc.json @@ -252,7 +252,7 @@ "incompatibleMethodOverride": "[i45Ka][นั้Mëthøð \"{ñæmë}\" øvërrïðës çlæss \"{çlæssÑæmë}\" ïñ æñ ïñçømpætïþlë mæññërẤğ倪İЂҰक्र्तिृまẤğ倪İЂҰक्र्तिृまนั้ढूँ]", "inconsistentIndent": "[gdrcy][นั้Üñïñðëñt æmøµñt ðøës ñøt mætçh prëvïøµs ïñðëñtẤğ倪İЂҰक्र्तिृまẤğ倪İนั้ढूँ]", "inconsistentTabs": "[I3Z6K][นั้Ïñçøñsïstëñt µsë øf tæþs æñð spæçës ïñ ïñðëñtætïøñẤğ倪İЂҰक्र्तिृまẤğ倪İЂҰนั้ढूँ]", - "initMethodSelfParamTypeVar": "[S5RC7][นั้Tÿpë æññøtætïøñ før \"sëlf\" pæræmëtër øf \"__ïñït__\" mëthøð çæññøt çøñtæïñ çlæss-sçøpëð tÿpë væræïæþlësẤğ倪İЂҰक्र्तिृまẤğ倪İЂҰक्र्तिृまẤğ倪İЂҰक्र्तिृนั้ढूँ]", + "initMethodSelfParamTypeVar": "[S5RC7][นั้Tÿpë æññøtætïøñ før \"sëlf\" pæræmëtër øf \"__ïñït__\" mëthøð çæññøt çøñtæïñ çlæss-sçøpëð tÿpë værïæþlësẤğ倪İЂҰक्र्तिृまẤğ倪İЂҰक्र्तिृまẤğ倪İЂҰक्र्तिृนั้ढूँ]", "initMustReturnNone": "[RlXyC][นั้Rëtµrñ tÿpë øf \"__ïñït__\" mµst þë ÑøñëẤğ倪İЂҰक्र्तिृまẤนั้ढूँ]", "initSubclassCallFailed": "[w22Kh][นั้Ïñçørrëçt këÿwørð ærgµmëñts før __ïñït_sµþçlæss__ mëthøðẤğ倪İЂҰक्र्तिृまẤğ倪İЂҰक्นั้ढूँ]", "initSubclassClsParam": "[6CWuS][นั้__ïñït_sµþçlæss__ øvërrïðë shøµlð tækë æ \"çls\" pæræmëtërẤğ倪İЂҰक्र्तिृまẤğ倪İЂҰक्นั้ढूँ]", @@ -594,7 +594,7 @@ "unpackNotAllowed": "[MZq6e][นั้Üñpæçk ïs ñøt ælløwëð ïñ thïs çøñtëxtẤğ倪İЂҰक्र्तिृまẤนั้ढूँ]", "unpackOperatorNotAllowed": "[lMq2B][นั้Üñpæçk øpërætïøñ ïs ñøt ælløwëð ïñ thïs çøñtëxtẤğ倪İЂҰक्र्तिृまẤğ倪İЂนั้ढूँ]", "unpackTuplesIllegal": "[RJvzW][นั้Üñpæçk øpërætïøñ ñøt ælløwëð ïñ tµplës prïør tø Pÿthøñ 3.8Ấğ倪İЂҰक्र्तिृまẤğ倪İЂҰक्र्นั้ढूँ]", - "unpackedArgInTypeArgument": "[skxlo][นั้Üñpæçkëð ærgµmëñts çæññøt þë µsëð ïñ tÿpë ærgµmëñt lïstsẤğ倪İЂҰक्र्तिृまẤğ倪İЂҰक्นั้ढूँ]", + "unpackedArgInTypeArgument": "[skxlo][นั้Üñpæçkëð ærgµmëñts çæññøt þë µsëð ïñ thïs çøñtëxtẤğ倪İЂҰक्र्तिृまẤğ倪İЂҰนั้ढूँ]", "unpackedArgWithVariadicParam": "[ZP3kP][นั้Üñpæçkëð ærgµmëñt çæññøt þë µsëð før TÿpëVærTµplë pæræmëtërẤğ倪İЂҰक्र्तिृまẤğ倪İЂҰक्र्นั้ढूँ]", "unpackedDictArgumentNotMapping": "[iSTnU][นั้Ærgµmëñt ëxprëssïøñ æftër ** mµst þë æ mæppïñg wïth æ \"str\" këÿ tÿpëẤğ倪İЂҰक्र्तिृまẤğ倪İЂҰक्र्तिृนั้ढूँ]", "unpackedDictSubscriptIllegal": "[slATr][นั้Ðïçtïøñærÿ µñpæçk øpërætør ïñ sµþsçrïpt ïs ñøt ælløwëðẤğ倪İЂҰक्र्तिृまẤğ倪İЂҰक्นั้ढूँ]", diff --git a/packages/pyright-internal/src/localization/package.nls.ru.json b/packages/pyright-internal/src/localization/package.nls.ru.json index d4e9d6acd6..2644548802 100644 --- a/packages/pyright-internal/src/localization/package.nls.ru.json +++ b/packages/pyright-internal/src/localization/package.nls.ru.json @@ -252,7 +252,7 @@ "incompatibleMethodOverride": "Метод \"{name}\" переопределяет класс \"{className}\" несовместимым образом", "inconsistentIndent": "Сумма отступа не соответствует предыдущему отступу", "inconsistentTabs": "Непоследовательное использование вкладок и пробелов в отступах", - "initMethodSelfParamTypeVar": "Аннотация типа для параметра \"self\" метода \"__init__\" не может содержать переменные типов с областью класса", + "initMethodSelfParamTypeVar": "Заметка типа для параметра \"self\" метода \"__init__\" не может содержать переменные типа в области класса.", "initMustReturnNone": "Метод \"__init__\" должен возвращать тип None", "initSubclassCallFailed": "Неверные аргументы ключевых слов для метода __init_subclass__", "initSubclassClsParam": "Переопределение метода __init_subclass__ должно принимать параметр \"cls\"", @@ -594,7 +594,7 @@ "unpackNotAllowed": "Распаковка допускается в этом контексте", "unpackOperatorNotAllowed": "Операция распаковки допускается в этом контексте", "unpackTuplesIllegal": "Операцию распаковки в кортежах можно использовать в Python версии не ниже 3.8", - "unpackedArgInTypeArgument": "Неупакованные аргументы нельзя использовать в списках аргументов типа", + "unpackedArgInTypeArgument": "В этом контексте нельзя использовать распакованные аргументы", "unpackedArgWithVariadicParam": "Невозможно использовать распакованный аргумент для параметра TypeVarTuple", "unpackedDictArgumentNotMapping": "Выражение аргумента после ** должно быть сопоставлением с типом ключа \"str\".", "unpackedDictSubscriptIllegal": "Оператор распаковки словаря не допускается внутри операции взятия подстроки", diff --git a/packages/pyright-internal/src/localization/package.nls.tr.json b/packages/pyright-internal/src/localization/package.nls.tr.json index dfb548067c..a42efcc479 100644 --- a/packages/pyright-internal/src/localization/package.nls.tr.json +++ b/packages/pyright-internal/src/localization/package.nls.tr.json @@ -594,7 +594,7 @@ "unpackNotAllowed": "Bu bağlamda paketi açma işlemine izin verilmiyor", "unpackOperatorNotAllowed": "Bu bağlamda paket açma işlemi kullanılamaz", "unpackTuplesIllegal": "Python 3.8'den önceki demetler içinde paket açma işlemi kullanılamıyor", - "unpackedArgInTypeArgument": "Paketlenmemiş bağımsız değişkenler tür bağımsız değişkeni listelerinde kullanılamaz", + "unpackedArgInTypeArgument": "Paketlenmemiş bağımsız değişkenler bu bağlamda kullanılamaz", "unpackedArgWithVariadicParam": "Paketlenmemiş bağımsız değişken, TypeVarTuple parametresi için kullanılamaz", "unpackedDictArgumentNotMapping": "** sonrasındaki bağımsız değişken ifadesi \"str\" anahtar türüne sahip bir eşleme olmalıdır", "unpackedDictSubscriptIllegal": "Alt simgede sözlük açma işlecine izin verilmiyor", diff --git a/packages/pyright-internal/src/localization/package.nls.zh-cn.json b/packages/pyright-internal/src/localization/package.nls.zh-cn.json index c71d1f2e08..dd3461a23d 100644 --- a/packages/pyright-internal/src/localization/package.nls.zh-cn.json +++ b/packages/pyright-internal/src/localization/package.nls.zh-cn.json @@ -252,7 +252,7 @@ "incompatibleMethodOverride": "方法“{name}”以不兼容的方式替代类“{className}”", "inconsistentIndent": "取消缩进量与以前的缩进不匹配", "inconsistentTabs": "缩进中制表符和空格的使用不一致", - "initMethodSelfParamTypeVar": "\"__init__\" 方法 “self” 参数的类型批注不能包含类范围的类型变量", + "initMethodSelfParamTypeVar": "\"__init__\" 方法 “self” 参数的类型注释不能包含类范围的类型变量", "initMustReturnNone": "“__init__”的返回类型必须为 None", "initSubclassCallFailed": "__init_subclass__ 方法的关键字参数不正确", "initSubclassClsParam": "__init_subclass__替代应采用“cls”参数", @@ -594,7 +594,7 @@ "unpackNotAllowed": "此上下文中不允许解包", "unpackOperatorNotAllowed": "此上下文中不允许解压缩操作", "unpackTuplesIllegal": "Python 3.8 之前的元组中不允许解包操作", - "unpackedArgInTypeArgument": "无法在类型参数列表中使用未打包的参数", + "unpackedArgInTypeArgument": "未打包的参数不能用于此上下文", "unpackedArgWithVariadicParam": "未打包的参数不能用于 TypeVarTuple 参数", "unpackedDictArgumentNotMapping": "** 后面的参数表达式必须是具有“str”键类型的映射", "unpackedDictSubscriptIllegal": "不允许在下标中使用字典解包运算符", diff --git a/packages/pyright-internal/src/localization/package.nls.zh-tw.json b/packages/pyright-internal/src/localization/package.nls.zh-tw.json index f9cd68ff29..0d7eac2982 100644 --- a/packages/pyright-internal/src/localization/package.nls.zh-tw.json +++ b/packages/pyright-internal/src/localization/package.nls.zh-tw.json @@ -594,7 +594,7 @@ "unpackNotAllowed": "此內容中不允許解壓縮", "unpackOperatorNotAllowed": "此內容中不允許解壓縮作業", "unpackTuplesIllegal": "Python 3.8 之前的 Tuple 中不允許解壓縮作業", - "unpackedArgInTypeArgument": "解壓縮的引數不能用於類型引數清單", + "unpackedArgInTypeArgument": "無法在此內容中使用未封裝的引數", "unpackedArgWithVariadicParam": "未封裝的引數無法用於 TypeVarTuple 參數", "unpackedDictArgumentNotMapping": "** 後的引數運算式必須是具有 \"str\" 金鑰類型的對應", "unpackedDictSubscriptIllegal": "字典解壓縮運算子在下標中不被允許", diff --git a/packages/pyright-internal/src/parser/tokenizer.ts b/packages/pyright-internal/src/parser/tokenizer.ts index 5cadb428a3..af95673940 100644 --- a/packages/pyright-internal/src/parser/tokenizer.ts +++ b/packages/pyright-internal/src/parser/tokenizer.ts @@ -1280,7 +1280,7 @@ export class Tokenizer { const length = this._cs.position - start; const comment = Comment.create(start, length, this._cs.getText().slice(start, start + length)); - const typeIgnoreRegexMatch = comment.value.match(/((^|#)\s*)type:\s*ignore(\s*\[([\s*\w-,]*)\]|\s|$)/); + const typeIgnoreRegexMatch = comment.value.match(/((^|#)\s*)type:\s*ignore(\s*\[([\s\w-,]*)\]|\s|$)/); if (typeIgnoreRegexMatch) { const commentStart = start + (typeIgnoreRegexMatch.index ?? 0); const textRange: TextRange = { @@ -1299,7 +1299,7 @@ export class Tokenizer { } } - const pyrightIgnoreRegexMatch = comment.value.match(/((^|#)\s*)pyright:\s*ignore(\s*\[([\s*\w-,]*)\]|\s|$)/); + const pyrightIgnoreRegexMatch = comment.value.match(/((^|#)\s*)pyright:\s*ignore(\s*\[([\s\w-,]*)\]|\s|$)/); if (pyrightIgnoreRegexMatch) { const commentStart = start + (pyrightIgnoreRegexMatch.index ?? 0); const textRange: TextRange = { diff --git a/packages/pyright-internal/src/pyright.ts b/packages/pyright-internal/src/pyright.ts index b809430a42..221a20aa7f 100644 --- a/packages/pyright-internal/src/pyright.ts +++ b/packages/pyright-internal/src/pyright.ts @@ -460,7 +460,7 @@ async function processArgs(): Promise { } } - if (args.createstub && results.filesRequiringAnalysis === 0) { + if (args.createstub && results.requiringAnalysisCount.files === 0) { try { service.writeTypeStub(cancellationNone); service.dispose(); diff --git a/packages/pyright-internal/src/tests/config.test.ts b/packages/pyright-internal/src/tests/config.test.ts index 102c317746..cb0627835b 100644 --- a/packages/pyright-internal/src/tests/config.test.ts +++ b/packages/pyright-internal/src/tests/config.test.ts @@ -218,7 +218,7 @@ test('PythonPlatform', () => { const nullConsole = new NullConsole(); const sp = createServiceProvider(fs, nullConsole); - configOptions.initializeFromJson(json, undefined, sp, new NoAccessHost()); + configOptions.initializeFromJson(json, cwd, sp, new NoAccessHost()); const env = configOptions.executionEnvironments[0]; assert.strictEqual(env.pythonPlatform, 'platform'); @@ -353,10 +353,9 @@ test('both pyright and basedpyright in pyproject.toml', () => { }); test('invalid option value in pyproject.toml', () => { - const { configOptions, analysisResult } = setupPyprojectToml( + const analysisResult = setupPyprojectToml( 'src/tests/samples/project_with_invalid_option_value_in_pyproject_toml' - ); - assert.strictEqual(configOptions.typeCheckingMode, undefined); + ).analysisResult; assert(analysisResult?.configParseErrorOccurred); assert(!analysisResult.fatalErrorOccurred); }); @@ -392,9 +391,10 @@ test('verify config fileSpecs after cloning', () => { ignore: ['**/node_modules/**'], }; - const config = new ConfigOptions(Uri.file(process.cwd(), fs)); + const rootUri = Uri.file(process.cwd(), fs); + const config = new ConfigOptions(rootUri); const sp = createServiceProvider(fs, new NullConsole()); - config.initializeFromJson(configFile, undefined, sp, new TestAccessHost()); + config.initializeFromJson(configFile, rootUri, sp, new TestAccessHost()); const cloned = deserialize(serialize(config)); assert.deepEqual(config.ignore, cloned.ignore); @@ -423,3 +423,18 @@ test('extra paths on undefined execution root/default workspace', () => { expectedExtraPaths.map((u) => u.getFilePath()) ); }); + +test('Extended config files', () => { + const cwd = normalizePath(combinePaths(process.cwd(), 'src/tests/samples/project_with_extended_config')); + const service = createAnalyzer(); + const commandLineOptions = new CommandLineOptions(cwd, /* fromVsCodeExtension */ true); + + service.setOptions(commandLineOptions); + + const fileList = service.test_getFileNamesFromFileSpecs(); + const fileNames = fileList.map((p) => p.fileName).sort(); + assert.deepStrictEqual(fileNames, ['sample.pyi', 'test.py']); + + const configOptions = service.test_getConfigOptions(commandLineOptions); + assert.equal(configOptions.diagnosticRuleSet.strictListInference, true); +}); diff --git a/packages/pyright-internal/src/tests/harness/fourslash/testState.ts b/packages/pyright-internal/src/tests/harness/fourslash/testState.ts index ddddabc5f0..ebe5123cb6 100644 --- a/packages/pyright-internal/src/tests/harness/fourslash/testState.ts +++ b/packages/pyright-internal/src/tests/harness/fourslash/testState.ts @@ -180,7 +180,13 @@ export class TestState { const configOptions = this._convertGlobalOptionsToConfigOptions(vfsInfo.projectRoot, mountPaths); if (this.rawConfigJson) { - configOptions.initializeFromJson(this.rawConfigJson, 'standard', this.serviceProvider, testAccessHost); + configOptions.initializeTypeCheckingMode('standard'); + configOptions.initializeFromJson( + this.rawConfigJson, + Uri.file(projectRoot, this.serviceProvider), + this.serviceProvider, + testAccessHost + ); this._applyTestConfigOptions(configOptions); } diff --git a/packages/pyright-internal/src/tests/harness/fourslash/testStateUtils.ts b/packages/pyright-internal/src/tests/harness/fourslash/testStateUtils.ts index 9bbde1b403..53c2b1d096 100644 --- a/packages/pyright-internal/src/tests/harness/fourslash/testStateUtils.ts +++ b/packages/pyright-internal/src/tests/harness/fourslash/testStateUtils.ts @@ -9,7 +9,7 @@ import assert from 'assert'; import * as JSONC from 'jsonc-parser'; -import { configFileNames } from '../../../analyzer/service'; +import { configFileName } from '../../../analyzer/service'; import { Comparison, toBoolean } from '../../../common/core'; import { combinePaths, getBaseFileName } from '../../../common/pathUtils'; import { getStringComparer } from '../../../common/stringUtils'; @@ -76,5 +76,5 @@ export function getMarkerNames(testData: FourSlashData): string[] { function isConfig(file: FourSlashFile, ignoreCase: boolean): boolean { const comparer = getStringComparer(ignoreCase); - return configFileNames.some((f) => comparer(getBaseFileName(file.fileName), f) === Comparison.EqualTo); + return comparer(getBaseFileName(file.fileName), configFileName) === Comparison.EqualTo; } diff --git a/packages/pyright-internal/src/tests/hoverProvider.test.ts b/packages/pyright-internal/src/tests/hoverProvider.test.ts index 1a81534049..a2527dac27 100644 --- a/packages/pyright-internal/src/tests/hoverProvider.test.ts +++ b/packages/pyright-internal/src/tests/hoverProvider.test.ts @@ -359,3 +359,23 @@ test('import symbol tooltip - useLibraryCodeForTypes true', async () => { marker1: '```python\n(class) bar\n```', }); }); + +test('TypedDict doc string', async () => { + const code = ` +// @filename: test.py +//// from typing import [|/*marker*/TypedDict|] + +// @filename: typing.py +// @library: true +//// def TypedDict(typename, fields=None, /, *, total=True, **kwargs): +//// """A simple typed namespace. At runtime it is equivalent to a plain dict.""" + `; + + const state = parseAndGetTestState(code).state; + const marker1 = state.getMarkerByName('marker'); + state.openFile(marker1.fileName); + + state.verifyHover('markdown', { + marker: '```python\n(class) TypedDict\n```\n---\nA simple typed namespace. At runtime it is equivalent to a plain dict.', + }); +}); diff --git a/packages/pyright-internal/src/tests/samples/callbackProtocol1.py b/packages/pyright-internal/src/tests/samples/callbackProtocol1.py index fd6b615673..23958f7470 100644 --- a/packages/pyright-internal/src/tests/samples/callbackProtocol1.py +++ b/packages/pyright-internal/src/tests/samples/callbackProtocol1.py @@ -76,6 +76,15 @@ def func5(**b: str): var2 = func5 +class NotProto: + def __call__(self, *vals: bytes, maxlen: int | None = None) -> list[bytes]: + return [] + + +# This should generate an error because NotProto is not a protocol class. +not_proto: NotProto = good_cb + + class TestClass3(Protocol): def __call__(self) -> None: pass @@ -96,30 +105,44 @@ def __call__(self, x: int) -> None: pass -def func6(x: int) -> None: +def test_func4(x: int) -> None: pass # This should generate an error. -var4: TestClass4 = func6 +var4: TestClass4 = test_func4 class TestClass5(Protocol): - def __call__(self, *, a: int, b: str) -> int: - ... + def __call__(self, *, a: int, b: str) -> int: ... -def func7(a: int, b: str) -> int: +def test_func5(a: int, b: str) -> int: return 123 -f: TestClass5 = func7 +f5: TestClass5 = test_func5 -class TestClass6: - def __call__(self, *vals: bytes, maxlen: int | None = None) -> list[bytes]: - return [] +class TestClass6(Protocol): + def __call__(self, a: int, /, *, b: str) -> int: ... + + +def test_func6(a: int, b: str) -> int: + return 123 -# This should generate an error because TestClass6 is not a protocol class. -var6: TestClass6 = good_cb +f6: TestClass6 = test_func6 + + +class TestClass7: + def __call__(self) -> None: + pass + + +def test_func7(*args: * tuple[int, *tuple[int, ...]]) -> int: + return 123 + + +# This should generate an error. +f7: TestClass7 = test_func7 diff --git a/packages/pyright-internal/src/tests/samples/comprehension11.py b/packages/pyright-internal/src/tests/samples/comprehension11.py index 311c146f4a..34717f076b 100644 --- a/packages/pyright-internal/src/tests/samples/comprehension11.py +++ b/packages/pyright-internal/src/tests/samples/comprehension11.py @@ -15,4 +15,4 @@ for meridian in ("am", "pm") ) ] -reveal_type(times, expected_text="list[tuple[int, int, Literal['am', 'pm']]]") +reveal_type(times, expected_text="list[tuple[int, int, str]]") diff --git a/packages/pyright-internal/src/tests/samples/enum1.py b/packages/pyright-internal/src/tests/samples/enum1.py index b122b4d9d4..54546e76e5 100644 --- a/packages/pyright-internal/src/tests/samples/enum1.py +++ b/packages/pyright-internal/src/tests/samples/enum1.py @@ -217,7 +217,7 @@ def __init__(self, value: int): reveal_type(te15_A._value_, expected_text="str") -class TestEnum16(Enum): +class TestEnum16(IntEnum): A = 1 B = 2 C = 3 diff --git a/packages/pyright-internal/src/tests/samples/final3.py b/packages/pyright-internal/src/tests/samples/final3.py index 0edd6d82b1..f4f819b6be 100644 --- a/packages/pyright-internal/src/tests/samples/final3.py +++ b/packages/pyright-internal/src/tests/samples/final3.py @@ -122,6 +122,8 @@ def func1(a: Final[int]): class ClassA: member1: Final = 3 member2: Final + member4: Final + member5: Final = 3 def __init__(self): # This should generate an error. @@ -131,6 +133,12 @@ def __init__(self): self.member3: Final = "hi" + # This should generate an error. + ClassA.member4 = "hi" + + # This should generate an error. + ClassA.member5 = 3 + def other(self): # This should generate an error. self.member1 = 5 diff --git a/packages/pyright-internal/src/tests/samples/generator14.py b/packages/pyright-internal/src/tests/samples/generator14.py index b923bc360e..65df1d3cbe 100644 --- a/packages/pyright-internal/src/tests/samples/generator14.py +++ b/packages/pyright-internal/src/tests/samples/generator14.py @@ -1,8 +1,7 @@ # This sample tests the inferred type of async and sync generators. -async def foo() -> int: - ... +async def foo() -> int: ... async def main() -> None: @@ -19,7 +18,7 @@ async def main() -> None: reveal_type(v4, expected_text="AsyncGenerator[int, None]") v5 = ((0, await foo()) for _ in [1, 2]) - reveal_type(v5, expected_text="AsyncGenerator[tuple[Literal[0], int], None]") + reveal_type(v5, expected_text="AsyncGenerator[tuple[int, int], None]") v6 = (x for x in [1, 2] if (x, await foo())) reveal_type(v6, expected_text="AsyncGenerator[int, None]") diff --git a/packages/pyright-internal/src/tests/samples/project_with_extended_config/baseconfig1.toml b/packages/pyright-internal/src/tests/samples/project_with_extended_config/baseconfig1.toml new file mode 100644 index 0000000000..face763401 --- /dev/null +++ b/packages/pyright-internal/src/tests/samples/project_with_extended_config/baseconfig1.toml @@ -0,0 +1,3 @@ +[tool.pyright] +extends = "sub2/baseconfig2.json" +typeCheckingMode = "strict" diff --git a/packages/pyright-internal/src/tests/samples/project_with_extended_config/pyproject.toml b/packages/pyright-internal/src/tests/samples/project_with_extended_config/pyproject.toml new file mode 100644 index 0000000000..784825fbcf --- /dev/null +++ b/packages/pyright-internal/src/tests/samples/project_with_extended_config/pyproject.toml @@ -0,0 +1,2 @@ +[tool.pyright] +extends = "baseconfig1.toml" diff --git a/packages/pyright-internal/src/tests/samples/project_with_extended_config/sub2/baseconfig2.json b/packages/pyright-internal/src/tests/samples/project_with_extended_config/sub2/baseconfig2.json new file mode 100644 index 0000000000..65c0afdf0b --- /dev/null +++ b/packages/pyright-internal/src/tests/samples/project_with_extended_config/sub2/baseconfig2.json @@ -0,0 +1,4 @@ +{ + "extends": "../sub3/baseconfig3.json", + "typeCheckingMode": "standard" +} \ No newline at end of file diff --git a/packages/pyright-internal/src/tests/samples/project_with_extended_config/sub3/baseconfig3.json b/packages/pyright-internal/src/tests/samples/project_with_extended_config/sub3/baseconfig3.json new file mode 100644 index 0000000000..33f7b91718 --- /dev/null +++ b/packages/pyright-internal/src/tests/samples/project_with_extended_config/sub3/baseconfig3.json @@ -0,0 +1,4 @@ +{ + "stubPath": "stubs", + "typeCheckingMode": "basic" +} diff --git a/packages/pyright-internal/src/tests/samples/project_with_extended_config/sub3/stubs/sample.pyi b/packages/pyright-internal/src/tests/samples/project_with_extended_config/sub3/stubs/sample.pyi new file mode 100644 index 0000000000..957223fd35 --- /dev/null +++ b/packages/pyright-internal/src/tests/samples/project_with_extended_config/sub3/stubs/sample.pyi @@ -0,0 +1 @@ +x: int diff --git a/packages/pyright-internal/src/tests/samples/project_with_extended_config/test.py b/packages/pyright-internal/src/tests/samples/project_with_extended_config/test.py new file mode 100644 index 0000000000..80c06c44f0 --- /dev/null +++ b/packages/pyright-internal/src/tests/samples/project_with_extended_config/test.py @@ -0,0 +1,7 @@ +# pyright: reportMissingModuleSource=false + +from typing import assert_type +from sample import x + +assert_type(x, int) + diff --git a/packages/pyright-internal/src/tests/typeEvaluator2.test.ts b/packages/pyright-internal/src/tests/typeEvaluator2.test.ts index ecaf243481..234ae3926d 100644 --- a/packages/pyright-internal/src/tests/typeEvaluator2.test.ts +++ b/packages/pyright-internal/src/tests/typeEvaluator2.test.ts @@ -17,7 +17,7 @@ import * as TestUtils from './testUtils'; test('CallbackProtocol1', () => { const analysisResults = TestUtils.typeAnalyzeSampleFiles(['callbackProtocol1.py']); - TestUtils.validateResults(analysisResults, 9); + TestUtils.validateResults(analysisResults, 10); }); test('CallbackProtocol2', () => { diff --git a/packages/pyright-internal/src/tests/typeEvaluator4.test.ts b/packages/pyright-internal/src/tests/typeEvaluator4.test.ts index 037404e89c..0400cb727c 100644 --- a/packages/pyright-internal/src/tests/typeEvaluator4.test.ts +++ b/packages/pyright-internal/src/tests/typeEvaluator4.test.ts @@ -33,7 +33,7 @@ test('Final2', () => { test('Final3', () => { const analysisResults = TestUtils.typeAnalyzeSampleFiles(['final3.py']); - TestUtils.validateResults(analysisResults, 34); + TestUtils.validateResults(analysisResults, 36); }); test('Final4', () => { diff --git a/packages/pyright-internal/src/tests/typeEvaluator7.test.ts b/packages/pyright-internal/src/tests/typeEvaluator7.test.ts index fd42f76992..1e36abef48 100644 --- a/packages/pyright-internal/src/tests/typeEvaluator7.test.ts +++ b/packages/pyright-internal/src/tests/typeEvaluator7.test.ts @@ -742,7 +742,7 @@ test('TypedDictInline1', () => { configOptions.diagnosticRuleSet.enableExperimentalFeatures = true; const analysisResults = TestUtils.typeAnalyzeSampleFiles(['typedDictInline1.py'], configOptions); - TestUtils.validateResults(analysisResults, 8); + TestUtils.validateResults(analysisResults, 9); }); test('ClassVar1', () => { diff --git a/packages/pyright/package-lock.json b/packages/pyright/package-lock.json index ade1b8b178..cc4ea71cf4 100644 --- a/packages/pyright/package-lock.json +++ b/packages/pyright/package-lock.json @@ -1,12 +1,12 @@ { "name": "basedpyright", - "version": "1.1.364", + "version": "1.1.365", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "basedpyright", - "version": "1.1.364", + "version": "1.1.365", "license": "MIT", "bin": { "pyright": "index.js", diff --git a/packages/pyright/package.json b/packages/pyright/package.json index 9ee0b20993..83a94adb31 100644 --- a/packages/pyright/package.json +++ b/packages/pyright/package.json @@ -2,7 +2,7 @@ "name": "basedpyright", "displayName": "basedpyright", "description": "a pyright fork with various type checking improvements, improved vscode support and pylance features built into the language server", - "version": "1.1.364", + "version": "1.1.365", "license": "MIT", "author": { "name": "detachhead" diff --git a/packages/vscode-pyright/package-lock.json b/packages/vscode-pyright/package-lock.json index 880121f87e..3829d7f9f8 100644 --- a/packages/vscode-pyright/package-lock.json +++ b/packages/vscode-pyright/package-lock.json @@ -1,12 +1,12 @@ { "name": "vscode-pyright", - "version": "1.1.364", + "version": "1.1.365", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "vscode-pyright", - "version": "1.1.364", + "version": "1.1.365", "license": "MIT", "dependencies": { "@vscode/python-extension": "^1.0.5", diff --git a/packages/vscode-pyright/package.json b/packages/vscode-pyright/package.json index 8566a8460f..31a3d033b0 100644 --- a/packages/vscode-pyright/package.json +++ b/packages/vscode-pyright/package.json @@ -2,7 +2,7 @@ "name": "vscode-pyright", "displayName": "BasedPyright", "description": "VS Code static type checking for Python (but based)", - "version": "1.1.364", + "version": "1.1.365", "private": true, "license": "MIT", "author": { diff --git a/packages/vscode-pyright/schemas/pyrightconfig.schema.json b/packages/vscode-pyright/schemas/pyrightconfig.schema.json index 1673e8eb55..427dd74c1c 100644 --- a/packages/vscode-pyright/schemas/pyrightconfig.schema.json +++ b/packages/vscode-pyright/schemas/pyrightconfig.schema.json @@ -56,6 +56,12 @@ } }, "properties": { + "extends": { + "$id": "#/properties/extends", + "type": "string", + "title": "Path to configuration file that this configuration extends", + "pattern": "^(.*)$" + }, "include": { "$id": "#/properties/include", "type": "array",