Skip to content

Commit ec3e611

Browse files
asukaminato0721meta-codesync[bot]
authored andcommitted
fix Go-to definition only finds definition from one element in a union #1470 (#1569)
Summary: fix #1470 Playground has this issue in the sandbox because its go-to-definition call only returned the first Range, even though the core analyzer already reports every branch of a union. Now collects every TextRangeWithModule from the transaction.goto_definition instead of truncating to the first match, serializes all of those ranges so the wasm bridge preserves the multiplicity. Pull Request resolved: #1569 Reviewed By: stroxler Differential Revision: D86973443 Pulled By: yangdanny97 fbshipit-source-id: 98332098260745edac0bbaef3461923ee718729c
1 parent d93497a commit ec3e611

File tree

8 files changed

+88
-21
lines changed

8 files changed

+88
-21
lines changed

pyrefly/lib/playground.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -468,16 +468,21 @@ impl Playground {
468468
SemanticTokensLegends::lsp_semantic_token_legends()
469469
}
470470

471-
pub fn goto_definition(&mut self, pos: Position) -> Option<Range> {
472-
let handle = self.handles.get(&self.active_filename)?;
471+
pub fn goto_definition(&mut self, pos: Position) -> Vec<Range> {
472+
let handle = match self.handles.get(&self.active_filename) {
473+
Some(handle) => handle,
474+
None => return Vec::new(),
475+
};
473476
let transaction = self.state.transaction();
474-
let position = self.to_text_size(&transaction, pos)?;
475-
// TODO: Support goto multiple definitions
477+
let position = match self.to_text_size(&transaction, pos) {
478+
Some(position) => position,
479+
None => return Vec::new(),
480+
};
476481
transaction
477482
.goto_definition(handle, position)
478483
.into_iter()
479-
.next()
480484
.map(|r| Range::new(r.module.display_range(r.range)))
485+
.collect()
481486
}
482487

483488
pub fn autocomplete(&self, pos: Position) -> Vec<AutoCompletionItem> {

pyrefly/lib/test/lsp/definition.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ x = 1 # go-to-definition is unsupported for literals
6565
# ^
6666
"#;
6767
let report = get_batched_lsp_operations_report(&[("main", code)], get_test_report);
68+
println!("REPORT=>{}<=", report);
6869
assert_eq!(
6970
r#"
7071
# main.py
@@ -1287,6 +1288,55 @@ Definition Result:
12871288
);
12881289
}
12891290

1291+
#[test]
1292+
fn union_method_access_test() {
1293+
let code = r#"
1294+
class State:
1295+
def get_location(self) -> str: ...
1296+
1297+
class NewYork:
1298+
def get_location(self) -> str:
1299+
return "10016"
1300+
1301+
class Massachusetts:
1302+
def get_location(self) -> str:
1303+
return "02108"
1304+
1305+
def find_location(x: Massachusetts | NewYork):
1306+
x.get_location()
1307+
# ^
1308+
1309+
def find_location2(x: NewYork | Massachusetts):
1310+
x.get_location()
1311+
# ^
1312+
"#;
1313+
let report = get_batched_lsp_operations_report(&[("main", code)], get_test_report);
1314+
assert_eq!(
1315+
r#"
1316+
# main.py
1317+
14 | x.get_location()
1318+
^
1319+
Definition Result:
1320+
10 | def get_location(self) -> str:
1321+
^^^^^^^^^^^^
1322+
Definition Result:
1323+
6 | def get_location(self) -> str:
1324+
^^^^^^^^^^^^
1325+
1326+
18 | x.get_location()
1327+
^
1328+
Definition Result:
1329+
10 | def get_location(self) -> str:
1330+
^^^^^^^^^^^^
1331+
Definition Result:
1332+
6 | def get_location(self) -> str:
1333+
^^^^^^^^^^^^
1334+
"#
1335+
.trim(),
1336+
report.trim(),
1337+
);
1338+
}
1339+
12901340
// todo(kylei) go-to definition on x should go to the definition
12911341
#[test]
12921342
fn global_keyword() {

pyrefly_wasm/lib.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,12 @@ impl State {
7676

7777
#[wasm_bindgen(js_name=gotoDefinition)]
7878
pub fn goto_definition(&mut self, line: i32, column: i32) -> JsValue {
79-
self.0
80-
.goto_definition(Position::new(line, column))
81-
.map(|result| serde_wasm_bindgen::to_value(&result).unwrap())
82-
.unwrap_or(JsValue::NULL)
79+
let results = self.0.goto_definition(Position::new(line, column));
80+
if results.is_empty() {
81+
JsValue::NULL
82+
} else {
83+
serde_wasm_bindgen::to_value(&results).unwrap_or(JsValue::NULL)
84+
}
8385
}
8486

8587
#[wasm_bindgen(js_name=autoComplete)]

website/src/__tests__/__mocks__/configuredMonacoMock.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ function setAutoCompleteFunction(
7272
*/
7373
function setGetDefFunction(
7474
_model: MockEditorModel,
75-
_getDefFunction: (line: number, column: number) => any
75+
_getDefFunction: (line: number, column: number) => any[] | null
7676
): void {}
7777

7878
/**

website/src/__tests__/__mocks__/pyreflyWasmMock.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ class MockState {
6161
* @param column Column number
6262
* @returns Empty definition result
6363
*/
64-
gotoDefinition(line?: number, column?: number): Record<string, any> {
65-
return {};
64+
gotoDefinition(_line?: number, _column?: number): Record<string, any>[] {
65+
return [];
6666
}
6767

6868
/**

website/src/__tests__/pyrefly_wasm.test.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,12 +118,13 @@ movie: Movie = {'name': 'Blade Runner',
118118
it('should return definition location for function call', () => {
119119
pyreService.setActiveFile('main.py');
120120
// Position of "test" in "test(42)"
121-
const definition = pyreService.gotoDefinition(18, 13);
121+
const definitions = pyreService.gotoDefinition(18, 13);
122122

123-
// Should return a location object
124-
expect(definition).toBeDefined();
123+
expect(definitions).toBeDefined();
124+
expect(definitions).not.toBeNull();
125+
expect(definitions!.length).toBeGreaterThan(0);
125126

126-
// expect that location is correct
127+
const definition = definitions![0];
127128
expect(definition.startLineNumber).toBe(13);
128129
expect(definition.startColumn).toBe(5);
129130
expect(definition.endLineNumber).toBe(13);

website/src/sandbox/Sandbox.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ export interface PyreflyState {
4545
setActiveFile: (filename: string) => void;
4646
getErrors: () => ReadonlyArray<PyreflyErrorMessage>;
4747
autoComplete: (line: number, column: number) => any;
48-
gotoDefinition: (line: number, column: number) => any;
48+
gotoDefinition: (line: number, column: number) => monaco.IRange[] | null;
4949
hover: (line: number, column: number) => any;
5050
inlayHint: () => any;
5151
semanticTokens: (range: any) => any;

website/src/sandbox/configured-monaco.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@ type SemanticTokens = monaco.languages.SemanticTokens;
1818
type SemanticTokensLegend = monaco.languages.SemanticTokensLegend;
1919

2020
type AutoCompleteFunction = (line: number, column: number) => CompletionItem[];
21-
type GetDefFunction = (line: number, column: number) => Range | null;
21+
type GetDefFunction = (
22+
line: number,
23+
column: number
24+
) => ReadonlyArray<Range> | null;
2225
type HoverFunction = (line: number, column: number) => Hover | null;
2326
type InlayHintFunction = () => InlayHint[];
2427
type SemanticTokensFunction = (range: Range | null) => SemanticTokens | null;
@@ -49,7 +52,7 @@ function setAutoCompleteFunction(
4952
const defaultGetDefFunctionForMonaco: GetDefFunction = (
5053
_l: number,
5154
_c: number
52-
): Range | null => null;
55+
): ReadonlyArray<Range> | null => null;
5356
const getDefFunctionsForMonaco = new Map<
5457
monaco.editor.ITextModel,
5558
GetDefFunction
@@ -217,8 +220,14 @@ monaco.languages.registerDefinitionProvider('python', {
217220
const f =
218221
getDefFunctionsForMonaco.get(model) ??
219222
defaultGetDefFunctionForMonaco;
220-
const range = f(position.lineNumber, position.column);
221-
return range != null ? { uri: model.uri, range } : null;
223+
const ranges = f(position.lineNumber, position.column);
224+
if (!ranges || ranges.length === 0) {
225+
return null;
226+
}
227+
if (ranges.length === 1) {
228+
return { uri: model.uri, range: ranges[0] };
229+
}
230+
return ranges.map((range) => ({ uri: model.uri, range }));
222231
} catch (e) {
223232
console.error(e);
224233
return null;

0 commit comments

Comments
 (0)