@@ -146,34 +146,36 @@ impl Options {
146146 let src = self . src . or_default ( ) ;
147147
148148 #[ allow( deprecated) ]
149- let src_roots = if let Some ( src_root) = self
150- . environment
151- . as_ref ( )
152- . and_then ( |environment| environment. root . as_ref ( ) )
153- . or_else ( || src. root . as_ref ( ) )
149+ let src_roots = if let Some ( roots) = environment
150+ . root
151+ . as_deref ( )
152+ . or_else ( || Some ( std:: slice:: from_ref ( src. root . as_ref ( ) ?) ) )
154153 {
155- vec ! [ src_root. absolute( project_root, system) ]
154+ roots
155+ . iter ( )
156+ . map ( |root| root. absolute ( project_root, system) )
157+ . collect ( )
156158 } else {
157159 let src = project_root. join ( "src" ) ;
158160
159161 let mut roots = if system. is_directory ( & src) {
160162 // Default to `src` and the project root if `src` exists and the root hasn't been specified.
161163 // This corresponds to the `src-layout`
162164 tracing:: debug!(
163- "Including `./src` in `src .root` because a `./src` directory exists"
165+ "Including `.` and `. /src` in `environment .root` because a `./src` directory exists"
164166 ) ;
165167 vec ! [ project_root. to_path_buf( ) , src]
166168 } else if system. is_directory ( & project_root. join ( project_name) . join ( project_name) ) {
167169 // `src-layout` but when the folder isn't called `src` but has the same name as the project.
168170 // For example, the "src" folder for `psycopg` is called `psycopg` and the python files are in `psycopg/psycopg/_adapters_map.py`
169171 tracing:: debug!(
170- "Including `./{project_name}` in `src .root` because a `./{project_name}/{project_name}` directory exists"
172+ "Including `.` and ` /{project_name}` in `environment .root` because a `./{project_name}/{project_name}` directory exists"
171173 ) ;
172174
173175 vec ! [ project_root. to_path_buf( ) , project_root. join( project_name) ]
174176 } else {
175177 // Default to a [flat project structure](https://packaging.python.org/en/latest/discussions/src-layout-vs-flat-layout/).
176- tracing:: debug!( "Defaulting `src.root` to `. `" ) ;
178+ tracing:: debug!( "Including `.` in `environment.root `" ) ;
177179 vec ! [ project_root. to_path_buf( ) ]
178180 } ;
179181
@@ -186,7 +188,7 @@ impl Options {
186188 {
187189 // If the `tests` directory exists and is not a package, include it as a source root.
188190 tracing:: debug!(
189- "Including `./tests` in `src .root` because a `./tests` directory exists"
191+ "Including `./tests` in `environment .root` because a `./tests` directory exists"
190192 ) ;
191193
192194 roots. push ( tests_dir) ;
@@ -248,7 +250,7 @@ impl Options {
248250 let mut diagnostics = Vec :: new ( ) ;
249251 let rules = self . to_rule_selection ( db, & mut diagnostics) ;
250252
251- let terminal_options = self . terminal . clone ( ) . unwrap_or_default ( ) ;
253+ let terminal_options = self . terminal . or_default ( ) ;
252254 let terminal = TerminalSettings {
253255 output_format : terminal_options
254256 . output_format
@@ -329,7 +331,7 @@ impl Options {
329331 project_root : & SystemPath ,
330332 diagnostics : & mut Vec < OptionDiagnostic > ,
331333 ) -> Result < Vec < Override > , Box < OptionDiagnostic > > {
332- let override_options = self . overrides . as_deref ( ) . unwrap_or_default ( ) ;
334+ let override_options = & * * self . overrides . or_default ( ) ;
333335
334336 let mut overrides = Vec :: with_capacity ( override_options. len ( ) ) ;
335337
@@ -352,9 +354,11 @@ impl Options {
352354#[ serde( rename_all = "kebab-case" , deny_unknown_fields) ]
353355#[ cfg_attr( feature = "schemars" , derive( schemars:: JsonSchema ) ) ]
354356pub struct EnvironmentOptions {
355- /// The root of the project, used for finding first-party modules.
357+ /// The root paths of the project, used for finding first-party modules.
356358 ///
357- /// If left unspecified, ty will try to detect common project layouts and initialize `src.root` accordingly:
359+ /// Accepts a list of directory paths searched in priority order (first has highest priority).
360+ ///
361+ /// If left unspecified, ty will try to detect common project layouts and initialize `root` accordingly:
358362 ///
359363 /// * if a `./src` directory exists, include `.` and `./src` in the first party search path (src layout or flat)
360364 /// * if a `./<project-name>/<project-name>` directory exists, include `.` and `./<project-name>` in the first party search path
@@ -365,12 +369,13 @@ pub struct EnvironmentOptions {
365369 #[ serde( skip_serializing_if = "Option::is_none" ) ]
366370 #[ option(
367371 default = r#"null"# ,
368- value_type = "str" ,
372+ value_type = "list[ str] " ,
369373 example = r#"
370- root = "./app"
374+ # Multiple directories (priority order)
375+ root = ["./src", "./lib", "./vendor"]
371376 "#
372377 ) ]
373- pub root : Option < RelativePathBuf > ,
378+ pub root : Option < Vec < RelativePathBuf > > ,
374379
375380 /// Specifies the version of Python that will be used to analyze the source code.
376381 /// The version should be specified as a string in the format `M.m` where `M` is the major version
0 commit comments