Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consolidate row simplification utility #1170

Merged
merged 2 commits into from
Sep 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 13 additions & 7 deletions quint/src/graphics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ import { zerog } from './idGenerator'
import { ConcreteFixedRow, QuintType, Row } from './ir/quintTypes'
import { TypeScheme } from './types/base'
import { canonicalTypeScheme } from './types/printing'
import { declarationToString, flattenRow, qualifierToString, rowToString } from './ir/IRprinting'
import { declarationToString, qualifierToString, rowToString } from './ir/IRprinting'
import { simplifyRow } from './types/simplification'

/**
* An abstraction of a Console of bounded text width.
Expand Down Expand Up @@ -205,23 +206,27 @@ export function prettyQuintType(type: QuintType): Doc {
}
}

function prettyRow(row: Row, showFieldName = true): Doc {
const [fields, other] = flattenRow(row)
function prettyRow(r: Row, showFieldName = true): Doc {
const row = simplifyRow(r)
const fields = row.kind === 'row' ? row.fields : []
const other = row.kind === 'row' ? row.other : undefined

const fieldsDocs = fields.map(f => {
const prefix = showFieldName ? `${f.fieldName}: ` : ''
return group([text(prefix), prettyQuintType(f.fieldType)])
})
const otherDoc = other.kind === 'var' ? [text(` | ${other.name}`)] : []
const otherDoc = other?.kind === 'var' ? [text(` | ${other.name}`)] : []

return group([nest(' ', [linebreak, docJoin([text(','), line()], fieldsDocs)]), ...otherDoc, linebreak])
}

function prettySumRow(row: ConcreteFixedRow): Doc {
const [fields, other] = flattenRow(row)
function prettySumRow(r: ConcreteFixedRow): Doc {
const row = simplifyRow(r)
const fields = row.kind === 'row' ? row.fields : []
const other = row.kind === 'row' ? row.other : undefined

const fieldsDocs = fields.map(f => {
if (other.kind === 'empty') {
if (other?.kind === 'empty') {
return group(text(f.fieldName))
} else {
return group([text(`${f.fieldName}(`), prettyQuintType(f.fieldType), text(')')])
Expand All @@ -230,6 +235,7 @@ function prettySumRow(row: ConcreteFixedRow): Doc {

return group([nest(' ', [linebreak, docJoin([text(','), line()], fieldsDocs)]), linebreak])
}

/**
* Print an execution frame and its children recursively.
* Since this function is printing a tree, we need precise text alignment.
Expand Down
2 changes: 1 addition & 1 deletion quint/src/ir/IRTransformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -504,7 +504,7 @@ function transformExpression(transformer: IRTransformer, expr: ir.QuintEx): ir.Q
*
* @returns the transformed Quint row
*/
function transformRow(transformer: IRTransformer, row: t.Row): t.Row {
export function transformRow(transformer: IRTransformer, row: t.Row): t.Row {
let newRow = row
if (transformer.enterRow) {
newRow = transformer.enterRow(newRow)
Expand Down
15 changes: 1 addition & 14 deletions quint/src/ir/IRprinting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
*/

import { OpQualifier, QuintDeclaration, QuintDef, QuintEx, QuintModule, isAnnotatedDef } from './quintIr'
import { EmptyRow, QuintSumType, QuintType, Row, RowField, VarRow, isTheUnit } from './quintTypes'
import { QuintSumType, QuintType, Row, RowField, isTheUnit } from './quintTypes'
import { TypeScheme } from '../types/base'
import { typeSchemeToString } from '../types/printing'

Expand Down Expand Up @@ -269,16 +269,3 @@ function rowFieldsToString(r: Row, showFieldName = true): string {
}
}
}

export function flattenRow(r: Row): [{ fieldName: string; fieldType: QuintType }[], VarRow | EmptyRow] {
switch (r.kind) {
case 'empty':
return [[], r]
case 'var':
return [[], r]
case 'row': {
const [fields, other] = flattenRow(r.other)
return [[...r.fields, ...fields], other]
}
}
}
19 changes: 1 addition & 18 deletions quint/src/types/constraintSolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { Constraint } from './base'
import { Substitutions, applySubstitution, applySubstitutionToConstraint, compose } from './substitutions'
import { unzip } from 'lodash'
import { LookupTable } from '../names/base'
import { simplifyRow } from './simplification'

/*
* Try to solve a constraint by unifying all pairs of types in equality
Expand Down Expand Up @@ -251,24 +252,6 @@ function chainUnifications(table: LookupTable, types1: QuintType[], types2: Quin
}, right([]))
}

function simplifyRow(r: Row): Row {
if (r.kind !== 'row') {
return r
}

let result = r
const otherSimplified = simplifyRow(r.other)
if (otherSimplified.kind === 'row') {
result = { kind: 'row', fields: r.fields.concat(otherSimplified.fields), other: otherSimplified.other }
}

if (result.fields.length > 0) {
return result
} else {
return otherSimplified
}
}

function tryToUnpack(
location: string,
types1: QuintType[],
Expand Down
4 changes: 3 additions & 1 deletion quint/src/types/inferrer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { QuintDeclaration } from '../ir/quintIr'
import { TypeScheme } from './base'
import { ConstraintGeneratorVisitor } from './constraintGenerator'
import { solveConstraint } from './constraintSolver'
import { simplify } from './simplification'

export type TypeInferenceResult = [Map<bigint, ErrorTree>, Map<bigint, TypeScheme>]

Expand All @@ -40,6 +41,7 @@ export class TypeInferrer extends ConstraintGeneratorVisitor {
declarations.forEach(decl => {
walkDeclaration(this, decl)
})
return [this.errors, this.types]
const simplifiedTypes = new Map([...this.types.entries()].map(([id, t]) => [id, simplify(t)]))
return [this.errors, simplifiedTypes]
}
}
65 changes: 65 additions & 0 deletions quint/src/types/simplification.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/* ----------------------------------------------------------------------------------
* Copyright (c) Informal Systems 2023. All rights reserved.
* Licensed under the Apache 2.0.
* See License.txt in the project root for license information.
* --------------------------------------------------------------------------------- */

/**
* Simplification for quint types
*
* @author Gabriela Moreira
*
* @module
*/

import { IRTransformer, transformRow, transformType } from '../ir/IRTransformer'
import { Row } from '../ir/quintTypes'
import { TypeScheme } from './base'

/**
* Simplifies a type scheme by flattening all type rows.
*
* A row like `{ a: int | { b: bool | r } }` becomes
* `{ a: int, b: bool | r }`.
*
* @param typeScheme - The type scheme to be simplified
*
* @returns The simplified type scheme
*/
export function simplify(typeScheme: TypeScheme): TypeScheme {
const simplifier = new RowSimplifier()
return { ...typeScheme, type: transformType(simplifier, typeScheme.type) }
}

/**
* Simplifies a row type.
*
* A row like `{ a: int | { b: bool | r } }` becomes
* `{ a: int, b: bool | r }`.
*
* @param row - The tow to be simplified
*
* @returns The simplified row
*/
export function simplifyRow(row: Row): Row {
const simplifier = new RowSimplifier()
return transformRow(simplifier, row)
}

class RowSimplifier implements IRTransformer {
exitRow(row: Row): Row {
if (row.kind !== 'row') {
return row
}

if (row.fields.length === 0) {
return row.other
}

if (row.other.kind !== 'row') {
return row
}

return { ...row, fields: row.fields.concat(row.other.fields), other: row.other.other }
}
}
56 changes: 56 additions & 0 deletions quint/test/types/simplification.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { describe, it } from 'mocha'
import { assert } from 'chai'
import { typeSchemeToString } from '../../src/types/printing'
import { simplify } from '../../src/types/simplification'
import { toScheme } from '../../src/types/base'
import { QuintRecordType } from '../../src'

describe('simplify', () => {
it('flattens nested row tails', () => {
// { a: int | { b: bool | { c: str | v } } }
const type: QuintRecordType = {
kind: 'rec',
fields: {
kind: 'row',
fields: [{ fieldName: 'a', fieldType: { kind: 'int' } }],
other: {
kind: 'row',
fields: [{ fieldName: 'b', fieldType: { kind: 'bool' } }],
other: {
kind: 'row',
fields: [{ fieldName: 'c', fieldType: { kind: 'str' } }],
other: { kind: 'var', name: 'v' },
},
},
},
}

const simplifiedType = simplify(toScheme(type))

assert.deepEqual(typeSchemeToString(simplifiedType), '{ a: int, b: bool, c: str | v }')
})

it('simplifies rows with empty fields', () => {
// { | { b: bool | { c: str | v } } }
const type: QuintRecordType = {
kind: 'rec',
fields: {
kind: 'row',
fields: [],
other: {
kind: 'row',
fields: [{ fieldName: 'b', fieldType: { kind: 'bool' } }],
other: {
kind: 'row',
fields: [{ fieldName: 'c', fieldType: { kind: 'str' } }],
other: { kind: 'var', name: 'v' },
},
},
},
}

const simplifiedType = simplify(toScheme(type))

assert.deepEqual(typeSchemeToString(simplifiedType), '{ b: bool, c: str | v }')
})
})