microsoft / pylance-release

Documentation and issues for Pylance
Creative Commons Attribution 4.0 International
1.7k stars 769 forks source link

Proposal: Use Nearest Configuration File #5564

Open DanielRosenwasser opened 6 months ago

DanielRosenwasser commented 6 months ago

Background

Today, the way that Pylance and Pyright work is that the editor experience only respects the options in pyrightconfig.json or pyproject.toml if they reside in a workspace root. Unfortunately this can be very confusing, and creates a lot of friction in specific workflows.

Scenario: Multi-Language Setups

.
├── javascript
│   └── package.json
└── python
    ├── pyproject.toml
    ├── package.json
    ├── pyrightconfig.json
    └── src
        └── package
            └── *.py

Here, every different language setup is sufficiently self-contained; however, a developer navigating such a codebase will not have their settings reflected in their editing experience out-of-the-box.

One could move their pyproject.toml or pyrightconfig.json to the top-level, but then the Python-specific package.json would not be co-located with the other Python-specific configuration files.

Scenario: Scripts and Examples

.
├── pyproject.toml
├── src
│   └── package
│       └── *.py
├── scripts
│   ├── pyrightconfig.json
│   └── *.py
└── examples
    ├── example1
    │   ├── pyrightconfig.json
    │   └── *.py
    └── example2
        ├── pyrightconfig.json
        └── *.py

When developing a library or shared codebase, it's common for examples and scripts to have different type-checking settings. For example,

However, when opening this directory, Pylance will only consult the top-level pyproject.toml, making such a setup impossible. While the strict option for specifying globs of directories works okay, it doesn't offer anything more granular.

Overall Pain Points

In these workflows, a developer who wants their editor experience to align with their type-checking needs to open a specific subdirectory up rather than the package root. This runs counter to lots of out-of-the-box expectations when cloning a repo and opening a directory, or creating a Codespace/container session, and things just working after some configuration step.

Additionally, when things don't work correctly (e.g. strict type checking not kicking in), there's no obvious way to know when the configuration file is (or isn't) being consulted.

Why Not Multi-Root Workspaces?

The most obvious alternative is multi-root workspaces which kind of solves the issue for the first motivating scenario, but not so much for the second.

Multi-root workspaces also require developers to reopen a directory in a specific way and fundamentally alter the editor UI which makes things a bit weird when the codebase root has to contain both itself and its subdirectories as roots.

Additionally, it is a sort of non-obvious fix for the problem at hand because the current behavior of configuration being tied to workspace roots is surprising in itself. Many users may dismiss the "we found a workspace file" message that VS Code issues, which is a non-obvious footgun when things don't behave correctly.

As a TypeScript developer, multi-root workspaces are not a typical requirement for structuring a codebase. In fact, the same is now becoming true for Go developers as well. Needing to add a workspace file just for Python is an awkward asymmetry.

Proposal

Search for Nearest Configuration File

I would propose that the Pyright/Pylance editing experience adopt an approach where pyrightconfig.json and pyproject.toml files are searched by walking upward in the directory chain. Upon opening a Python file, the language service would need to decide on which project "owns" a file (though note that in some cases, a file might be owned by multiple projects).

In TypeScript, this problem has been tackled in a similar fashion, walking up the spine of directories looking for tsconfig.json and jsconfig.json files, rather than just consulting the workspace root. For the most part, this flexibility has worked out well for us and matches most users' intuitions. The proposal here is very similar in spirit.

UI for "Find Configuration"

In addition to this, the TypeScript/JavaScript editor integration in Visual Studio Code also provides a command called TypeScript: Go to Project Configuration (tsconfig).

TypeScript: Go to Project Configuration (tsconfig)

There is also an indicator for the configuration file path in the status bar, with an option to open that file.

When clicking the status bar icon, the editor shows the path of the tsconfig.json and lets you open that file.

I would suggest adopting similar UI/conventions here to make configuration more discoverable and diagnosable.

rchiodo commented 6 months ago

This would be rather hard for us to implement given our current design.

Each workspace folder shares settings. We have no facility for having different settings per python file or subdirectory.

jakebailey commented 6 months ago

Given you can only have one pyrightconfig per dir, can you pretend like the user has done this via multi-root workspaces internally? LSP calls back use the URI of the file, so will still get the same settings. This isn't unlike tsserver's "ProjectService" which just maps requests down to specific language service instances.

rchiodo commented 6 months ago

You mean each pyrightconfig.js is like a different workspace? I think that might work? I don't think we actually have to map our internal workspace to a VS code one. I mean we have the <default> workspace which doesn't exist anywhere.

jakebailey commented 6 months ago

Yeah; when I was talking to Daniel about the feasibility to it, the way I was thinking it would be possible would be to use multi-root as more of an "advisory" set of roots to consider, but to still be able to infer other roots when the user doesn't specify them.

I'm in the process of getting tsserver to do LSP, and reconsiling multi-root with our existing ProjectService is something I've been thinking about, and based on the fact that gopls does this sort of inference for go modules / workspaces, it feels possible for both sides of the pipe to have their own idea of what a "workspace" or "project" is and still interact.

jakebailey commented 6 months ago

Expanding, I think my perspective on multi-root workspaces has sorta shifted over the years; now I see it mainly as a way to provide multiple VS Code settings in a single folder. The underlying LS implementation can completely ignore this information when serving requests or performing analysis. But when it needs to talk back to ask questions, the answers it gets may vary based on what workspace each root was. (But if I'm missing something I'd love to know!)

DanielRosenwasser commented 6 months ago

Linking to https://github.com/microsoft/vscode-python/issues/21204 since this issue feels like it's trying to solve many of the same issues.

luabud commented 6 months ago

I'm thinking another problem we probably have currently is that we depend on the Python extension's concept of a "selected environment", so in the example where you'd like to use Python 3.12 for one folder and Python 3.11 for another, I think the Python extension would have to support automatic "flipping" environments when users change active files, which currently isn't supported unless users are in a multi-root workspace. There are conversations happening on the Python extension side around no longer stricting environment selection to a workspace (and instead have more of a "per file" environment association), so when that happens I think it might be easier to get Pylance to work with the proposal outlined here

(cc @karthiknadig)

ElieGallet commented 3 months ago

For Scenario 1, could it be possible to specify in VS Code settings what pyproject.toml has to be used ?

DanielRosenwasser commented 1 month ago

I guess one thing I didn't really mention in this write-up is anything about when a file isn't included by the nearest pyrightconfig.json. Is it implicitly covered by the nearest pyproject.toml? Or does it need to keep searching upwards?