Skip to content

Commit f7d7bd4

Browse files
gkzfacebook-github-bot
authored andcommitted
[flow][match] Improve match logic for determining if a "property exists" refinement is needed for an object property's pattern
Summary: This diff is the Flow side of the change made to the transform in D67777384. We need to add a "property exists" refinement in some additional cases, where a possible check of `obj.prop === undefined` is possible, since that doesn't check that the property exists. Changelog: [internal] Reviewed By: panagosg7 Differential Revision: D70362252 fbshipit-source-id: fd2208d8aaf85e34458eaacb20f7f0b602d0f2fe
1 parent fd1f9b0 commit f7d7bd4

File tree

3 files changed

+99
-63
lines changed

3 files changed

+99
-63
lines changed

src/analysis/env_builder/name_resolver.ml

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3099,6 +3099,25 @@ module Make (Context : C) (FlowAPIUtils : F with type cx = Context.t) :
30993099
add_output (Error_message.EMatchInvalidPatternReference { loc; binding_reason })
31003100
| _ -> ()
31013101
in
3102+
let rec needs_prop_exists_refi = function
3103+
| (loc, WildcardPattern _)
3104+
| (loc, BindingPattern _)
3105+
| (loc, IdentifierPattern _)
3106+
| (loc, MemberPattern _) ->
3107+
Some loc
3108+
| (_, NumberPattern _)
3109+
| (_, BigIntPattern _)
3110+
| (_, StringPattern _)
3111+
| (_, BooleanPattern _)
3112+
| (_, NullPattern _)
3113+
| (_, UnaryPattern _)
3114+
| (_, ObjectPattern _)
3115+
| (_, ArrayPattern _) ->
3116+
None
3117+
| (_, AsPattern { AsPattern.pattern; _ }) -> needs_prop_exists_refi pattern
3118+
| (_, OrPattern { OrPattern.patterns; _ }) ->
3119+
Base.List.find_map patterns ~f:needs_prop_exists_refi
3120+
in
31023121
let rec recurse acc pattern bindings =
31033122
( if Flow_ast_utils.match_pattern_has_binding pattern then
31043123
let (loc, _) = pattern in
@@ -3279,15 +3298,14 @@ module Make (Context : C) (FlowAPIUtils : F with type cx = Context.t) :
32793298
let open Ast.Expression in
32803299
(loc, Member { Member._object = acc; property; comments = None })
32813300
in
3282-
(match pattern with
3283-
| (loc, WildcardPattern _)
3284-
| (loc, BindingPattern _) ->
3301+
(match needs_prop_exists_refi pattern with
3302+
| Some loc ->
32853303
(match RefinementKey.of_expression acc with
32863304
| Some key ->
32873305
let refi = PropExistsR { propname; loc } in
32883306
this#add_single_refinement key ~refining_locs:(L.LSet.singleton loc) refi
32893307
| None -> ())
3290-
| _ -> ());
3308+
| None -> ());
32913309
recurse member pattern bindings
32923310
)
32933311
in

tests/match/match.exp

Lines changed: 59 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -259,172 +259,172 @@ References:
259259
^ [1]
260260

261261

262-
Error ----------------------------------------------------------------------------------------------- matching.js:202:14
262+
Error ----------------------------------------------------------------------------------------------- matching.js:220:14
263263

264264
`match` is not exhaustively checked: object type [1] has not been fully checked against by the match patterns below.
265265
[match-not-exhaustive]
266266

