From 82f06f080da2ee362c7940449b9a7d2d5e6a4a13 Mon Sep 17 00:00:00 2001 From: Shon Feder Date: Fri, 10 Nov 2023 11:08:08 -0500 Subject: [PATCH 1/2] Add support for wildcard match cases --- quint/src/types/specialConstraints.ts | 63 +++++++++++++++------------ quint/test/types/inferrer.test.ts | 33 +++++++++++++- 2 files changed, 68 insertions(+), 28 deletions(-) diff --git a/quint/src/types/specialConstraints.ts b/quint/src/types/specialConstraints.ts index 943f2b858..87fafbde4 100644 --- a/quint/src/types/specialConstraints.ts +++ b/quint/src/types/specialConstraints.ts @@ -256,39 +256,48 @@ export function matchConstraints( // Now we verify that each label is a string literal and each eliminator is a unary operator. // This is a well-formedness check prior to checking that the match expression fits the // `sumType` of the value we are matching on. - const fieldValidationError: (msg: string) => Either = msg => - left(buildErrorLeaf(`Generating match constraints for ${labelsAndcases.map(a => expressionToString(a[0]))}`, msg)) - - const validatedFields: Either[] = labelAndElimPairs.map( - ([[labelExpr, _labelType], [elimExpr, elimType]]) => { - return labelExpr.kind !== 'str' - ? fieldValidationError( - `Match variant name must be a string literal but it is a ${labelExpr.kind}: ${expressionToString( - labelExpr - )}` - ) - : elimType.kind !== 'oper' - ? fieldValidationError( - `Match case eliminator must be an operator expression but it is a ${elimType.kind}: ${expressionToString( - elimExpr - )}` - ) - : elimType.args.length !== 1 - ? fieldValidationError( - `Match case eliminator must be a unary operator but it is an operator of ${ - elimType.args.length - } arguments: ${expressionToString(elimExpr)}` - ) - : right([labelExpr.value, elimType.args[0]]) // The label and associated type of a varaint case - } - ) + const validatedFields: Either[] = [] + const fieldValidationError = (msg: string) => + validatedFields.push( + left(buildErrorLeaf(`Generating match constraints for ${labelsAndcases.map(a => expressionToString(a[0]))}`, msg)) + ) + let wildCardMatch = false + for (const [[labelExpr, _labelType], [elimExpr, elimType]] of labelAndElimPairs) { + labelExpr.kind !== 'str' + ? fieldValidationError( + `Match variant name must be a string literal but it is a ${labelExpr.kind}: ${expressionToString(labelExpr)}` + ) + : elimType.kind !== 'oper' + ? fieldValidationError( + `Match case eliminator must be an operator expression but it is a ${elimType.kind}: ${expressionToString( + elimExpr + )}` + ) + : elimType.args.length > 1 + ? fieldValidationError( + `Match case eliminator must be a nullary or unary operator but it is an operator of ${ + elimType.args.length + } arguments: ${expressionToString(elimExpr)}` + ) + : !wildCardMatch && elimType.args.length === 0 && labelExpr.value === '_' + ? (wildCardMatch = true) // The wildcard case, `_ => foo`, means we can match anything else + : wildCardMatch // There should only ever be 1 wilcard match, and it should be the last case + ? fieldValidationError( + `Invalid wildcard match ('_') in match expression: ${expressionToString( + elimExpr + )}. Only one wildcard can appear and it must be the final case of a match.` + ) + : elimType.args.length === 1 + ? validatedFields.push(right([labelExpr.value, elimType.args[0]])) // The label and associated type of a variant case + : fieldValidationError(`Invalid match expression: ${expressionToString(elimExpr)}`) // Unknown invalid case + } // TODO: Support more expressive and informative type errors. // This will require tying into the constraint checking system. // See https://github.com/informalsystems/quint/issues/1231 return mergeInMany(validatedFields).map((fields: [string, QuintType][]): Constraint[] => { // Form a constraint ensuring that the match expression fits the sum-type it is applied to: - const matchCaseType = sumType(fields) // The sum-type implied by the match elimination cases. + const matchCaseType = wildCardMatch ? sumType(fields, `${resultTypeVar.name}_wildcard`) : sumType(fields) // The sum-type implied by the match elimination cases. // s ~ < i1 : t1, ..., in : tn > ) const variantTypeIsMatchCaseType: Constraint = { kind: 'eq', types: [variantType, matchCaseType], sourceId: id } diff --git a/quint/test/types/inferrer.test.ts b/quint/test/types/inferrer.test.ts index a7046976d..ea634475e 100644 --- a/quint/test/types/inferrer.test.ts +++ b/quint/test/types/inferrer.test.ts @@ -197,6 +197,13 @@ describe('inferTypes', () => { ]) }) + it('infers types for match expression with wildcard case', () => { + const defs = ['type T = A(int) | B', 'val nine : int = match B { _ => 9 }'] + + const [errors, _] = inferTypesForDefs(defs) + assert.isEmpty(errors) + }) + it('reports a type error for match expressions that return inconsitent types in cases', () => { const defs = [ 'type T = A(int) | B', @@ -209,6 +216,30 @@ describe('inferTypes', () => { assert.match([...errors.values()].map(errorTreeToString)[0], RegExp("Couldn't unify int and str")) }) + it('reports a type error for match expressions with multiple wildcard cases', () => { + const defs = [ + 'type T = A(int) | B | C', + 'val a = variant("A", 3)', + 'val nine = match B { A(n) => "OK" | _ => "first wilcard" | _ => "second, invalid wildcard" }', + ] + + const [errors, _] = inferTypesForDefs(defs) + assert.isNotEmpty(errors) + assert.match([...errors.values()].map(errorTreeToString)[0], RegExp('Invalid wildcard match')) + }) + + it('reports a type error for match expressions with non-final wildcard case', () => { + const defs = [ + 'type T = A(int) | B | C', + 'val a = variant("A", 3)', + 'val nine = match B { A(n) => "OK" | _ => "invalid, non-final wilcard" | C => "OK" }', + ] + + const [errors, _] = inferTypesForDefs(defs) + assert.isNotEmpty(errors) + assert.match([...errors.values()].map(errorTreeToString)[0], RegExp('Invalid wildcard match')) + }) + it('reports a type error for match expressions on non-variant expressions', () => { const defs = [ 'val notAVariant = "this is not a variant"', @@ -221,7 +252,7 @@ describe('inferTypes', () => { }) it('reports a type error for matchVariant operator with non-label arguments', () => { - const defs = ['type T = A(int) | B', 'val a = variant("A", 3)', 'val nine = matchVariant(a, 3, 9)'] + const defs = ['type T = A(int) | B', 'val a = variant("A", 3)', 'val nine = matchVariant(a, 3, (_ => 9))'] const [errors, _] = inferTypesForDefs(defs) assert.isNotEmpty(errors) From a85f99c1d8b3fcb8aac1823a615fbb4c6876b54f Mon Sep 17 00:00:00 2001 From: Shon Feder Date: Fri, 10 Nov 2023 12:40:18 -0500 Subject: [PATCH 2/2] Rework wilcard case elim to use unary lambda We had been using thunks, lambdas that take no arguments, to encode the eliminators for wildcard match cases. I.e., the match expression ``` match foo { | _ => bar } ``` Was being converted into our builtin exotic thus: ``` matchVariant(foo, "_", () => bar) ``` This made sense because in the case of the wildcard match we are discarding the variant, and there is no argument to be supplied to the eliminator. However: 1. We don't really support nullary lambdas (even tho we are making use of them at several places in our code currently). 2. Since we are committed to using the exotic builtin operators, and there is no way to specify the effect of a variadic builtin with unpredictable arguments, we could not give this builtin the correct effect signature. So I have changed the way wildcard cases are encoded, to also use unary lambas that just ignore their argument. This simplifies a lot of the match typing and evaluation, so it is the right call overall. --- examples/language-features/option.qnt | 5 ++++ quint/src/parsing/ToIrListener.ts | 29 ++++++------------- quint/src/runtime/impl/compilerImpl.ts | 7 +---- quint/src/types/specialConstraints.ts | 10 +++---- quint/testFixture/_1044matchExpression.json | 2 +- .../testFixture/_1044matchExpression.map.json | 2 +- 6 files changed, 21 insertions(+), 34 deletions(-) diff --git a/examples/language-features/option.qnt b/examples/language-features/option.qnt index 1c0555aaf..72b7991b3 100644 --- a/examples/language-features/option.qnt +++ b/examples/language-features/option.qnt @@ -37,4 +37,9 @@ module option { }, outcome' = SumVotes } + + run matchWithDefaultTest = { + val expected = match Some(42) { _ => "default" } + assert(expected == "default") + } } diff --git a/quint/src/parsing/ToIrListener.ts b/quint/src/parsing/ToIrListener.ts index b9d244999..e9d8f9755 100644 --- a/quint/src/parsing/ToIrListener.ts +++ b/quint/src/parsing/ToIrListener.ts @@ -998,27 +998,16 @@ export class ToIrListener implements QuintListener { private formMatchCase([caseExpr, caseCtx]: [QuintEx, p.MatchSumCaseContext]): (QuintStr | QuintLambda)[] { const labelId = this.getId(caseCtx) const elimId = this.getId(caseCtx) - let label: string - let params: QuintLambdaParameter[] - if (caseCtx._wildCardMatch) { - // a wildcard case: _ => expr - label = '_' - params = [] - } else if (caseCtx._variantMatch) { - const variant = caseCtx._variantMatch - let name: string - if (variant._variantParam) { - name = variant._variantParam.text - } else { - // We either have a hole or no param specified, in which case our lambda only needs a hole - name = '_' - } - label = variant._variantLabel.text - params = [{ name, id: this.getId(variant) }] - } else { - throw new Error('impossible: either _wildCardMatch or _variantMatch must be present') - } + const variantMatch = caseCtx._variantMatch + + // If there is not a variant param, then we have a wildcard case, `_ => foo`, or a hole in the paramater position, `Foo(_) => bar` + const name = variantMatch && variantMatch._variantParam ? variantMatch._variantParam.text : '_' + const params = [{ name, id: this.getId(caseCtx) }] + + // If there is not a variant label, then we have a wildcard case + const label = variantMatch ? variantMatch._variantLabel.text : '_' const labelStr: QuintStr = { id: labelId, kind: 'str', value: label } + const elim: QuintLambda = { id: elimId, kind: 'lambda', qualifier: 'def', expr: caseExpr, params } return [labelStr, elim] } diff --git a/quint/src/runtime/impl/compilerImpl.ts b/quint/src/runtime/impl/compilerImpl.ts index 91f56b03c..3848e35c2 100644 --- a/quint/src/runtime/impl/compilerImpl.ts +++ b/quint/src/runtime/impl/compilerImpl.ts @@ -713,12 +713,7 @@ export class CompilerVisitor implements IRVisitor { let result: Maybe | undefined for (const [caseLabel, caseElim] of chunk(cases, 2)) { const caseLabelStr = caseLabel.toStr() - if (caseLabelStr === '_') { - // The wilcard case ignores the value. - // NOTE: This SHOULD be a nullary lambda, but by this point the compiler - // has already converted it into a value. Confusing! - result = just(caseElim as RuntimeValueLambda) - } else if (caseLabelStr === label) { + if (caseLabelStr === '_' || caseLabelStr === label) { // Type checking ensures the second item of each case is a lambda const eliminator = caseElim as RuntimeValueLambda result = eliminator.eval([just(value)]).map(r => r as RuntimeValue) diff --git a/quint/src/types/specialConstraints.ts b/quint/src/types/specialConstraints.ts index 87fafbde4..5a1a8194d 100644 --- a/quint/src/types/specialConstraints.ts +++ b/quint/src/types/specialConstraints.ts @@ -273,13 +273,13 @@ export function matchConstraints( elimExpr )}` ) - : elimType.args.length > 1 + : elimType.args.length !== 1 ? fieldValidationError( - `Match case eliminator must be a nullary or unary operator but it is an operator of ${ + `Match case eliminator must be a unary operator but it is an operator of ${ elimType.args.length } arguments: ${expressionToString(elimExpr)}` ) - : !wildCardMatch && elimType.args.length === 0 && labelExpr.value === '_' + : !wildCardMatch && labelExpr.value === '_' ? (wildCardMatch = true) // The wildcard case, `_ => foo`, means we can match anything else : wildCardMatch // There should only ever be 1 wilcard match, and it should be the last case ? fieldValidationError( @@ -287,9 +287,7 @@ export function matchConstraints( elimExpr )}. Only one wildcard can appear and it must be the final case of a match.` ) - : elimType.args.length === 1 - ? validatedFields.push(right([labelExpr.value, elimType.args[0]])) // The label and associated type of a variant case - : fieldValidationError(`Invalid match expression: ${expressionToString(elimExpr)}`) // Unknown invalid case + : validatedFields.push(right([labelExpr.value, elimType.args[0]])) // The label and associated type of a variant case } // TODO: Support more expressive and informative type errors. diff --git a/quint/testFixture/_1044matchExpression.json b/quint/testFixture/_1044matchExpression.json index ddcf96193..6ceb3f033 100644 --- a/quint/testFixture/_1044matchExpression.json +++ b/quint/testFixture/_1044matchExpression.json @@ -1 +1 @@ -{"stage":"parsing","warnings":[],"modules":[{"id":39,"name":"SumTypes","declarations":[{"id":4,"name":"T","kind":"typedef","type":{"id":4,"kind":"sum","fields":{"kind":"row","fields":[{"fieldName":"A","fieldType":{"id":1,"kind":"rec","fields":{"kind":"row","fields":[],"other":{"kind":"empty"}}}},{"fieldName":"B","fieldType":{"id":2,"kind":"int"}},{"fieldName":"C","fieldType":{"id":3,"kind":"str"}}],"other":{"kind":"empty"}}}},{"id":14,"kind":"def","name":"B","qualifier":"def","typeAnnotation":{"kind":"oper","args":[{"id":2,"kind":"int"}],"res":{"id":4,"kind":"const","name":"T"}},"expr":{"id":13,"kind":"lambda","params":[{"id":10,"name":"__BParam"}],"qualifier":"def","expr":{"id":12,"kind":"app","opcode":"variant","args":[{"id":9,"kind":"str","value":"B"},{"kind":"name","name":"__BParam","id":11}]}}},{"id":20,"kind":"def","name":"C","qualifier":"def","typeAnnotation":{"kind":"oper","args":[{"id":3,"kind":"str"}],"res":{"id":4,"kind":"const","name":"T"}},"expr":{"id":19,"kind":"lambda","params":[{"id":16,"name":"__CParam"}],"qualifier":"def","expr":{"id":18,"kind":"app","opcode":"variant","args":[{"id":15,"kind":"str","value":"C"},{"kind":"name","name":"__CParam","id":17}]}}},{"id":8,"kind":"def","name":"A","qualifier":"val","typeAnnotation":{"id":4,"kind":"const","name":"T"},"expr":{"id":7,"kind":"app","opcode":"variant","args":[{"id":5,"kind":"str","value":"A"},{"id":6,"kind":"app","opcode":"Rec","args":[]}]}},{"id":23,"kind":"def","name":"c","qualifier":"val","expr":{"id":22,"kind":"app","opcode":"C","args":[{"id":21,"kind":"str","value":"Foo"}]}},{"id":38,"kind":"def","name":"ex","qualifier":"val","expr":{"id":29,"kind":"app","opcode":"matchVariant","args":[{"id":24,"kind":"name","name":"c"},{"id":30,"kind":"str","value":"A"},{"id":31,"kind":"lambda","qualifier":"def","expr":{"id":25,"kind":"int","value":0},"params":[{"name":"_","id":32}]},{"id":33,"kind":"str","value":"B"},{"id":34,"kind":"lambda","qualifier":"def","expr":{"id":26,"kind":"name","name":"n"},"params":[{"name":"n","id":35}]},{"id":36,"kind":"str","value":"_"},{"id":37,"kind":"lambda","qualifier":"def","expr":{"id":28,"kind":"app","opcode":"iuminus","args":[{"id":27,"kind":"int","value":1}]},"params":[]}]}}]}],"table":{"4":{"id":4,"name":"T","kind":"typedef","type":{"id":4,"kind":"sum","fields":{"kind":"row","fields":[{"fieldName":"A","fieldType":{"id":1,"kind":"rec","fields":{"kind":"row","fields":[],"other":{"kind":"empty"}}}},{"fieldName":"B","fieldType":{"id":2,"kind":"int"}},{"fieldName":"C","fieldType":{"id":3,"kind":"str"}}],"other":{"kind":"empty"}}}},"11":{"id":10,"name":"__BParam","kind":"param"},"17":{"id":16,"name":"__CParam","kind":"param"},"22":{"id":20,"kind":"def","name":"C","qualifier":"def","expr":{"id":19,"kind":"lambda","params":[{"id":16,"name":"__CParam"}],"qualifier":"def","expr":{"id":18,"kind":"app","opcode":"variant","args":[{"id":15,"kind":"str","value":"C"},{"kind":"name","name":"__CParam","id":17}]}},"depth":0},"24":{"id":23,"kind":"def","name":"c","qualifier":"val","expr":{"id":22,"kind":"app","opcode":"C","args":[{"id":21,"kind":"str","value":"Foo"}]},"depth":0},"26":{"name":"n","id":35,"kind":"param"}},"errors":[]} \ No newline at end of file +{"stage":"parsing","warnings":[],"modules":[{"id":40,"name":"SumTypes","declarations":[{"id":4,"name":"T","kind":"typedef","type":{"id":4,"kind":"sum","fields":{"kind":"row","fields":[{"fieldName":"A","fieldType":{"id":1,"kind":"rec","fields":{"kind":"row","fields":[],"other":{"kind":"empty"}}}},{"fieldName":"B","fieldType":{"id":2,"kind":"int"}},{"fieldName":"C","fieldType":{"id":3,"kind":"str"}}],"other":{"kind":"empty"}}}},{"id":14,"kind":"def","name":"B","qualifier":"def","typeAnnotation":{"kind":"oper","args":[{"id":2,"kind":"int"}],"res":{"id":4,"kind":"const","name":"T"}},"expr":{"id":13,"kind":"lambda","params":[{"id":10,"name":"__BParam"}],"qualifier":"def","expr":{"id":12,"kind":"app","opcode":"variant","args":[{"id":9,"kind":"str","value":"B"},{"kind":"name","name":"__BParam","id":11}]}}},{"id":20,"kind":"def","name":"C","qualifier":"def","typeAnnotation":{"kind":"oper","args":[{"id":3,"kind":"str"}],"res":{"id":4,"kind":"const","name":"T"}},"expr":{"id":19,"kind":"lambda","params":[{"id":16,"name":"__CParam"}],"qualifier":"def","expr":{"id":18,"kind":"app","opcode":"variant","args":[{"id":15,"kind":"str","value":"C"},{"kind":"name","name":"__CParam","id":17}]}}},{"id":8,"kind":"def","name":"A","qualifier":"val","typeAnnotation":{"id":4,"kind":"const","name":"T"},"expr":{"id":7,"kind":"app","opcode":"variant","args":[{"id":5,"kind":"str","value":"A"},{"id":6,"kind":"app","opcode":"Rec","args":[]}]}},{"id":23,"kind":"def","name":"c","qualifier":"val","expr":{"id":22,"kind":"app","opcode":"C","args":[{"id":21,"kind":"str","value":"Foo"}]}},{"id":39,"kind":"def","name":"ex","qualifier":"val","expr":{"id":29,"kind":"app","opcode":"matchVariant","args":[{"id":24,"kind":"name","name":"c"},{"id":30,"kind":"str","value":"A"},{"id":31,"kind":"lambda","qualifier":"def","expr":{"id":25,"kind":"int","value":0},"params":[{"name":"_","id":32}]},{"id":33,"kind":"str","value":"B"},{"id":34,"kind":"lambda","qualifier":"def","expr":{"id":26,"kind":"name","name":"n"},"params":[{"name":"n","id":35}]},{"id":36,"kind":"str","value":"_"},{"id":37,"kind":"lambda","qualifier":"def","expr":{"id":28,"kind":"app","opcode":"iuminus","args":[{"id":27,"kind":"int","value":1}]},"params":[{"name":"_","id":38}]}]}}]}],"table":{"4":{"id":4,"name":"T","kind":"typedef","type":{"id":4,"kind":"sum","fields":{"kind":"row","fields":[{"fieldName":"A","fieldType":{"id":1,"kind":"rec","fields":{"kind":"row","fields":[],"other":{"kind":"empty"}}}},{"fieldName":"B","fieldType":{"id":2,"kind":"int"}},{"fieldName":"C","fieldType":{"id":3,"kind":"str"}}],"other":{"kind":"empty"}}}},"11":{"id":10,"name":"__BParam","kind":"param"},"17":{"id":16,"name":"__CParam","kind":"param"},"22":{"id":20,"kind":"def","name":"C","qualifier":"def","expr":{"id":19,"kind":"lambda","params":[{"id":16,"name":"__CParam"}],"qualifier":"def","expr":{"id":18,"kind":"app","opcode":"variant","args":[{"id":15,"kind":"str","value":"C"},{"kind":"name","name":"__CParam","id":17}]}},"depth":0},"24":{"id":23,"kind":"def","name":"c","qualifier":"val","expr":{"id":22,"kind":"app","opcode":"C","args":[{"id":21,"kind":"str","value":"Foo"}]},"depth":0},"26":{"name":"n","id":35,"kind":"param"}},"errors":[]} \ No newline at end of file diff --git a/quint/testFixture/_1044matchExpression.map.json b/quint/testFixture/_1044matchExpression.map.json index 3f485df91..5a444ca1c 100644 --- a/quint/testFixture/_1044matchExpression.map.json +++ b/quint/testFixture/_1044matchExpression.map.json @@ -1 +1 @@ -{"sourceIndex":{"0":"mocked_path/testFixture/_1044matchExpression.qnt"},"map":{"1":[0,{"line":1,"col":11,"index":29},{"line":1,"col":11,"index":29}],"2":[0,{"line":1,"col":17,"index":35},{"line":1,"col":19,"index":37}],"3":[0,{"line":1,"col":26,"index":44},{"line":1,"col":28,"index":46}],"4":[0,{"line":1,"col":2,"index":20},{"line":1,"col":29,"index":47}],"5":[0,{"line":1,"col":11,"index":29},{"line":1,"col":11,"index":29}],"6":[0,{"line":1,"col":11,"index":29},{"line":1,"col":11,"index":29}],"7":[0,{"line":1,"col":11,"index":29},{"line":1,"col":11,"index":29}],"8":[0,{"line":1,"col":11,"index":29},{"line":1,"col":11,"index":29}],"9":[0,{"line":1,"col":15,"index":33},{"line":1,"col":20,"index":38}],"10":[0,{"line":1,"col":17,"index":35},{"line":1,"col":19,"index":37}],"11":[0,{"line":1,"col":15,"index":33},{"line":1,"col":15,"index":33}],"12":[0,{"line":1,"col":15,"index":33},{"line":1,"col":20,"index":38}],"13":[0,{"line":1,"col":15,"index":33},{"line":1,"col":20,"index":38}],"14":[0,{"line":1,"col":15,"index":33},{"line":1,"col":20,"index":38}],"15":[0,{"line":1,"col":24,"index":42},{"line":1,"col":29,"index":47}],"16":[0,{"line":1,"col":26,"index":44},{"line":1,"col":28,"index":46}],"17":[0,{"line":1,"col":24,"index":42},{"line":1,"col":24,"index":42}],"18":[0,{"line":1,"col":24,"index":42},{"line":1,"col":29,"index":47}],"19":[0,{"line":1,"col":24,"index":42},{"line":1,"col":29,"index":47}],"20":[0,{"line":1,"col":24,"index":42},{"line":1,"col":29,"index":47}],"21":[0,{"line":3,"col":12,"index":62},{"line":3,"col":16,"index":66}],"22":[0,{"line":3,"col":10,"index":60},{"line":3,"col":17,"index":67}],"23":[0,{"line":3,"col":2,"index":52},{"line":3,"col":17,"index":67}],"24":[0,{"line":5,"col":17,"index":87},{"line":5,"col":17,"index":87}],"25":[0,{"line":6,"col":14,"index":105},{"line":6,"col":14,"index":105}],"26":[0,{"line":7,"col":14,"index":121},{"line":7,"col":14,"index":121}],"27":[0,{"line":8,"col":15,"index":138},{"line":8,"col":15,"index":138}],"28":[0,{"line":8,"col":14,"index":137},{"line":8,"col":15,"index":138}],"29":[0,{"line":5,"col":11,"index":81},{"line":9,"col":72,"index":142}],"30":[0,{"line":6,"col":6,"index":97},{"line":6,"col":14,"index":105}],"31":[0,{"line":6,"col":6,"index":97},{"line":6,"col":14,"index":105}],"32":[0,{"line":6,"col":6,"index":97},{"line":6,"col":6,"index":97}],"33":[0,{"line":7,"col":6,"index":113},{"line":7,"col":14,"index":121}],"34":[0,{"line":7,"col":6,"index":113},{"line":7,"col":14,"index":121}],"35":[0,{"line":7,"col":6,"index":113},{"line":7,"col":9,"index":116}],"36":[0,{"line":8,"col":6,"index":129},{"line":8,"col":15,"index":138}],"37":[0,{"line":8,"col":6,"index":129},{"line":8,"col":15,"index":138}],"38":[0,{"line":5,"col":2,"index":72},{"line":9,"col":72,"index":142}],"39":[0,{"line":0,"col":0,"index":0},{"line":10,"col":144,"index":144}]}} \ No newline at end of file +{"sourceIndex":{"0":"mocked_path/testFixture/_1044matchExpression.qnt"},"map":{"1":[0,{"line":1,"col":11,"index":29},{"line":1,"col":11,"index":29}],"2":[0,{"line":1,"col":17,"index":35},{"line":1,"col":19,"index":37}],"3":[0,{"line":1,"col":26,"index":44},{"line":1,"col":28,"index":46}],"4":[0,{"line":1,"col":2,"index":20},{"line":1,"col":29,"index":47}],"5":[0,{"line":1,"col":11,"index":29},{"line":1,"col":11,"index":29}],"6":[0,{"line":1,"col":11,"index":29},{"line":1,"col":11,"index":29}],"7":[0,{"line":1,"col":11,"index":29},{"line":1,"col":11,"index":29}],"8":[0,{"line":1,"col":11,"index":29},{"line":1,"col":11,"index":29}],"9":[0,{"line":1,"col":15,"index":33},{"line":1,"col":20,"index":38}],"10":[0,{"line":1,"col":17,"index":35},{"line":1,"col":19,"index":37}],"11":[0,{"line":1,"col":15,"index":33},{"line":1,"col":15,"index":33}],"12":[0,{"line":1,"col":15,"index":33},{"line":1,"col":20,"index":38}],"13":[0,{"line":1,"col":15,"index":33},{"line":1,"col":20,"index":38}],"14":[0,{"line":1,"col":15,"index":33},{"line":1,"col":20,"index":38}],"15":[0,{"line":1,"col":24,"index":42},{"line":1,"col":29,"index":47}],"16":[0,{"line":1,"col":26,"index":44},{"line":1,"col":28,"index":46}],"17":[0,{"line":1,"col":24,"index":42},{"line":1,"col":24,"index":42}],"18":[0,{"line":1,"col":24,"index":42},{"line":1,"col":29,"index":47}],"19":[0,{"line":1,"col":24,"index":42},{"line":1,"col":29,"index":47}],"20":[0,{"line":1,"col":24,"index":42},{"line":1,"col":29,"index":47}],"21":[0,{"line":3,"col":12,"index":62},{"line":3,"col":16,"index":66}],"22":[0,{"line":3,"col":10,"index":60},{"line":3,"col":17,"index":67}],"23":[0,{"line":3,"col":2,"index":52},{"line":3,"col":17,"index":67}],"24":[0,{"line":5,"col":17,"index":87},{"line":5,"col":17,"index":87}],"25":[0,{"line":6,"col":14,"index":105},{"line":6,"col":14,"index":105}],"26":[0,{"line":7,"col":14,"index":121},{"line":7,"col":14,"index":121}],"27":[0,{"line":8,"col":15,"index":138},{"line":8,"col":15,"index":138}],"28":[0,{"line":8,"col":14,"index":137},{"line":8,"col":15,"index":138}],"29":[0,{"line":5,"col":11,"index":81},{"line":9,"col":72,"index":142}],"30":[0,{"line":6,"col":6,"index":97},{"line":6,"col":14,"index":105}],"31":[0,{"line":6,"col":6,"index":97},{"line":6,"col":14,"index":105}],"32":[0,{"line":6,"col":6,"index":97},{"line":6,"col":14,"index":105}],"33":[0,{"line":7,"col":6,"index":113},{"line":7,"col":14,"index":121}],"34":[0,{"line":7,"col":6,"index":113},{"line":7,"col":14,"index":121}],"35":[0,{"line":7,"col":6,"index":113},{"line":7,"col":14,"index":121}],"36":[0,{"line":8,"col":6,"index":129},{"line":8,"col":15,"index":138}],"37":[0,{"line":8,"col":6,"index":129},{"line":8,"col":15,"index":138}],"38":[0,{"line":8,"col":6,"index":129},{"line":8,"col":15,"index":138}],"39":[0,{"line":5,"col":2,"index":72},{"line":9,"col":72,"index":142}],"40":[0,{"line":0,"col":0,"index":0},{"line":10,"col":144,"index":144}]}} \ No newline at end of file