diff --git a/tools/hermes-parser/js/hermes-parser/__tests__/MatchExpression-test.js b/tools/hermes-parser/js/hermes-parser/__tests__/MatchExpression-test.js index d859b6aa1b4..f1788cb2b2b 100644 --- a/tools/hermes-parser/js/hermes-parser/__tests__/MatchExpression-test.js +++ b/tools/hermes-parser/js/hermes-parser/__tests__/MatchExpression-test.js @@ -705,6 +705,41 @@ describe('MatchExpression', () => { expect(runMatchExp(output, {foo: false, bar: 2})).toBe(1); }); + test('object property with `undefined` value', async () => { + const code = ` + const e = match (x) { + {foo: undefined}: true, + {bar: undefined as const a}: true, + {baz: 0 | undefined}: true, + _: false, + }; + `; + const output = await transform(code); + expect(output).toMatchInlineSnapshot(` + "const e = (() => { + if (typeof x === "object" && x !== null && "foo" in x && x.foo === undefined) { + return true; + } + + if (typeof x === "object" && x !== null && "bar" in x && x.bar === undefined) { + const a = x.bar; + return true; + } + + if (typeof x === "object" && x !== null && "baz" in x && (x.baz === 0 || x.baz === undefined)) { + return true; + } + + return false; + })();" + `); + + expect(runMatchExp(output, {})).toBe(false); + expect(runMatchExp(output, {foo: undefined})).toBe(true); + expect(runMatchExp(output, {bar: undefined})).toBe(true); + expect(runMatchExp(output, {baz: undefined})).toBe(true); + }); + test('nested', async () => { const code = ` const e = match (x) { diff --git a/tools/hermes-parser/js/hermes-parser/src/estree/TransformMatchSyntax.js b/tools/hermes-parser/js/hermes-parser/src/estree/TransformMatchSyntax.js index 128934dece2..6c727df492c 100644 --- a/tools/hermes-parser/js/hermes-parser/src/estree/TransformMatchSyntax.js +++ b/tools/hermes-parser/js/hermes-parser/src/estree/TransformMatchSyntax.js @@ -175,6 +175,36 @@ function checkBindingKind(node: MatchPattern, kind: BindingKind): void { } } +/** + * Does an object property's pattern require a `prop-exists` condition added? + * If the pattern is a literal like `0`, then it's not required, since the `eq` + * condition implies the prop exists. However, if we could be doing an equality + * check against `undefined`, then it is required, since that will be true even + * if the property doesn't exist. + */ +function needsPropExistsCond(pattern: MatchPattern): boolean { + switch (pattern.type) { + case 'MatchWildcardPattern': + case 'MatchBindingPattern': + case 'MatchIdentifierPattern': + case 'MatchMemberPattern': + return true; + case 'MatchLiteralPattern': + case 'MatchUnaryPattern': + case 'MatchObjectPattern': + case 'MatchArrayPattern': + return false; + case 'MatchAsPattern': { + const {pattern: asPattern} = pattern; + return needsPropExistsCond(asPattern); + } + case 'MatchOrPattern': { + const {patterns} = pattern; + return patterns.some(needsPropExistsCond); + } + } +} + /** * Analyzes a match pattern, and produced both the conditions and bindings * produced by that pattern. @@ -301,15 +331,12 @@ function analyzePattern( } seenNames.add(name); const propKey: Key = key.concat(objKey); - switch (propPattern.type) { - case 'MatchWildcardPattern': - case 'MatchBindingPattern': - conditions.push({ - type: 'prop-exists', - key, - propName: name, - }); - break; + if (needsPropExistsCond(propPattern)) { + conditions.push({ + type: 'prop-exists', + key, + propName: name, + }); } const {conditions: childConditions, bindings: childBindings} = analyzePattern(propPattern, propKey, seenBindingNames);