267-
matching.js:202:14
268-
202| const e2 = match (x) { // ERROR: `type: 'baz'` not checked
267+
matching.js:220:14
268+
220| const e2 = match (x) { // ERROR: `type: 'baz'` not checked
269269
^^^^^
270270

271271
References:
272-
matching.js:193:20
273-
193| | {type: 'baz', val: boolean};
272+
matching.js:211:20
273+
211| | {type: 'baz', val: boolean};
274274
^^^^^^^^^^^^^^^^^^^^^^^^^^^ [1]
275275

276276

277-
Error ----------------------------------------------------------------------------------------------- matching.js:251:14
277+
Error ----------------------------------------------------------------------------------------------- matching.js:269:14
278278

279279
`match` is not exhaustively checked: object type [1] has not been fully checked against by the match patterns below.
280280
[match-not-exhaustive]
281281

282-
matching.js:251:14
283-
251| const e3 = match (x) { // ERROR: `type: 'bar', n: 2` not checked
282+
matching.js:269:14
283+
269| const e3 = match (x) { // ERROR: `type: 'bar', n: 2` not checked
284284
^^^^^
285285

286286
References:
287-
matching.js:236:20
288-
236| | {type: 'bar', n: 2, val: boolean};
287+
matching.js:254:20
288+
254| | {type: 'bar', n: 2, val: boolean};
289289
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [1]
290290

291291

292-
Error ----------------------------------------------------------------------------------------------- matching.js:279:14
292+
Error ----------------------------------------------------------------------------------------------- matching.js:297:14
293293

294294
`match` is not exhaustively checked: object type [1] has not been fully checked against by the match patterns below.
295295
[match-not-exhaustive]
296296

297-
matching.js:279:14
298-
279| const e2 = match (x) { // ERROR: `type: 'bar'` not checked
297+
matching.js:297:14
298+
297| const e2 = match (x) { // ERROR: `type: 'bar'` not checked
299299
^^^^^
300300

301301
References:
302-
matching.js:271:20
303-
271| | {type: 'bar', val: string}
302+
matching.js:289:20
303+
289| | {type: 'bar', val: string}
304304
^^^^^^^^^^^^^^^^^^^^^^^^^^ [1]
305305

306306

307-
Error ----------------------------------------------------------------------------------------------- matching.js:297:14
307+
Error ----------------------------------------------------------------------------------------------- matching.js:315:14
308308

309309
`match` is not exhaustively checked: tuple type [1] has not been fully checked against by the match patterns below.
310310
[match-not-exhaustive]
311311

312-
matching.js:297:14
313-
297| const e2 = match (x) { // ERROR: `'baz'` element not checked
312+
matching.js:315:14
313+
315| const e2 = match (x) { // ERROR: `'baz'` element not checked
314314
^^^^^
315315

316316
References:
317-
matching.js:288:20
318-
288| | ['baz', boolean];
317+
matching.js:306:20
318+
306| | ['baz', boolean];
319319
^^^^^^^^^^^^^^^^ [1]
320320

321321

322-
Error ----------------------------------------------------------------------------------------------- matching.js:335:22
322+
Error ----------------------------------------------------------------------------------------------- matching.js:353:22
323323

324324
Cannot cast `a` to empty because boolean [1] is incompatible with empty [2]. [incompatible-cast]
325325

326-
matching.js:335:22
327-
335| [const a, _, _]: a as empty, // ERROR: `boolean` is not `empty`
326+
matching.js:353:22
327+
353| [const a, _, _]: a as empty, // ERROR: `boolean` is not `empty`
328328
^
329329

330330
References:
331-
matching.js:330:21
332-
330| | [boolean, boolean, boolean];
331+
matching.js:348:21
332+
348| | [boolean, boolean, boolean];
333333
^^^^^^^ [1]
334-
matching.js:335:27
335-
335| [const a, _, _]: a as empty, // ERROR: `boolean` is not `empty`
334+
matching.js:353:27
335+
353| [const a, _, _]: a as empty, // ERROR: `boolean` is not `empty`
336336
^^^^^ [2]
337337

338338

339-
Error ----------------------------------------------------------------------------------------------- matching.js:355:16
339+
Error ----------------------------------------------------------------------------------------------- matching.js:373:16
340340

341341
Cannot cast `a` to string because number [1] is incompatible with string [2]. [incompatible-cast]
342342

343-
matching.js:355:16
344-
355| [const a]: a as string, // ERROR: `number` is not `string`
343+
matching.js:373:16
344+
373| [const a]: a as string, // ERROR: `number` is not `string`
345345
^
346346

347347
References:
348-
matching.js:351:21
349-
351| declare const x: [number] | Array<string>;
348+
matching.js:369:21
349+
369| declare const x: [number] | Array<string>;
350350
^^^^^^ [1]
351-
matching.js:355:21
352-
355| [const a]: a as string, // ERROR: `number` is not `string`
351+
matching.js:373:21
352+
373| [const a]: a as string, // ERROR: `number` is not `string`
353353
^^^^^^ [2]
354354

355355

356-
Error ----------------------------------------------------------------------------------------------- matching.js:375:14
356+
Error ----------------------------------------------------------------------------------------------- matching.js:393:14
357357

358358
`match` is not exhaustively checked: tuple type [1] has not been fully checked against by the match patterns below.
359359
[match-not-exhaustive]
360360

361-
matching.js:375:14
362-
375| const e2 = match (x) { // ERROR: does not match all possibilities
361+
matching.js:393:14
362+
393| const e2 = match (x) { // ERROR: does not match all possibilities
363363
^^^^^
364364

365365
References:
366-
matching.js:368:20
367-
368| declare const x: [a: 0, b?: 1, c?: 2];
366+
matching.js:386:20
367+
386| declare const x: [a: 0, b?: 1, c?: 2];
368368
^^^^^^^^^^^^^^^^^^^^ [1]
369369

370370

371-
Error ----------------------------------------------------------------------------------------------- matching.js:379:14
371+
Error ----------------------------------------------------------------------------------------------- matching.js:397:14
372372

373373
`match` is not exhaustively checked: tuple type [1] has not been fully checked against by the match patterns below.
374374
[match-not-exhaustive]
375375

376-
matching.js:379:14
377-
379| const e3 = match (x) { // ERROR: does not match all possibilities
376+
matching.js:397:14
377+
397| const e3 = match (x) { // ERROR: does not match all possibilities
378378
^^^^^
379379

380380
References:
381-
matching.js:368:20
382-
368| declare const x: [a: 0, b?: 1, c?: 2];
381+
matching.js:386:20
382+
386| declare const x: [a: 0, b?: 1, c?: 2];
383383
^^^^^^^^^^^^^^^^^^^^ [1]
384384

385385

386-
Error ----------------------------------------------------------------------------------------------- matching.js:383:14
386+
Error ----------------------------------------------------------------------------------------------- matching.js:401:14
387387

388388
`match` is not exhaustively checked: tuple type [1] has not been fully checked against by the match patterns below.
389389
[match-not-exhaustive]
390390

391-
matching.js:383:14
392-
383| const e4 = match (x) { // ERROR: does not match all possibilities
391+
matching.js:401:14
392+
401| const e4 = match (x) { // ERROR: does not match all possibilities
393393
^^^^^
394394

395395
References:
396-
matching.js:368:20
397-
368| declare const x: [a: 0, b?: 1, c?: 2];
396+
matching.js:386:20
397+
386| declare const x: [a: 0, b?: 1, c?: 2];
398398
^^^^^^^^^^^^^^^^^^^^ [1]
399399

400400

401-
Error ----------------------------------------------------------------------------------------------- matching.js:397:14
401+
Error ----------------------------------------------------------------------------------------------- matching.js:415:14
402402

403403
`match` is not exhaustively checked: tuple type [1] has not been fully checked against by the match patterns below.
404404
[match-not-exhaustive]
405405

406-
matching.js:397:14
407-
397| const e2 = match (x) { // ERROR: does not match all elements
406+
matching.js:415:14
407+
415| const e2 = match (x) { // ERROR: does not match all elements
408408
^^^^^
409409

410410
References:
411-
matching.js:390:20
412-
390| declare const x: [a: 0, ...];
411+
matching.js:408:20
412+
408| declare const x: [a: 0, ...];
413413
^^^^^^^^^^^ [1]
414414

415415

416-
Error ----------------------------------------------------------------------------------------------- matching.js:407:14
416+
Error ----------------------------------------------------------------------------------------------- matching.js:425:14
417417

418418
`match` is not exhaustively checked: object type [1] has not been fully checked against by the match patterns below.
419419
[match-not-exhaustive]
420420

421-
matching.js:407:14
422-
407| const e1 = match (x) {
421+
matching.js:425:14
422+
425| const e1 = match (x) {
423423
^^^^^
424424

425425
References:
426-
matching.js:404:23
427-
404| type T = {foo: 1} | {foo: 2};
426+
matching.js:422:23
427+
422| type T = {foo: 1} | {foo: 2};
428428
^^^^^^^^ [1]
429429

430430

tests/match/matching.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,24 @@
185185
const d: d as empty, // OK: all members checked
186186
};
187187
}
188+
{
189+
declare const x:
190+
| {foo: void, a: 0}
191+
| {bar: void, a: 1}
192+
| {baz: void, a: 2}
193+
| {zap: void, a: 3};
194+
195+
declare const u: void;
196+
declare const o: {u: void};
197+
198+
const e1 = match (x) {
199+
{foo: u, const a}: a as 0, // OK
200+
{bar: o.u, const a}: a as 1, // OK
201+
{baz: undefined as v, const a}: a as 2, // OK
202+
{zap: 2 | u, const a}: a as 3, // OK
203+
const d: d as empty, // OK: all members checked
204+
};
205+
}
188206

189207
// Disjoint object union
190208
{

0 commit comments

Comments
 (0)