From b0e21f86ca1786613d0491ca8df086b998b656c7 Mon Sep 17 00:00:00 2001 From: Michael Toy <66150587+mtoy-googly-moogly@users.noreply.github.com> Date: Mon, 28 Oct 2024 10:15:34 -0700 Subject: [PATCH] Make TypeDesc contain/be a type interface (#1976) * better parse errors for records and arrays * Change TypeDesc to BE a TypeDef * all hail our benevolent protectors * mini review * go ahead and go back to 'turtle' * get cascade type correct * move the def util to TDU * all hail * cleanup * make TDU a normal module * docs update --- .../functions/malloy_standard_functions.ts | 4 +- packages/malloy/src/dialect/functions/util.ts | 97 ++++------ packages/malloy/src/doc/typedesc.md | 32 ++++ packages/malloy/src/index.ts | 2 - packages/malloy/src/lang/ast/ast-utils.ts | 2 +- .../lang/ast/expressions/binary-boolean.ts | 8 +- .../lang/ast/expressions/binary-numeric.ts | 4 +- .../src/lang/ast/expressions/boolean.ts | 4 +- .../malloy/src/lang/ast/expressions/case.ts | 37 ++-- .../expressions/expr-aggregate-function.ts | 20 +- .../ast/expressions/expr-alternation-tree.ts | 4 +- .../lang/ast/expressions/expr-asymmetric.ts | 5 + .../src/lang/ast/expressions/expr-cast.ts | 12 +- .../src/lang/ast/expressions/expr-coalesce.ts | 15 +- .../src/lang/ast/expressions/expr-compare.ts | 20 +- .../ast/expressions/expr-count-distinct.ts | 15 +- .../src/lang/ast/expressions/expr-count.ts | 13 +- .../src/lang/ast/expressions/expr-func.ts | 43 +++-- .../ast/expressions/expr-granular-time.ts | 32 ++-- .../lang/ast/expressions/expr-logical-op.ts | 4 +- .../src/lang/ast/expressions/expr-max.ts | 12 +- .../src/lang/ast/expressions/expr-min.ts | 12 +- .../src/lang/ast/expressions/expr-minus.ts | 8 +- .../src/lang/ast/expressions/expr-not.ts | 6 +- .../src/lang/ast/expressions/expr-now.ts | 2 +- .../src/lang/ast/expressions/expr-null.ts | 2 +- .../src/lang/ast/expressions/expr-number.ts | 7 +- .../src/lang/ast/expressions/expr-props.ts | 4 +- .../src/lang/ast/expressions/expr-regex.ts | 2 +- .../src/lang/ast/expressions/expr-string.ts | 2 +- .../lang/ast/expressions/expr-time-extract.ts | 37 ++-- .../src/lang/ast/expressions/expr-time.ts | 8 +- .../src/lang/ast/expressions/expr-ungroup.ts | 11 +- .../src/lang/ast/expressions/for-range.ts | 18 +- .../src/lang/ast/expressions/pick-when.ts | 48 +++-- .../malloy/src/lang/ast/expressions/range.ts | 2 +- .../src/lang/ast/expressions/time-literal.ts | 13 +- .../lang/ast/field-space/index-field-space.ts | 7 +- .../ast/field-space/project-field-space.ts | 6 +- .../src/lang/ast/field-space/query-spaces.ts | 7 +- .../lang/ast/field-space/reference-field.ts | 33 ++-- .../ast/field-space/rename-space-field.ts | 8 +- .../src/lang/ast/field-space/static-space.ts | 2 +- .../field-space/struct-space-field-base.ts | 24 ++- .../src/lang/ast/field-space/view-field.ts | 7 +- .../malloy/src/lang/ast/fragtype-utils.ts | 137 -------------- .../src/lang/ast/parameters/has-parameter.ts | 18 +- .../lang/ast/query-items/field-declaration.ts | 24 ++- .../lang/ast/query-items/typecheck_utils.ts | 28 ++- .../src/lang/ast/query-properties/filters.ts | 2 +- .../lang/ast/source-elements/named-source.ts | 4 +- .../src/lang/ast/source-properties/join.ts | 6 +- packages/malloy/src/lang/ast/time-utils.ts | 12 +- .../malloy/src/lang/ast/typedesc-utils.ts | 174 ++++++++++++++++++ .../malloy/src/lang/ast/types/expr-result.ts | 4 +- .../malloy/src/lang/ast/types/expr-value.ts | 59 +++--- .../src/lang/ast/types/expression-def.ts | 138 +++++++------- .../src/lang/ast/types/global-name-space.ts | 6 +- .../src/lang/ast/types/granular-result.ts | 7 +- .../malloy/src/lang/ast/types/space-field.ts | 17 +- .../malloy/src/lang/ast/types/space-param.ts | 20 +- .../malloy/src/lang/ast/types/time-result.ts | 10 +- packages/malloy/src/lang/parse-log.ts | 30 +-- .../malloy/src/lang/test/literals.spec.ts | 2 +- .../malloy/src/lang/test/parse-expects.ts | 6 +- packages/malloy/src/model/malloy_types.ts | 105 ++++++----- 66 files changed, 770 insertions(+), 700 deletions(-) create mode 100644 packages/malloy/src/doc/typedesc.md delete mode 100644 packages/malloy/src/lang/ast/fragtype-utils.ts create mode 100644 packages/malloy/src/lang/ast/typedesc-utils.ts diff --git a/packages/malloy/src/dialect/functions/malloy_standard_functions.ts b/packages/malloy/src/dialect/functions/malloy_standard_functions.ts index 275c81c77..d34829769 100644 --- a/packages/malloy/src/dialect/functions/malloy_standard_functions.ts +++ b/packages/malloy/src/dialect/functions/malloy_standard_functions.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import {ExpressionValueType} from '../../model'; +import {LeafExpressionType} from '../../model/malloy_types'; import { DefinitionBlueprint, DialectFunctionOverloadDef, @@ -505,7 +505,7 @@ const first_value: DefinitionFor = { impl: {function: 'FIRST_VALUE', needsWindowOrderBy: true}, }; -const LAG_TYPES: ExpressionValueType[] = [ +const LAG_TYPES: LeafExpressionType[] = [ 'string', 'number', 'timestamp', diff --git a/packages/malloy/src/dialect/functions/util.ts b/packages/malloy/src/dialect/functions/util.ts index 6c1ccf5de..58c32da13 100644 --- a/packages/malloy/src/dialect/functions/util.ts +++ b/packages/malloy/src/dialect/functions/util.ts @@ -23,12 +23,11 @@ import { FunctionParameterDef, - FieldValueType, TypeDesc, Expr, FunctionParamTypeDesc, GenericSQLExpr, - ExpressionValueType, + LeafExpressionType, } from '../../model/malloy_types'; import {SQLExprElement} from '../../model/utils'; @@ -140,70 +139,38 @@ export function makeParam( return {param: param(name, ...allowedTypes), arg: arg(name)}; } -export function maxScalar(dataType: FieldValueType): TypeDesc { - return { - dataType, - expressionType: 'scalar', - evalSpace: 'input', - }; +export function maxScalar(type: LeafExpressionType): TypeDesc { + return {type, expressionType: 'scalar', evalSpace: 'input'}; } -export function maxAggregate(dataType: FieldValueType): TypeDesc { - return { - dataType, - expressionType: 'aggregate', - evalSpace: 'input', - }; +export function maxAggregate(type: LeafExpressionType): TypeDesc { + return {type, expressionType: 'aggregate', evalSpace: 'input'}; } -export function anyExprType(dataType: FieldValueType): FunctionParamTypeDesc { - return { - dataType, - expressionType: undefined, - evalSpace: 'input', - }; +export function anyExprType(type: LeafExpressionType): FunctionParamTypeDesc { + return {type, expressionType: undefined, evalSpace: 'input'}; } export function maxUngroupedAggregate( - dataType: FieldValueType + type: LeafExpressionType ): FunctionParamTypeDesc { - return { - dataType, - expressionType: 'ungrouped_aggregate', - evalSpace: 'input', - }; + return {type, expressionType: 'ungrouped_aggregate', evalSpace: 'input'}; } -export function maxAnalytic(dataType: FieldValueType): FunctionParamTypeDesc { - return { - dataType, - expressionType: 'aggregate_analytic', - evalSpace: 'input', - }; +export function maxAnalytic(type: LeafExpressionType): FunctionParamTypeDesc { + return {type, expressionType: 'aggregate_analytic', evalSpace: 'input'}; } -export function minScalar(dataType: FieldValueType): TypeDesc { - return { - dataType, - expressionType: 'scalar', - evalSpace: 'input', - }; +export function minScalar(type: LeafExpressionType): TypeDesc { + return {type, expressionType: 'scalar', evalSpace: 'input'}; } -export function minAggregate(dataType: FieldValueType): TypeDesc { - return { - dataType, - expressionType: 'aggregate', - evalSpace: 'input', - }; +export function minAggregate(type: LeafExpressionType): TypeDesc { + return {type, expressionType: 'aggregate', evalSpace: 'input'}; } -export function minAnalytic(dataType: FieldValueType): TypeDesc { - return { - dataType, - expressionType: 'scalar_analytic', - evalSpace: 'input', - }; +export function minAnalytic(type: LeafExpressionType): TypeDesc { + return {type, expressionType: 'scalar_analytic', evalSpace: 'input'}; } export function overload( @@ -235,13 +202,13 @@ export function overload( export type TypeDescBlueprint = // default for return type is min scalar // default for param type is any expression type (max input) - | ExpressionValueType + | LeafExpressionType | {generic: string} - | {literal: ExpressionValueType | {generic: string}} - | {constant: ExpressionValueType | {generic: string}} - | {dimension: ExpressionValueType | {generic: string}} - | {measure: ExpressionValueType | {generic: string}} - | {calculation: ExpressionValueType | {generic: string}}; + | {literal: LeafExpressionType | {generic: string}} + | {constant: LeafExpressionType | {generic: string}} + | {dimension: LeafExpressionType | {generic: string}} + | {measure: LeafExpressionType | {generic: string}} + | {calculation: LeafExpressionType | {generic: string}}; type ParamTypeBlueprint = | TypeDescBlueprint @@ -251,7 +218,7 @@ type ParamTypeBlueprint = export interface SignatureBlueprint { // today only one generic is allowed, but if we need more // we could change this to `{[name: string]: ExpressionValueType[]}` - generic?: [string, ExpressionValueType[]]; + generic?: [string, LeafExpressionType[]]; takes: {[name: string]: ParamTypeBlueprint}; returns: TypeDescBlueprint; supportsOrderBy?: boolean | 'only_default'; @@ -303,8 +270,8 @@ export type OverrideMap = { }; function removeGeneric( - type: ExpressionValueType | {generic: string}, - generic: {name: string; type: ExpressionValueType} | undefined + type: LeafExpressionType | {generic: string}, + generic: {name: string; type: LeafExpressionType} | undefined ) { if (typeof type === 'string') { return type; @@ -317,7 +284,7 @@ function removeGeneric( function expandReturnTypeBlueprint( blueprint: TypeDescBlueprint, - generic: {name: string; type: ExpressionValueType} | undefined + generic: {name: string; type: LeafExpressionType} | undefined ): TypeDesc { if (typeof blueprint === 'string') { return minScalar(blueprint); @@ -366,7 +333,7 @@ function extractParamTypeBlueprints( function expandParamTypeBlueprint( blueprint: TypeDescBlueprint, - generic: {name: string; type: ExpressionValueType} | undefined + generic: {name: string; type: LeafExpressionType} | undefined ): FunctionParamTypeDesc { if (typeof blueprint === 'string') { return anyExprType(blueprint); @@ -387,7 +354,7 @@ function expandParamTypeBlueprint( function expandParamTypeBlueprints( blueprints: TypeDescBlueprint[], - generic: {name: string; type: ExpressionValueType} | undefined + generic: {name: string; type: LeafExpressionType} | undefined ) { return blueprints.map(blueprint => expandParamTypeBlueprint(blueprint, generic) @@ -401,7 +368,7 @@ function isVariadicParamBlueprint(blueprint: ParamTypeBlueprint): boolean { function expandParamBlueprint( name: string, blueprint: ParamTypeBlueprint, - generic: {name: string; type: ExpressionValueType} | undefined + generic: {name: string; type: LeafExpressionType} | undefined ): FunctionParameterDef { return { name, @@ -415,7 +382,7 @@ function expandParamBlueprint( function expandParamsBlueprints( blueprints: {[name: string]: ParamTypeBlueprint}, - generic: {name: string; type: ExpressionValueType} | undefined + generic: {name: string; type: LeafExpressionType} | undefined ) { const paramsArray = Object.entries(blueprints); return paramsArray.map(blueprint => @@ -522,7 +489,7 @@ function expandImplBlueprint(blueprint: DefinitionBlueprint): { function expandOneBlueprint( blueprint: DefinitionBlueprint, - generic?: {name: string; type: ExpressionValueType} + generic?: {name: string; type: LeafExpressionType} ): DialectFunctionOverloadDef { return { returnType: expandReturnTypeBlueprint(blueprint.returns, generic), diff --git a/packages/malloy/src/doc/typedesc.md b/packages/malloy/src/doc/typedesc.md new file mode 100644 index 000000000..f08749bf7 --- /dev/null +++ b/packages/malloy/src/doc/typedesc.md @@ -0,0 +1,32 @@ +# TypeDesc + +## Types +Mostly in the translator, there is the need for more meta data about expressions than exists in a [FieldDef/QueryFieldDef](fielddef.md). `TypeDesc` is an extension of the type portion of a fielddef, which contains every type that a name lookup, or expression evaluation might result in. + +In addition to `AtomicFieldType`s (which can be stored in a column), the other types which cannot be stored in a database column are: + +* durations +* regular expression +* join +* view +* null + +The `type:` field of a `TypeDesc` is in the same "type space" as [StructDef](structdef.md) and [FieldDef](fielddef.md), with the following weirdnesses + +* A joined `SourceDef` will have it's `type:` field match the field version, but the `TypeDesc` will not include the definition +* A view will have `type: 'turtle'` but the `TypeDesc` will not include the definition +* An array or a record **will** include the type definition, because the schema of the contents is part of the type. + +## Additional type metadata + +A TypeDesc also has an `expressionType` and an `evalSpace` which are used by the translator to generate correct code and also catch a wide variety of errors. + +## Other cousins + +The types `Parameter` and `FunctionParamTypeDesc` can be used most places where a `TypeDesc` is accepted. A `FunctionParamTypeDesc` also can have an `any` type and a `Parameter` has a narrower set of types. + +## TDU + +`typedesc-utils.ts`, typically imported `* as TDU`, contains an number of utility functions for dealing with `TypeDesc` types, including pre-made typedescs for all the atomic types. + +In previous versions, much of this functionality was accessed with the prefix `FT` which was a remnant of the days when sub expressions were called "fragments" \ No newline at end of file diff --git a/packages/malloy/src/index.ts b/packages/malloy/src/index.ts index d3e0a4d2a..d69b3758c 100644 --- a/packages/malloy/src/index.ts +++ b/packages/malloy/src/index.ts @@ -93,8 +93,6 @@ export type { FunctionParameterDef, ExpressionValueType, TypeDesc, - FieldValueType, - ExpressionTypeDesc, FunctionParamTypeDesc, // used in MalloyError.log DocumentLocation, diff --git a/packages/malloy/src/lang/ast/ast-utils.ts b/packages/malloy/src/lang/ast/ast-utils.ts index 4c2c1a1a1..aa1c5519e 100644 --- a/packages/malloy/src/lang/ast/ast-utils.ts +++ b/packages/malloy/src/lang/ast/ast-utils.ts @@ -33,7 +33,7 @@ import {ExprValue} from './types/expr-value'; */ export function errorFor(reason: string): ExprValue { return { - dataType: 'error', + type: 'error', expressionType: 'scalar', value: {node: 'error', message: reason}, evalSpace: 'constant', diff --git a/packages/malloy/src/lang/ast/expressions/binary-boolean.ts b/packages/malloy/src/lang/ast/expressions/binary-boolean.ts index e2faedd37..2f726e749 100644 --- a/packages/malloy/src/lang/ast/expressions/binary-boolean.ts +++ b/packages/malloy/src/lang/ast/expressions/binary-boolean.ts @@ -22,7 +22,7 @@ */ import {errorFor} from '../ast-utils'; -import {FT} from '../fragtype-utils'; +import * as TDU from '../typedesc-utils'; import {BinaryMalloyOperator, getExprNode} from '../types/binary_operators'; import {ExprValue, computedExprValue} from '../types/expr-value'; import {ExpressionDef} from '../types/expression-def'; @@ -32,7 +32,7 @@ export abstract class BinaryBoolean< opType extends BinaryMalloyOperator, > extends ExpressionDef { elementType = 'abstract boolean binary'; - legalChildTypes = [FT.boolT]; + legalChildTypes = [TDU.boolT]; constructor( readonly left: ExpressionDef, readonly op: opType, @@ -46,7 +46,7 @@ export abstract class BinaryBoolean< const right = this.right.getExpression(fs); if (this.typeCheck(this.left, left) && this.typeCheck(this.right, right)) { return computedExprValue({ - dataType: 'boolean', + dataType: {type: 'boolean'}, value: { node: getExprNode(this.op), kids: {left: left.value, right: right.value}, @@ -54,6 +54,6 @@ export abstract class BinaryBoolean< from: [left, right], }); } - return errorFor('logial required boolean'); + return errorFor('logical-op expected boolean'); } } diff --git a/packages/malloy/src/lang/ast/expressions/binary-numeric.ts b/packages/malloy/src/lang/ast/expressions/binary-numeric.ts index 79aa5f8e4..24718cd2e 100644 --- a/packages/malloy/src/lang/ast/expressions/binary-numeric.ts +++ b/packages/malloy/src/lang/ast/expressions/binary-numeric.ts @@ -21,7 +21,7 @@ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import {FT} from '../fragtype-utils'; +import * as TDU from '../typedesc-utils'; import {ArithmeticMalloyOperator} from '../types/binary_operators'; import {ExprValue} from '../types/expr-value'; import {ExpressionDef} from '../types/expression-def'; @@ -37,7 +37,7 @@ export abstract class BinaryNumeric< readonly right: ExpressionDef ) { super({left: left, right: right}); - this.legalChildTypes = [FT.numberT]; + this.legalChildTypes = [TDU.numberT]; } getExpression(fs: FieldSpace): ExprValue { diff --git a/packages/malloy/src/lang/ast/expressions/boolean.ts b/packages/malloy/src/lang/ast/expressions/boolean.ts index 8c0c91d59..43e5b25f9 100644 --- a/packages/malloy/src/lang/ast/expressions/boolean.ts +++ b/packages/malloy/src/lang/ast/expressions/boolean.ts @@ -23,7 +23,7 @@ import {ExprValue} from '../types/expr-value'; import {ExpressionDef} from '../types/expression-def'; -import {FT} from '../fragtype-utils'; +import * as TDU from '../typedesc-utils'; export class Boolean extends ExpressionDef { elementType = 'boolean literal'; @@ -32,6 +32,6 @@ export class Boolean extends ExpressionDef { } getExpression(): ExprValue { - return {...FT.boolT, value: {node: this.value}}; + return {...TDU.boolT, value: {node: this.value}}; } } diff --git a/packages/malloy/src/lang/ast/expressions/case.ts b/packages/malloy/src/lang/ast/expressions/case.ts index 279b98a6d..687b407f3 100644 --- a/packages/malloy/src/lang/ast/expressions/case.ts +++ b/packages/malloy/src/lang/ast/expressions/case.ts @@ -9,7 +9,7 @@ import {ExprValue, computedExprValue} from '../types/expr-value'; import {ExpressionDef} from '../types/expression-def'; import {FieldSpace} from '../types/field-space'; import {MalloyElement} from '../types/malloy-element'; -import {FT} from '../fragtype-utils'; +import * as TDU from '../typedesc-utils'; import {CaseExpr} from '../../../model'; interface Choice { @@ -18,9 +18,7 @@ interface Choice { } function typeCoalesce(ev1: ExprValue | undefined, ev2: ExprValue): ExprValue { - return ev1 === undefined || - ev1.dataType === 'null' || - ev1.dataType === 'error' + return ev1 === undefined || ev1.type === 'null' || ev1.type === 'error' ? ev2 : ev1; } @@ -47,9 +45,10 @@ export class Case extends ExpressionDef { const dependents: ExprValue[] = []; let value: ExprValue | undefined = undefined; if (this.value) { - value = this.value.getExpression(fs); - dependents.push(value); - resultExpr.kids.caseValue = value.value; + const v = this.value.getExpression(fs); + dependents.push(v); + resultExpr.kids.caseValue = v.value; + value = v; } const choiceValues: Choice[] = []; for (const c of this.choices) { @@ -61,23 +60,23 @@ export class Case extends ExpressionDef { let returnType: ExprValue | undefined; for (const aChoice of choiceValues) { if (value !== undefined) { - if (!FT.typeEq(aChoice.when, value)) { + if (!TDU.typeEq(aChoice.when, value)) { return this.loggedErrorExpr('case-when-type-does-not-match', { - whenType: aChoice.when.dataType, - valueType: value.dataType, + whenType: aChoice.when.type, + valueType: value.type, }); } } else { - if (!FT.typeEq(aChoice.when, FT.boolT)) { + if (!TDU.typeEq(aChoice.when, TDU.boolT)) { return this.loggedErrorExpr('case-when-must-be-boolean', { - whenType: aChoice.when.dataType, + whenType: aChoice.when.type, }); } } - if (returnType && !FT.typeEq(returnType, aChoice.then, true)) { + if (returnType && !TDU.typeEq(returnType, aChoice.then, true)) { return this.loggedErrorExpr('case-then-type-does-not-match', { - thenType: aChoice.then.dataType, - returnType: returnType.dataType, + thenType: aChoice.then.type, + returnType: returnType.type, }); } returnType = typeCoalesce(returnType, aChoice.then); @@ -86,10 +85,10 @@ export class Case extends ExpressionDef { } if (this.elseValue) { const elseValue = this.elseValue.getExpression(fs); - if (returnType && !FT.typeEq(returnType, elseValue, true)) { + if (returnType && !TDU.typeEq(returnType, elseValue, true)) { return this.loggedErrorExpr('case-else-type-does-not-match', { - elseType: elseValue.dataType, - returnType: returnType.dataType, + elseType: elseValue.type, + returnType: returnType.type, }); } returnType = typeCoalesce(returnType, elseValue); @@ -98,7 +97,7 @@ export class Case extends ExpressionDef { } return computedExprValue({ value: resultExpr, - dataType: returnType?.dataType ?? 'null', + dataType: returnType ? TDU.atomicDef(returnType) : {type: 'null'}, from: dependents, }); } diff --git a/packages/malloy/src/lang/ast/expressions/expr-aggregate-function.ts b/packages/malloy/src/lang/ast/expressions/expr-aggregate-function.ts index 2b3b9a788..47a7628ac 100644 --- a/packages/malloy/src/lang/ast/expressions/expr-aggregate-function.ts +++ b/packages/malloy/src/lang/ast/expressions/expr-aggregate-function.ts @@ -25,7 +25,6 @@ import { AggregateFunctionType, expressionIsAggregate, FieldDef, - FieldValueType, isAtomicFieldType, AggregateExpr, Expr, @@ -37,7 +36,7 @@ import {exprWalk} from '../../../model/utils'; import {errorFor} from '../ast-utils'; import {StructSpaceField} from '../field-space/static-space'; -import {FT} from '../fragtype-utils'; +import * as TDU from '../typedesc-utils'; import {FieldReference} from '../query-items/field-references'; import {ExprValue} from '../types/expr-value'; import {ExpressionDef} from '../types/expression-def'; @@ -52,7 +51,7 @@ export abstract class ExprAggregateFunction extends ExpressionDef { source?: FieldReference; expr?: ExpressionDef; explicitSource?: boolean; - legalChildTypes = [FT.numberT]; + legalChildTypes = [TDU.numberT]; constructor( readonly func: AggregateFunctionType, expr?: ExpressionDef, @@ -66,10 +65,7 @@ export abstract class ExprAggregateFunction extends ExpressionDef { this.has({expr: expr}); } } - - returns(_forExpression: ExprValue): FieldValueType { - return 'number'; - } + abstract returns(fromExpr: ExprValue): ExprValue; getExpression(fs: FieldSpace): ExprValue { // It is never useful to use output fields in an aggregate expression @@ -86,10 +82,10 @@ export abstract class ExprAggregateFunction extends ExpressionDef { const sourceFoot = result.found; const footType = sourceFoot.typeDesc(); if (!(sourceFoot instanceof StructSpaceField)) { - if (isAtomicFieldType(footType.dataType)) { + if (isAtomicFieldType(footType.type)) { expr = this.source; exprVal = { - dataType: footType.dataType, + ...TDU.atomicDef(footType), expressionType: footType.expressionType, value: footType.evalSpace === 'output' @@ -113,7 +109,7 @@ export abstract class ExprAggregateFunction extends ExpressionDef { } else { return this.loggedErrorExpr( 'invalid-aggregate-source', - `Aggregate source cannot be a ${footType.dataType}` + `Aggregate source cannot be a ${footType.type}` ); } } @@ -136,7 +132,7 @@ export abstract class ExprAggregateFunction extends ExpressionDef { 'Aggregate expression cannot be aggregate' ); } - const isAnError = exprVal.dataType === 'error'; + const isAnError = exprVal.type === 'error'; if (!isAnError) { const joinUsage = this.getJoinUsage(inputFS); // Did the user spceify a source, either as `source.agg()` or `path.to.join.agg()` or `path.to.field.agg()` @@ -183,7 +179,7 @@ export abstract class ExprAggregateFunction extends ExpressionDef { f.structPath = structPath; } return { - dataType: this.returns(exprVal), + ...this.returns(exprVal), expressionType: 'aggregate', value: f, evalSpace: 'output', diff --git a/packages/malloy/src/lang/ast/expressions/expr-alternation-tree.ts b/packages/malloy/src/lang/ast/expressions/expr-alternation-tree.ts index 1411d3687..81c704f60 100644 --- a/packages/malloy/src/lang/ast/expressions/expr-alternation-tree.ts +++ b/packages/malloy/src/lang/ast/expressions/expr-alternation-tree.ts @@ -87,7 +87,7 @@ export class ExprAlternationTree extends ExpressionDef { const isIn = expr.getExpression(fs); const values = inList.map(v => v.getExpression(fs)); return computedExprValue({ - dataType: 'boolean', + dataType: {type: 'boolean'}, value: { node: 'in', not: applyOp === '!=', @@ -106,7 +106,7 @@ export class ExprAlternationTree extends ExpressionDef { const choice1 = this.left.apply(fs, applyOp, expr); const choice2 = this.right.apply(fs, applyOp, expr); return computedExprValue({ - dataType: 'boolean', + dataType: {type: 'boolean'}, value: { node: this.op === '&' ? 'and' : 'or', kids: {left: choice1.value, right: choice2.value}, diff --git a/packages/malloy/src/lang/ast/expressions/expr-asymmetric.ts b/packages/malloy/src/lang/ast/expressions/expr-asymmetric.ts index 62e7a8c32..12ee8f46a 100644 --- a/packages/malloy/src/lang/ast/expressions/expr-asymmetric.ts +++ b/packages/malloy/src/lang/ast/expressions/expr-asymmetric.ts @@ -22,6 +22,7 @@ */ import {FieldReference} from '../query-items/field-references'; +import {ExprValue} from '../types/expr-value'; import {ExpressionDef} from '../types/expression-def'; import {ExprAggregateFunction} from './expr-aggregate-function'; @@ -40,6 +41,10 @@ export abstract class ExprAsymmetric extends ExprAggregateFunction { return false; } + returns(ev: ExprValue): ExprValue { + return ev; + } + defaultFieldName(): undefined | string { if (this.source && this.expr === undefined) { const tag = this.source.nameString; diff --git a/packages/malloy/src/lang/ast/expressions/expr-cast.ts b/packages/malloy/src/lang/ast/expressions/expr-cast.ts index 526bd93f5..a1904680f 100644 --- a/packages/malloy/src/lang/ast/expressions/expr-cast.ts +++ b/packages/malloy/src/lang/ast/expressions/expr-cast.ts @@ -21,7 +21,7 @@ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import {AtomicFieldType, CastType} from '../../../model'; +import {CastType, LeafAtomicTypeDef} from '../../../model'; import {castTo} from '../time-utils'; import {ExprValue, computedExprValue} from '../types/expr-value'; import {ExpressionDef} from '../types/expression-def'; @@ -39,16 +39,14 @@ export class ExprCast extends ExpressionDef { getExpression(fs: FieldSpace): ExprValue { const expr = this.expr.getExpression(fs); - let dataType: AtomicFieldType = 'error'; + let dataType: LeafAtomicTypeDef = {type: 'error'}; if (typeof this.castType === 'string') { - dataType = this.castType; + dataType = {type: this.castType}; } else { const dialect = fs.dialectObj(); if (dialect) { if (dialect.validateTypeName(this.castType.raw)) { - // TODO theoretically `sqlTypeToMalloyType` can get number subtypes, - // but `TypeDesc` does not support them. - dataType = dialect.sqlTypeToMalloyType(this.castType.raw).type; + dataType = dialect.sqlTypeToMalloyType(this.castType.raw); } else { this.logError( 'invalid-sql-native-type', @@ -65,7 +63,7 @@ export class ExprCast extends ExpressionDef { } return computedExprValue({ dataType, - value: castTo(this.castType, expr.value, expr.dataType, this.safe), + value: castTo(this.castType, expr.value, expr.type, this.safe), from: [expr], }); } diff --git a/packages/malloy/src/lang/ast/expressions/expr-coalesce.ts b/packages/malloy/src/lang/ast/expressions/expr-coalesce.ts index 19dd7785d..1593030d4 100644 --- a/packages/malloy/src/lang/ast/expressions/expr-coalesce.ts +++ b/packages/malloy/src/lang/ast/expressions/expr-coalesce.ts @@ -22,14 +22,14 @@ */ import {maxExpressionType, mergeEvalSpaces} from '../../../model'; -import {FT} from '../fragtype-utils'; +import * as TDU from '../typedesc-utils'; import {ExprValue} from '../types/expr-value'; import {ExpressionDef} from '../types/expression-def'; import {FieldSpace} from '../types/field-space'; export class ExprCoalesce extends ExpressionDef { elementType = 'coalesce expression'; - legalChildTypes = FT.anyAtomicT; + legalChildTypes = TDU.anyAtomicT; constructor( readonly expr: ExpressionDef, readonly altExpr: ExpressionDef @@ -40,7 +40,7 @@ export class ExprCoalesce extends ExpressionDef { getExpression(fs: FieldSpace): ExprValue { const maybeNull = this.expr.getExpression(fs); const whenNull = this.altExpr.getExpression(fs); - if (maybeNull.dataType === 'null') { + if (maybeNull.type === 'null') { return whenNull; } /** @@ -51,16 +51,15 @@ export class ExprCoalesce extends ExpressionDef { * SQL, but I decided that is will happen when the "expressions are true * trees" rewrite happens. */ - if (!FT.typeEq(maybeNull, whenNull)) { + if (!TDU.typeEq(maybeNull, whenNull)) { this.logError( 'mismatched-coalesce-types', - `Mismatched types for coalesce (${maybeNull.dataType}, ${whenNull.dataType})` + `Mismatched types for coalesce (${maybeNull.type}, ${whenNull.type})` ); } + const srcForType = maybeNull.type === 'error' ? whenNull : maybeNull; return { - ...whenNull, - dataType: - maybeNull.dataType === 'error' ? whenNull.dataType : maybeNull.dataType, + ...srcForType, expressionType: maxExpressionType( maybeNull.expressionType, whenNull.expressionType diff --git a/packages/malloy/src/lang/ast/expressions/expr-compare.ts b/packages/malloy/src/lang/ast/expressions/expr-compare.ts index 4fc9bce64..3f7c8b411 100644 --- a/packages/malloy/src/lang/ast/expressions/expr-compare.ts +++ b/packages/malloy/src/lang/ast/expressions/expr-compare.ts @@ -22,7 +22,7 @@ */ import {maxExpressionType, mergeEvalSpaces} from '../../../model'; -import {FT} from '../fragtype-utils'; +import * as TDU from '../typedesc-utils'; import { BinaryMalloyOperator, CompareMalloyOperator, @@ -34,14 +34,14 @@ import {FieldSpace} from '../types/field-space'; import {BinaryBoolean} from './binary-boolean'; const compareTypes = { - '~': [FT.stringT], - '!~': [FT.stringT], - '<': [FT.numberT, FT.stringT, FT.dateT, FT.timestampT], - '<=': [FT.numberT, FT.stringT, FT.dateT, FT.timestampT], - '=': [FT.numberT, FT.stringT, FT.dateT, FT.timestampT], - '!=': [FT.numberT, FT.stringT, FT.dateT, FT.timestampT], - '>=': [FT.numberT, FT.stringT, FT.dateT, FT.timestampT], - '>': [FT.numberT, FT.stringT, FT.dateT, FT.timestampT], + '~': [TDU.stringT], + '!~': [TDU.stringT], + '<': [TDU.numberT, TDU.stringT, TDU.dateT, TDU.timestampT], + '<=': [TDU.numberT, TDU.stringT, TDU.dateT, TDU.timestampT], + '=': [TDU.numberT, TDU.stringT, TDU.dateT, TDU.timestampT], + '!=': [TDU.numberT, TDU.stringT, TDU.dateT, TDU.timestampT], + '>=': [TDU.numberT, TDU.stringT, TDU.dateT, TDU.timestampT], + '>': [TDU.numberT, TDU.stringT, TDU.dateT, TDU.timestampT], }; export class ExprCompare extends BinaryBoolean { @@ -110,7 +110,7 @@ export class ExprLegacyIn extends ExpressionDef { return choice.value; }); return { - dataType: 'boolean', + type: 'boolean', expressionType, evalSpace, value: { diff --git a/packages/malloy/src/lang/ast/expressions/expr-count-distinct.ts b/packages/malloy/src/lang/ast/expressions/expr-count-distinct.ts index 74dc9af7c..567644f61 100644 --- a/packages/malloy/src/lang/ast/expressions/expr-count-distinct.ts +++ b/packages/malloy/src/lang/ast/expressions/expr-count-distinct.ts @@ -21,13 +21,24 @@ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import {FT} from '../fragtype-utils'; +import * as TDU from '../typedesc-utils'; import {ExprAggregateFunction} from './expr-aggregate-function'; import {ExpressionDef} from '../types/expression-def'; +import {ExprValue} from '../types/expr-value'; export class ExprCountDistinct extends ExprAggregateFunction { - legalChildTypes = [FT.numberT, FT.stringT, FT.dateT, FT.timestampT]; + legalChildTypes = [TDU.numberT, TDU.stringT, TDU.dateT, TDU.timestampT]; constructor(expr: ExpressionDef) { super('distinct', expr); } + + returns(ev: ExprValue): ExprValue { + return { + type: 'number', + numberType: 'integer', + evalSpace: ev.evalSpace, + expressionType: 'aggregate', + value: ev.value, + }; + } } diff --git a/packages/malloy/src/lang/ast/expressions/expr-count.ts b/packages/malloy/src/lang/ast/expressions/expr-count.ts index f959e45db..f6590cfb1 100644 --- a/packages/malloy/src/lang/ast/expressions/expr-count.ts +++ b/packages/malloy/src/lang/ast/expressions/expr-count.ts @@ -41,6 +41,16 @@ export class ExprCount extends ExprAggregateFunction { return undefined; } + returns(ev: ExprValue): ExprValue { + return { + type: 'number', + numberType: 'integer', + evalSpace: ev.evalSpace, + expressionType: 'aggregate', + value: ev.value, + }; + } + getExpression(_fs: FieldSpace): ExprValue { const ret: AggregateExpr = { node: 'aggregate', @@ -51,7 +61,8 @@ export class ExprCount extends ExprAggregateFunction { ret.structPath = this.source.path; } return { - dataType: 'number', + type: 'number', + numberType: 'integer', expressionType: 'aggregate', value: ret, evalSpace: 'output', diff --git a/packages/malloy/src/lang/ast/expressions/expr-func.ts b/packages/malloy/src/lang/ast/expressions/expr-func.ts index 9ac41eff9..72dd33060 100644 --- a/packages/malloy/src/lang/ast/expressions/expr-func.ts +++ b/packages/malloy/src/lang/ast/expressions/expr-func.ts @@ -22,6 +22,7 @@ */ import { + CastType, EvalSpace, Expr, expressionIsAggregate, @@ -29,15 +30,16 @@ import { expressionIsScalar, expressionIsUngroupedAggregate, ExpressionType, - FieldValueType, FunctionCallNode, FunctionDef, FunctionOverloadDef, FunctionParameterDef, isAtomicFieldType, + isCastType, isExpressionTypeLEQ, maxOfExpressionTypes, mergeEvalSpaces, + TD, } from '../../../model/malloy_types'; import {errorFor} from '../ast-utils'; import {StructSpaceFieldBase} from '../field-space/struct-space-field-base'; @@ -50,6 +52,7 @@ import {computedExprValue, ExprValue} from '../types/expr-value'; import {ExpressionDef} from '../types/expression-def'; import {FieldName, FieldSpace} from '../types/field-space'; import {composeSQLExpr, SQLExprElement} from '../../../model/utils'; +import * as TDU from '../typedesc-utils'; export class ExprFunc extends ExpressionDef { elementType = 'function call()'; @@ -57,7 +60,7 @@ export class ExprFunc extends ExpressionDef { readonly name: string, readonly args: ExpressionDef[], readonly isRaw: boolean, - readonly rawType: FieldValueType | undefined, + readonly rawType: CastType | undefined, readonly source?: FieldReference ) { super({args: args}); @@ -76,7 +79,7 @@ export class ExprFunc extends ExpressionDef { return true; } - getExpression(fs: FieldSpace) { + getExpression(fs: FieldSpace): ExprValue { return this.getPropsExpression(fs); } @@ -124,13 +127,15 @@ export class ExprFunc extends ExpressionDef { ): ExprValue { const argExprsWithoutImplicit = this.args.map(arg => arg.getExpression(fs)); if (this.isRaw) { - let collectType: FieldValueType | undefined; + let collectType: CastType | undefined; const funcCall: SQLExprElement[] = [`${this.name}(`]; for (const expr of argExprsWithoutImplicit) { if (collectType) { funcCall.push(','); } else { - collectType = expr.dataType; + if (isCastType(expr.type)) { + collectType = expr.type; + } } funcCall.push(expr.value); } @@ -138,7 +143,7 @@ export class ExprFunc extends ExpressionDef { const dataType = this.rawType ?? collectType ?? 'number'; return computedExprValue({ - dataType, + dataType: {type: dataType}, value: composeSQLExpr(funcCall), from: argExprsWithoutImplicit, }); @@ -157,9 +162,9 @@ export class ExprFunc extends ExpressionDef { const sourceFoot = lookup.found; if (sourceFoot) { const footType = sourceFoot.typeDesc(); - if (isAtomicFieldType(footType.dataType)) { + if (isAtomicFieldType(footType.type)) { implicitExpr = { - dataType: footType.dataType, + ...TDU.atomicDef(footType), expressionType: footType.expressionType, value: {node: 'field', path: this.source.path}, evalSpace: footType.evalSpace, @@ -169,7 +174,7 @@ export class ExprFunc extends ExpressionDef { if (!(sourceFoot instanceof StructSpaceFieldBase)) { return this.loggedErrorExpr( 'invalid-aggregate-source', - `Aggregate source cannot be a ${footType.dataType}` + `Aggregate source cannot be a ${footType.type}` ); } } @@ -190,7 +195,7 @@ export class ExprFunc extends ExpressionDef { return this.loggedErrorExpr( 'no-matching-function-overload', `No matching overload for function ${this.name}(${argExprs - .map(e => e.dataType) + .map(e => e.type) .join(', ')})` ); } @@ -250,7 +255,7 @@ export class ExprFunc extends ExpressionDef { return this.loggedErrorExpr( 'non-aggregate-function-with-source', `Cannot call function ${this.name}(${argExprs - .map(e => e.dataType) + .map(e => e.type) .join(', ')}) with source` ); } @@ -405,10 +410,10 @@ export class ExprFunc extends ExpressionDef { funcCall = composeSQLExpr(expr); } } - if (type.dataType === 'any') { + if (type.type === 'any') { return this.loggedErrorExpr( 'function-returns-any', - `Invalid return type ${type.dataType} for function '${this.name}'` + `Invalid return type ${type.type} for function '${this.name}'` ); } const maxEvalSpace = mergeEvalSpaces(...argExprs.map(e => e.evalSpace)); @@ -426,7 +431,7 @@ export class ExprFunc extends ExpressionDef { // TODO consider if I can use `computedExprValue` here... // seems like the rules for the evalSpace is a bit different from normal though return { - dataType: type.dataType, + ...TDU.atomicDef(type), expressionType, value: funcCall, evalSpace, @@ -482,13 +487,13 @@ function findOverload( // Check whether types match (allowing for nullability errors, expression type errors, // eval space errors, and unknown types due to prior errors in args) const dataTypeMatch = - paramT.dataType === arg.dataType || - paramT.dataType === 'any' || + TD.eq(paramT, arg) || + paramT.type === 'any' || // TODO We should consider whether `nulls` should always be allowed. It probably // does not make sense to limit function calls to not allow nulls, since have // so little control over nullability. - arg.dataType === 'null' || - arg.dataType === 'error'; + arg.type === 'null' || + arg.type === 'error'; // Check expression type errors if (paramT.expressionType) { const expressionTypeMatch = isExpressionTypeLEQ( @@ -526,7 +531,7 @@ function findOverload( // Check nullability errors. For now we only require that literal arguments must be // non-null, but in the future we may allow parameters to say whether they can accept literal // nulls. - if (paramT.evalSpace === 'literal' && arg.dataType === 'null') { + if (paramT.evalSpace === 'literal' && arg.type === 'null') { nullabilityErrors.push({ argIndex, param, diff --git a/packages/malloy/src/lang/ast/expressions/expr-granular-time.ts b/packages/malloy/src/lang/ast/expressions/expr-granular-time.ts index 77b49584a..a53f6f52d 100644 --- a/packages/malloy/src/lang/ast/expressions/expr-granular-time.ts +++ b/packages/malloy/src/lang/ast/expressions/expr-granular-time.ts @@ -24,13 +24,13 @@ import { Expr, isDateUnit, - isTemporalField, mkTemporal, TimestampUnit, + TD, } from '../../../model/malloy_types'; import {errorFor} from '../ast-utils'; -import {FT} from '../fragtype-utils'; +import * as TDU from '../typedesc-utils'; import {timeOffset} from '../time-utils'; import {ExprValue} from '../types/expr-value'; import {ExpressionDef} from '../types/expression-def'; @@ -49,7 +49,7 @@ import {Range} from './range'; export class ExprGranularTime extends ExpressionDef { elementType = 'granularTime'; - legalChildTypes = [FT.timestampT, FT.dateT]; + legalChildTypes = [TDU.timestampT, TDU.dateT]; constructor( readonly expr: ExpressionDef, readonly units: TimestampUnit, @@ -65,36 +65,34 @@ export class ExprGranularTime extends ExpressionDef { getExpression(fs: FieldSpace): ExprValue { const timeframe = this.units; const exprVal = this.expr.getExpression(fs); - if (isTemporalField(exprVal.dataType)) { + if (TD.isTemporal(exprVal)) { const tsVal: GranularResult = { ...exprVal, - dataType: exprVal.dataType, timeframe: timeframe, }; if (this.truncate) { tsVal.value = { node: 'trunc', - e: mkTemporal(exprVal.value, exprVal.dataType), + e: mkTemporal(exprVal.value, exprVal.type), units: timeframe, }; } return tsVal; } - if (exprVal.dataType !== 'error') { + if (exprVal.type !== 'error') { this.logError( 'unsupported-type-for-time-truncation', - `Cannot do time truncation on type '${exprVal.dataType}'` + `Cannot do time truncation on type '${exprVal.type}'` ); } - const returnType = - exprVal.dataType === 'error' - ? isDateUnit(timeframe) - ? 'date' - : 'timestamp' - : exprVal.dataType; + const returnType = {...exprVal}; + if (exprVal.type === 'error') { + (returnType as ExprValue).type = isDateUnit(timeframe) + ? 'date' + : 'timestamp'; + } return { - ...exprVal, - dataType: returnType, + ...returnType, value: errorFor('granularity typecheck').value, evalSpace: 'constant', }; @@ -123,7 +121,7 @@ export class ExprGranularTime extends ExpressionDef { toRange(fs: FieldSpace): Range { const begin = this.getExpression(fs); const one: Expr = {node: 'numberLiteral', literal: '1'}; - if (begin.dataType === 'timestamp') { + if (begin.type === 'timestamp') { const beginTS = ExprTime.fromValue('timestamp', begin); const endTS = new ExprTime( 'timestamp', diff --git a/packages/malloy/src/lang/ast/expressions/expr-logical-op.ts b/packages/malloy/src/lang/ast/expressions/expr-logical-op.ts index 711991080..0359ce4ed 100644 --- a/packages/malloy/src/lang/ast/expressions/expr-logical-op.ts +++ b/packages/malloy/src/lang/ast/expressions/expr-logical-op.ts @@ -22,9 +22,9 @@ */ import {BinaryBoolean} from './binary-boolean'; -import {FT} from '../fragtype-utils'; +import * as TDU from '../typedesc-utils'; export class ExprLogicalOp extends BinaryBoolean<'and' | 'or'> { elementType = 'logical operator'; - legalChildTypes = [FT.boolT, FT.aggregateBoolT]; + legalChildTypes = [TDU.boolT, TDU.aggregateBoolT]; } diff --git a/packages/malloy/src/lang/ast/expressions/expr-max.ts b/packages/malloy/src/lang/ast/expressions/expr-max.ts index f952923a0..3256b1132 100644 --- a/packages/malloy/src/lang/ast/expressions/expr-max.ts +++ b/packages/malloy/src/lang/ast/expressions/expr-max.ts @@ -21,19 +21,17 @@ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import {ExprValue} from '../types/expr-value'; -import {FieldValueType} from '../../../model'; -import {FT} from '../fragtype-utils'; +import * as TDU from '../typedesc-utils'; import {ExprAggregateFunction} from './expr-aggregate-function'; import {ExpressionDef} from '../types/expression-def'; +import {ExprValue} from '../types/expr-value'; export class ExprMax extends ExprAggregateFunction { - legalChildTypes = [FT.numberT, FT.stringT, FT.dateT, FT.timestampT]; + legalChildTypes = [TDU.numberT, TDU.stringT, TDU.dateT, TDU.timestampT]; constructor(expr: ExpressionDef) { super('max', expr); } - - returns(forExpression: ExprValue): FieldValueType { - return forExpression.dataType; + returns(ev: ExprValue): ExprValue { + return ev; } } diff --git a/packages/malloy/src/lang/ast/expressions/expr-min.ts b/packages/malloy/src/lang/ast/expressions/expr-min.ts index 11ce772eb..36d23c0ca 100644 --- a/packages/malloy/src/lang/ast/expressions/expr-min.ts +++ b/packages/malloy/src/lang/ast/expressions/expr-min.ts @@ -21,19 +21,17 @@ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import {ExprValue} from '../types/expr-value'; -import {FieldValueType} from '../../../model'; -import {FT} from '../fragtype-utils'; +import * as TDU from '../typedesc-utils'; import {ExprAggregateFunction} from './expr-aggregate-function'; import {ExpressionDef} from '../types/expression-def'; +import {ExprValue} from '../types/expr-value'; export class ExprMin extends ExprAggregateFunction { - legalChildTypes = [FT.numberT, FT.stringT, FT.dateT, FT.timestampT]; + legalChildTypes = [TDU.numberT, TDU.stringT, TDU.dateT, TDU.timestampT]; constructor(expr: ExpressionDef) { super('min', expr); } - - returns(forExpression: ExprValue): FieldValueType { - return forExpression.dataType; + returns(ev: ExprValue): ExprValue { + return ev; } } diff --git a/packages/malloy/src/lang/ast/expressions/expr-minus.ts b/packages/malloy/src/lang/ast/expressions/expr-minus.ts index 98ef43008..14c14ddab 100644 --- a/packages/malloy/src/lang/ast/expressions/expr-minus.ts +++ b/packages/malloy/src/lang/ast/expressions/expr-minus.ts @@ -22,7 +22,7 @@ */ import {errorFor} from '../ast-utils'; -import {FT} from '../fragtype-utils'; +import * as TDU from '../typedesc-utils'; import {ExprValue} from '../types/expr-value'; import {ExpressionDef} from '../types/expression-def'; import {FieldSpace} from '../types/field-space'; @@ -32,15 +32,15 @@ export class ExprMinus extends ExpressionDef { constructor(readonly expr: ExpressionDef) { super({expr: expr}); - this.legalChildTypes = [FT.numberT]; + this.legalChildTypes = [TDU.numberT]; } getExpression(fs: FieldSpace): ExprValue { const expr = this.expr.getExpression(fs); - if (FT.typeIn(expr, this.legalChildTypes)) { + if (TDU.typeIn(expr, this.legalChildTypes)) { return { ...expr, - dataType: 'number', + type: 'number', value: {node: 'unary-', e: expr.value}, }; } diff --git a/packages/malloy/src/lang/ast/expressions/expr-not.ts b/packages/malloy/src/lang/ast/expressions/expr-not.ts index e23893bcb..d89270952 100644 --- a/packages/malloy/src/lang/ast/expressions/expr-not.ts +++ b/packages/malloy/src/lang/ast/expressions/expr-not.ts @@ -21,7 +21,7 @@ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import {FT} from '../fragtype-utils'; +import * as TDU from '../typedesc-utils'; import {ExprValue} from '../types/expr-value'; import {ExpressionDef} from '../types/expression-def'; import {FieldSpace} from '../types/field-space'; @@ -29,7 +29,7 @@ import {Unary} from './unary'; export class ExprNot extends Unary { elementType = 'not'; - legalChildTypes = [FT.boolT, FT.nullT]; + legalChildTypes = [TDU.boolT, TDU.nullT]; constructor(expr: ExpressionDef) { super(expr); } @@ -39,7 +39,7 @@ export class ExprNot extends Unary { const doNot = this.typeCheck(this.expr, notThis); return { ...notThis, - dataType: 'boolean', + type: 'boolean', value: {node: 'not', e: doNot ? notThis.value : {node: 'false'}}, }; } diff --git a/packages/malloy/src/lang/ast/expressions/expr-now.ts b/packages/malloy/src/lang/ast/expressions/expr-now.ts index 72c83b107..0ee95f2e5 100644 --- a/packages/malloy/src/lang/ast/expressions/expr-now.ts +++ b/packages/malloy/src/lang/ast/expressions/expr-now.ts @@ -30,7 +30,7 @@ export class ExprNow extends ExpressionDef { getExpression(_fs: FieldSpace): ExprValue { return { - dataType: 'timestamp', + type: 'timestamp', expressionType: 'scalar', // `now` is considered to be a constant, at least in the dialects we support today evalSpace: 'constant', diff --git a/packages/malloy/src/lang/ast/expressions/expr-null.ts b/packages/malloy/src/lang/ast/expressions/expr-null.ts index dc1e0fd4e..f774bc7cb 100644 --- a/packages/malloy/src/lang/ast/expressions/expr-null.ts +++ b/packages/malloy/src/lang/ast/expressions/expr-null.ts @@ -28,7 +28,7 @@ export class ExprNULL extends ExpressionDef { elementType = 'NULL'; getExpression(): ExprValue { return literalExprValue({ - dataType: 'null', + dataType: {type: 'null'}, value: {node: 'null'}, }); } diff --git a/packages/malloy/src/lang/ast/expressions/expr-number.ts b/packages/malloy/src/lang/ast/expressions/expr-number.ts index 65a9e923d..e96567587 100644 --- a/packages/malloy/src/lang/ast/expressions/expr-number.ts +++ b/packages/malloy/src/lang/ast/expressions/expr-number.ts @@ -24,6 +24,7 @@ import {ExprValue, literalExprValue} from '../types/expr-value'; import {FieldSpace} from '../types/field-space'; import {ExpressionDef} from '../types/expression-def'; +import {NumberTypeDef} from '../../../model'; export class ExprNumber extends ExpressionDef { elementType = 'numeric literal'; @@ -36,8 +37,12 @@ export class ExprNumber extends ExpressionDef { } constantExpression(): ExprValue { + const n = Number(this.n); + const dataType: NumberTypeDef = Number.isNaN(n) + ? {type: 'number'} + : {type: 'number', numberType: Number.isInteger(n) ? 'integer' : 'float'}; return literalExprValue({ - dataType: 'number', + dataType, value: {node: 'numberLiteral', literal: this.n}, }); } diff --git a/packages/malloy/src/lang/ast/expressions/expr-props.ts b/packages/malloy/src/lang/ast/expressions/expr-props.ts index 4832cf04d..a78c711be 100644 --- a/packages/malloy/src/lang/ast/expressions/expr-props.ts +++ b/packages/malloy/src/lang/ast/expressions/expr-props.ts @@ -26,7 +26,7 @@ import { expressionIsCalculation, } from '../../../model/malloy_types'; import {errorFor} from '../ast-utils'; -import {FT} from '../fragtype-utils'; +import * as TDU from '../typedesc-utils'; import {FunctionOrdering} from './function-ordering'; import {Filter} from '../query-properties/filters'; import {Limit} from '../query-properties/limit'; @@ -39,7 +39,7 @@ import {ExprFunc} from './expr-func'; export class ExprProps extends ExpressionDef { elementType = 'expression with props'; - legalChildTypes = FT.anyAtomicT; + legalChildTypes = TDU.anyAtomicT; constructor( readonly expr: ExpressionDef, readonly statements: FieldPropStatement[] diff --git a/packages/malloy/src/lang/ast/expressions/expr-regex.ts b/packages/malloy/src/lang/ast/expressions/expr-regex.ts index 8e851f014..6c50a6a7f 100644 --- a/packages/malloy/src/lang/ast/expressions/expr-regex.ts +++ b/packages/malloy/src/lang/ast/expressions/expr-regex.ts @@ -32,7 +32,7 @@ export class ExprRegEx extends ExpressionDef { getExpression(): ExprValue { return literalExprValue({ - dataType: 'regular expression', + dataType: {type: 'regular expression'}, value: {node: 'regexpLiteral', literal: this.regex}, }); } diff --git a/packages/malloy/src/lang/ast/expressions/expr-string.ts b/packages/malloy/src/lang/ast/expressions/expr-string.ts index 6f05f1d3d..b5d9261de 100644 --- a/packages/malloy/src/lang/ast/expressions/expr-string.ts +++ b/packages/malloy/src/lang/ast/expressions/expr-string.ts @@ -35,7 +35,7 @@ export class ExprString extends ExpressionDef { getExpression(_fs: FieldSpace): ExprValue { return literalExprValue({ - dataType: 'string', + dataType: {type: 'string'}, value: {node: 'stringLiteral', literal: this.value}, }); } diff --git a/packages/malloy/src/lang/ast/expressions/expr-time-extract.ts b/packages/malloy/src/lang/ast/expressions/expr-time-extract.ts index 9a0313273..0ad933293 100644 --- a/packages/malloy/src/lang/ast/expressions/expr-time-extract.ts +++ b/packages/malloy/src/lang/ast/expressions/expr-time-extract.ts @@ -27,6 +27,7 @@ import { isTemporalField, isTimestampUnit, mkTemporal, + TD, } from '../../../model/malloy_types'; import { @@ -81,30 +82,30 @@ export class ExprTimeExtract extends ExpressionDef { if (from instanceof Range) { let first = from.first.getExpression(fs); let last = from.last.getExpression(fs); - if (first.dataType === 'error' || last.dataType === 'error') { + if (first.type === 'error' || last.type === 'error') { return computedErrorExprValue({ - dataType: 'number', + dataType: {type: 'number'}, error: 'extract from error', from: [first, last], }); } - if (!isTemporalField(first.dataType)) { + if (!isTemporalField(first.type)) { return from.first.loggedErrorExpr( 'invalid-type-for-time-extraction', - `Can't extract ${extractTo} from '${first.dataType}'` + `Can't extract ${extractTo} from '${first.type}'` ); } - if (!isTemporalField(last.dataType)) { + if (!isTemporalField(last.type)) { return from.last.loggedErrorExpr( 'invalid-type-for-time-extraction', - `Cannot extract ${extractTo} from '${last.dataType}'` + `Cannot extract ${extractTo} from '${last.type}'` ); } - let valueType = first.dataType; - if (first.dataType !== last.dataType) { + let valueType = first.type; + if (!TD.eq(first, last)) { let cannotMeasure = true; valueType = 'timestamp'; - if (first.dataType === 'date') { + if (first.type === 'date') { const newFirst = getMorphicValue(first, 'timestamp'); if (newFirst) { first = newFirst; @@ -120,7 +121,7 @@ export class ExprTimeExtract extends ExpressionDef { if (cannotMeasure) { return from.first.loggedErrorExpr( 'invalid-types-for-time-measurement', - `Cannot measure from ${first.dataType} to ${last.dataType}` + `Cannot measure from ${first.type} to ${last.type}` ); } } @@ -137,7 +138,7 @@ export class ExprTimeExtract extends ExpressionDef { ); } return computedExprValue({ - dataType: 'number', + dataType: {type: 'number', numberType: 'integer'}, value: { node: 'timeDiff', units: extractTo, @@ -150,26 +151,26 @@ export class ExprTimeExtract extends ExpressionDef { }); } else { const argV = from.getExpression(fs); - if (isTemporalField(argV.dataType)) { + if (isTemporalField(argV.type)) { return computedExprValue({ - dataType: 'number', + dataType: {type: 'number', numberType: 'integer'}, value: { node: 'extract', - e: mkTemporal(argV.value, argV.dataType), + e: mkTemporal(argV.value, argV.type), units: extractTo, }, from: [argV], }); } - if (argV.dataType !== 'error') { + if (argV.type !== 'error') { this.logError( 'unsupported-type-for-time-extraction', - `${this.extractText}() requires time type, not '${argV.dataType}'` + `${this.extractText}() requires time type, not '${argV.type}'` ); } return computedErrorExprValue({ - dataType: 'number', - error: `${this.extractText} bad type ${argV.dataType}`, + dataType: {type: 'number', numberType: 'integer'}, + error: `${this.extractText} bad type ${argV.type}`, from: [argV], }); } diff --git a/packages/malloy/src/lang/ast/expressions/expr-time.ts b/packages/malloy/src/lang/ast/expressions/expr-time.ts index c3ddb88c6..a965bcc2a 100644 --- a/packages/malloy/src/lang/ast/expressions/expr-time.ts +++ b/packages/malloy/src/lang/ast/expressions/expr-time.ts @@ -39,7 +39,7 @@ export class ExprTime extends ExpressionDef { super(); this.elementType = timeType; this.translationValue = computedExprValue({ - dataType: timeType, + dataType: {type: timeType}, value, from: from ?? [], }); @@ -51,15 +51,15 @@ export class ExprTime extends ExpressionDef { static fromValue(timeType: TemporalFieldType, expr: ExprValue): ExprTime { let value = expr.value; - if (timeType !== expr.dataType) { + if (timeType !== expr.type) { const toTs: TypecastExpr = { node: 'cast', safe: false, dstType: {type: timeType}, e: expr.value, }; - if (isTemporalField(expr.dataType)) { - toTs.srcType = {type: expr.dataType}; + if (isTemporalField(expr.type)) { + toTs.srcType = {type: expr.type}; } value = toTs; } diff --git a/packages/malloy/src/lang/ast/expressions/expr-ungroup.ts b/packages/malloy/src/lang/ast/expressions/expr-ungroup.ts index f6acf0265..8780faacf 100644 --- a/packages/malloy/src/lang/ast/expressions/expr-ungroup.ts +++ b/packages/malloy/src/lang/ast/expressions/expr-ungroup.ts @@ -24,18 +24,17 @@ import { expressionIsAggregate, expressionIsUngroupedAggregate, - FieldValueType, UngroupNode, } from '../../../model/malloy_types'; import {QuerySpace} from '../field-space/query-spaces'; -import {FT} from '../fragtype-utils'; +import * as TDU from '../typedesc-utils'; import {ExprValue} from '../types/expr-value'; import {ExpressionDef} from '../types/expression-def'; import {FieldName, FieldSpace} from '../types/field-space'; export class ExprUngroup extends ExpressionDef { - legalChildTypes = FT.anyAtomicT; + legalChildTypes = TDU.anyAtomicT; elementType = 'ungroup'; constructor( readonly control: 'all' | 'exclude', @@ -45,10 +44,6 @@ export class ExprUngroup extends ExpressionDef { super({expr: expr, fields: fields}); } - returns(_forExpression: ExprValue): FieldValueType { - return 'number'; - } - getExpression(fs: FieldSpace): ExprValue { const exprVal = this.expr.getExpression(fs); if (!expressionIsAggregate(exprVal.expressionType)) { @@ -100,7 +95,7 @@ export class ExprUngroup extends ExpressionDef { ungroup.fields = dstFields; } return { - dataType: this.returns(exprVal), + ...TDU.atomicDef(exprVal), expressionType: 'ungrouped_aggregate', value: ungroup, evalSpace: 'output', diff --git a/packages/malloy/src/lang/ast/expressions/for-range.ts b/packages/malloy/src/lang/ast/expressions/for-range.ts index 3924d6293..87859bef0 100644 --- a/packages/malloy/src/lang/ast/expressions/for-range.ts +++ b/packages/malloy/src/lang/ast/expressions/for-range.ts @@ -22,7 +22,7 @@ */ import {errorFor} from '../ast-utils'; -import {FT} from '../fragtype-utils'; +import * as TDU from '../typedesc-utils'; import {castTo, resolution, timeOffset} from '../time-utils'; import {BinaryMalloyOperator} from '../types/binary_operators'; import {ExprValue, computedErrorExprValue} from '../types/expr-value'; @@ -34,7 +34,7 @@ import {Timeframe} from './time-frame'; export class ForRange extends ExpressionDef { elementType = 'forRange'; - legalChildTypes = [FT.timestampT, FT.dateT]; + legalChildTypes = [TDU.timestampT, TDU.dateT]; constructor( readonly from: ExpressionDef, readonly duration: ExpressionDef, @@ -54,15 +54,15 @@ export class ForRange extends ExpressionDef { return errorFor('no time for range'); } const nV = this.duration.getExpression(fs); - if (nV.dataType !== 'number') { - if (nV.dataType !== 'error') { + if (nV.type !== 'number') { + if (nV.type !== 'error') { this.logError( 'invalid-duration-quantity', - `FOR duration count must be a number, not '${nV.dataType}'` + `FOR duration count must be a number, not '${nV.type}'` ); } return computedErrorExprValue({ - dataType: 'boolean', + dataType: {type: 'boolean'}, error: 'for not number', from: [startV, checkV], }); @@ -75,12 +75,12 @@ export class ForRange extends ExpressionDef { // Next, if the beginning of the range is a timestamp, then we // also have to do the computation as a timestamp - if (startV.dataType === 'timestamp') { + if (startV.type === 'timestamp') { rangeType = 'timestamp'; } // everything is dates, do date math - if (checkV.dataType === 'date' && rangeType === 'date') { + if (checkV.type === 'date' && rangeType === 'date') { const rangeStart = this.from; const rangeEndV = timeOffset('date', startV.value, '+', nV.value, units); const rangeEnd = new ExprTime('date', rangeEndV); @@ -93,7 +93,7 @@ export class ForRange extends ExpressionDef { let rangeStart = this.from; let from = startV.value; - if (startV.dataType === 'date') { + if (startV.type === 'date') { const tsVersion = startV.morphic && startV.morphic['timestamp']; if (tsVersion) { from = tsVersion; diff --git a/packages/malloy/src/lang/ast/expressions/pick-when.ts b/packages/malloy/src/lang/ast/expressions/pick-when.ts index efb5ae749..d96c34428 100644 --- a/packages/malloy/src/lang/ast/expressions/pick-when.ts +++ b/packages/malloy/src/lang/ast/expressions/pick-when.ts @@ -23,7 +23,7 @@ import {CaseExpr} from '../../../model/malloy_types'; -import {FT} from '../fragtype-utils'; +import * as TDU from '../typedesc-utils'; import {ExprValue, computedExprValue} from '../types/expr-value'; import {ExpressionDef} from '../types/expression-def'; import {FieldSpace} from '../types/field-space'; @@ -35,9 +35,7 @@ interface Choice { } function typeCoalesce(ev1: ExprValue | undefined, ev2: ExprValue): ExprValue { - return ev1 === undefined || - ev1.dataType === 'null' || - ev1.dataType === 'error' + return ev1 === undefined || ev1.type === 'null' || ev1.type === 'error' ? ev2 : ev1; } @@ -63,7 +61,7 @@ export class Pick extends ExpressionDef { return undefined; } const whenResp = c.when.requestExpression(fs); - if (whenResp === undefined || whenResp.dataType !== 'boolean') { + if (whenResp === undefined || whenResp.type !== 'boolean') { // If when is not a boolean, we'll treat it like a partial compare return undefined; } @@ -87,10 +85,10 @@ export class Pick extends ExpressionDef { ? choice.pick.getExpression(fs) : expr.getExpression(fs); dependents.push(whenExpr, thenExpr); - if (returnType && !FT.typeEq(returnType, thenExpr, true)) { + if (returnType && !TDU.typeEq(returnType, thenExpr, true)) { return this.loggedErrorExpr('pick-type-does-not-match', { - pickType: thenExpr.dataType, - returnType: returnType.dataType, + pickType: thenExpr.type, + returnType: returnType.type, }); } returnType = typeCoalesce(returnType, thenExpr); @@ -102,23 +100,23 @@ export class Pick extends ExpressionDef { dependents.push(exprVal); if (elseVal) dependents.push(elseVal); const defaultVal = elseVal ?? exprVal; - returnType = typeCoalesce(returnType, defaultVal); - if (!FT.typeEq(returnType, defaultVal, true)) { + const definedReturnType = typeCoalesce(returnType, defaultVal); + if (!TDU.typeEq(definedReturnType, defaultVal, true)) { if (this.elsePick) { return this.loggedErrorExpr('pick-else-type-does-not-match', { - elseType: defaultVal.dataType, - returnType: returnType.dataType, + elseType: defaultVal.type, + returnType: definedReturnType.type, }); } else { return this.loggedErrorExpr('pick-default-type-does-not-match', { - defaultType: defaultVal.dataType, - returnType: returnType.dataType, + defaultType: defaultVal.type, + returnType: definedReturnType.type, }); } } caseValue.kids.caseElse = defaultVal.value; return computedExprValue({ - dataType: returnType.dataType, + dataType: definedReturnType, value: caseValue, from: dependents, }); @@ -162,15 +160,15 @@ export class Pick extends ExpressionDef { } let returnType: ExprValue | undefined; for (const aChoice of choiceValues) { - if (!FT.typeEq(aChoice.when, FT.boolT)) { + if (!TDU.typeEq(aChoice.when, TDU.boolT)) { return this.loggedErrorExpr('pick-when-must-be-boolean', { - whenType: aChoice.when.dataType, + whenType: aChoice.when.type, }); } - if (returnType && !FT.typeEq(returnType, aChoice.pick, true)) { + if (returnType && !TDU.typeEq(returnType, aChoice.pick, true)) { return this.loggedErrorExpr('pick-type-does-not-match', { - pickType: aChoice.pick.dataType, - returnType: returnType.dataType, + pickType: aChoice.pick.type, + returnType: returnType.type, }); } returnType = typeCoalesce(returnType, aChoice.pick); @@ -179,16 +177,16 @@ export class Pick extends ExpressionDef { } const defVal = this.elsePick.getExpression(fs); dependents.push(defVal); - returnType = typeCoalesce(returnType, defVal); - if (!FT.typeEq(returnType, defVal, true)) { + const definedReturnType = typeCoalesce(returnType, defVal); + if (!TDU.typeEq(definedReturnType, defVal, true)) { return this.elsePick.loggedErrorExpr('pick-else-type-does-not-match', { - elseType: defVal.dataType, - returnType: returnType.dataType, + elseType: defVal.type, + returnType: definedReturnType.type, }); } pick.kids.caseElse = defVal.value; return computedExprValue({ - dataType: returnType.dataType, + dataType: definedReturnType, value: pick, from: dependents, }); diff --git a/packages/malloy/src/lang/ast/expressions/range.ts b/packages/malloy/src/lang/ast/expressions/range.ts index 4a3605b1f..1e441b2f9 100644 --- a/packages/malloy/src/lang/ast/expressions/range.ts +++ b/packages/malloy/src/lang/ast/expressions/range.ts @@ -50,7 +50,7 @@ export class Range extends ExpressionDef { const fromValue = this.first.apply(fs, op1, expr); const toValue = this.last.apply(fs, op3, expr); return computedExprValue({ - dataType: 'boolean', + dataType: {type: 'boolean'}, value: { node: op2, kids: {left: fromValue.value, right: toValue.value}, diff --git a/packages/malloy/src/lang/ast/expressions/time-literal.ts b/packages/malloy/src/lang/ast/expressions/time-literal.ts index 9e74c3a59..d8711ceaa 100644 --- a/packages/malloy/src/lang/ast/expressions/time-literal.ts +++ b/packages/malloy/src/lang/ast/expressions/time-literal.ts @@ -105,7 +105,11 @@ abstract class TimeLiteral extends ExpressionDef { protected makeValue(val: string, dataType: TemporalFieldType): TimeResult { const value = this.makeLiteral(val, dataType); - return literalTimeResult({value, dataType, timeframe: this.units}); + return literalTimeResult({ + value, + dataType: {type: dataType}, + timeframe: this.units, + }); } getExpression(_fs: FieldSpace): ExprValue { @@ -190,7 +194,7 @@ class GranularLiteral extends TimeLiteral { if (rangeEnd) { const testValue = left.getExpression(fs); - if (testValue.dataType === 'timestamp') { + if (testValue.type === 'timestamp') { const newStart = getMorphicValue(rangeStart, 'timestamp'); const newEnd = getMorphicValue(rangeEnd, 'timestamp'); if (newStart && newEnd) { @@ -201,8 +205,9 @@ class GranularLiteral extends TimeLiteral { } } - if (isTemporalField(testValue.dataType)) { - const rangeType = testValue.dataType; + // Compiler is unsure about rangeEnd = newEnd for some reason + if (rangeEnd && isTemporalField(testValue.type)) { + const rangeType = testValue.type; const range = new Range( new ExprTime(rangeType, rangeStart.value), new ExprTime(rangeType, rangeEnd.value) diff --git a/packages/malloy/src/lang/ast/field-space/index-field-space.ts b/packages/malloy/src/lang/ast/field-space/index-field-space.ts index ba7cb3d5c..e5c4d5b12 100644 --- a/packages/malloy/src/lang/ast/field-space/index-field-space.ts +++ b/packages/malloy/src/lang/ast/field-space/index-field-space.ts @@ -25,8 +25,8 @@ import { IndexSegment, PipeSegment, IndexFieldDef, - isAtomicFieldType, expressionIsScalar, + TD, } from '../../../model/malloy_types'; import { FieldReference, @@ -146,12 +146,9 @@ export class IndexFieldSpace extends QueryOperationSpace { ); } else { const eTypeDesc = entry.typeDesc(); - const eType = eTypeDesc.dataType; // Don't index arrays and records - const canIndex = - isAtomicFieldType(eType) && eType !== 'record' && eType !== 'array'; if ( - canIndex && + TD.isLeafAtomic(eTypeDesc) && expressionIsScalar(eTypeDesc.expressionType) && (dialect === undefined || !dialect.ignoreInProject(name)) ) { diff --git a/packages/malloy/src/lang/ast/field-space/project-field-space.ts b/packages/malloy/src/lang/ast/field-space/project-field-space.ts index 8f02a174d..8859b3920 100644 --- a/packages/malloy/src/lang/ast/field-space/project-field-space.ts +++ b/packages/malloy/src/lang/ast/field-space/project-field-space.ts @@ -26,6 +26,7 @@ import { expressionInvolvesAggregate, expressionIsAnalytic, TypeDesc, + TD, } from '../../../model/malloy_types'; import {QuerySpace} from './query-spaces'; @@ -33,9 +34,10 @@ import {QuerySpace} from './query-spaces'; export class ProjectFieldSpace extends QuerySpace { readonly segmentType = 'project'; - canContain(typeDesc: TypeDesc): boolean { + canContain(typeDesc: TypeDesc | undefined): boolean { if ( - typeDesc.dataType === 'turtle' || + typeDesc === undefined || + !TD.isLeafAtomic(typeDesc) || expressionIsAggregate(typeDesc.expressionType) ) { // We don't need to log here, because an error should have already been logged. diff --git a/packages/malloy/src/lang/ast/field-space/query-spaces.ts b/packages/malloy/src/lang/ast/field-space/query-spaces.ts index 50ab275ee..928d6f410 100644 --- a/packages/malloy/src/lang/ast/field-space/query-spaces.ts +++ b/packages/malloy/src/lang/ast/field-space/query-spaces.ts @@ -143,7 +143,7 @@ export abstract class QueryOperationSpace } else { const eType = entry.typeDesc(); if ( - model.isAtomicFieldType(eType.dataType) && + model.TD.isAtomic(eType) && model.expressionIsScalar(eType.expressionType) && (dialect === undefined || !dialect.ignoreInProject(name)) ) { @@ -194,7 +194,7 @@ export abstract class QuerySpace extends QueryOperationSpace { } } - canContain(_typeDesc: model.TypeDesc): boolean { + canContain(_typeDescResult: model.TypeDesc | undefined) { return true; } @@ -215,7 +215,8 @@ export abstract class QuerySpace extends QueryOperationSpace { // TODO Figure out how to make errors generated by `canContain` go in the right place, // maybe by adding a logable element to SpaceFields. if ( - typeDesc.dataType !== 'error' && + typeDesc && + typeDesc.type !== 'error' && this.canContain(typeDesc) && !isEmptyNest(fieldQueryDef) ) { diff --git a/packages/malloy/src/lang/ast/field-space/reference-field.ts b/packages/malloy/src/lang/ast/field-space/reference-field.ts index 47c0a5cd5..d9b9ffe35 100644 --- a/packages/malloy/src/lang/ast/field-space/reference-field.ts +++ b/packages/malloy/src/lang/ast/field-space/reference-field.ts @@ -23,11 +23,11 @@ import { Annotation, - CastType, QueryFieldDef, + TD, TypeDesc, } from '../../../model/malloy_types'; - +import * as TDU from '../typedesc-utils'; import {FieldReference} from '../query-items/field-references'; import {FieldSpace} from '../types/field-space'; import {SpaceEntry} from '../types/space-entry'; @@ -63,17 +63,22 @@ export class ReferenceField extends SpaceField { // TODO investigate removing 'fieldref' as a type, as it obscures the // actual type of the field and is redundant with the slightly // more verbose `{ e: [{ type: 'field', path }] }` - const path = this.fieldRef.list.map(f => f.name); - const queryFieldDef: QueryFieldDef = - check.found?.refType === 'parameter' - ? { - type: check.found.typeDesc().dataType as CastType, - name: path[0], - e: {node: 'parameter', path}, - } - : {type: 'fieldref', path}; - this.queryFieldDef = queryFieldDef; - + const path = this.fieldRef.path; + if (check.found && check.found.refType === 'parameter') { + const foundType = check.found.typeDesc(); + if (TD.isAtomic(foundType)) { + this.queryFieldDef = { + ...TDU.atomicDef(foundType), + name: path[0], + e: {node: 'parameter', path}, + }; + } else { + // mtoy todo + throw new Error('impossible turtle/join parameter'); + } + } else { + this.queryFieldDef = {type: 'fieldref', path}; + } const refTo = this.referenceTo; if (refTo instanceof SpaceField) { const origFd = refTo.constructorFieldDef(); @@ -99,6 +104,6 @@ export class ReferenceField extends SpaceField { this.memoTypeDesc = refTo.typeDesc(); return this.memoTypeDesc; } - return {dataType: 'error', expressionType: 'scalar', evalSpace: 'input'}; + return TDU.errorT; } } diff --git a/packages/malloy/src/lang/ast/field-space/rename-space-field.ts b/packages/malloy/src/lang/ast/field-space/rename-space-field.ts index eb13ad9dc..87b0e8803 100644 --- a/packages/malloy/src/lang/ast/field-space/rename-space-field.ts +++ b/packages/malloy/src/lang/ast/field-space/rename-space-field.ts @@ -21,11 +21,7 @@ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { - DocumentLocation, - FieldDef, - TypeDesc, -} from '../../../model/malloy_types'; +import {DocumentLocation, FieldDef} from '../../../model/malloy_types'; import {SpaceField} from '../types/space-field'; @@ -50,7 +46,7 @@ export class RenameSpaceField extends SpaceField { }; } - typeDesc(): TypeDesc { + typeDesc() { return this.otherField.typeDesc(); } } diff --git a/packages/malloy/src/lang/ast/field-space/static-space.ts b/packages/malloy/src/lang/ast/field-space/static-space.ts index 134679435..03958f767 100644 --- a/packages/malloy/src/lang/ast/field-space/static-space.ts +++ b/packages/malloy/src/lang/ast/field-space/static-space.ts @@ -202,7 +202,7 @@ export class StructSpaceField extends StructSpaceFieldBase { } get fieldSpace(): FieldSpace { - return new StaticSpace(this.sourceDef); + return new StaticSpace(this.structDef); } } diff --git a/packages/malloy/src/lang/ast/field-space/struct-space-field-base.ts b/packages/malloy/src/lang/ast/field-space/struct-space-field-base.ts index 5804b90ad..f426b28ee 100644 --- a/packages/malloy/src/lang/ast/field-space/struct-space-field-base.ts +++ b/packages/malloy/src/lang/ast/field-space/struct-space-field-base.ts @@ -21,35 +21,43 @@ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import {JoinFieldDef, TypeDesc} from '../../../model/malloy_types'; +import {isSourceDef, JoinFieldDef, TypeDesc} from '../../../model/malloy_types'; +import * as TDU from '../typedesc-utils'; import {FieldSpace} from '../types/field-space'; import {JoinPathElement} from '../types/lookup-result'; import {SpaceField} from '../types/space-field'; export abstract class StructSpaceFieldBase extends SpaceField { - constructor(protected sourceDef: JoinFieldDef) { + constructor(protected structDef: JoinFieldDef) { super(); } abstract get fieldSpace(): FieldSpace; fieldDef(): JoinFieldDef { - return this.sourceDef; + return this.structDef; } get joinPathElement(): JoinPathElement { return { - name: this.sourceDef.as || this.sourceDef.name, - joinType: this.sourceDef.join, - joinElementType: this.sourceDef.type, + name: this.structDef.as || this.structDef.name, + joinType: this.structDef.join, + joinElementType: this.structDef.type, }; } typeDesc(): TypeDesc { + if (isSourceDef(this.structDef)) { + return { + type: this.structDef.type, + evalSpace: 'input', + expressionType: 'scalar', + }; + } return { - dataType: this.sourceDef.type, - expressionType: 'scalar', + ...TDU.atomicDef(this.structDef), evalSpace: 'input', + expressionType: 'scalar', }; } } diff --git a/packages/malloy/src/lang/ast/field-space/view-field.ts b/packages/malloy/src/lang/ast/field-space/view-field.ts index dc53aa43e..6390c8815 100644 --- a/packages/malloy/src/lang/ast/field-space/view-field.ts +++ b/packages/malloy/src/lang/ast/field-space/view-field.ts @@ -21,7 +21,8 @@ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import {FieldDef, QueryFieldDef, TypeDesc} from '../../../model/malloy_types'; +import {FieldDef, QueryFieldDef} from '../../../model/malloy_types'; +import * as TDU from '../typedesc-utils'; import {FieldSpace} from '../types/field-space'; import {SpaceField} from '../types/space-field'; @@ -34,7 +35,7 @@ export abstract class ViewField extends SpaceField { abstract getQueryFieldDef(fs: FieldSpace): QueryFieldDef | undefined; abstract fieldDef(): FieldDef; - typeDesc(): TypeDesc { - return {dataType: 'turtle', expressionType: 'scalar', evalSpace: 'input'}; + typeDesc() { + return TDU.viewT; } } diff --git a/packages/malloy/src/lang/ast/fragtype-utils.ts b/packages/malloy/src/lang/ast/fragtype-utils.ts deleted file mode 100644 index 71a24a743..000000000 --- a/packages/malloy/src/lang/ast/fragtype-utils.ts +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright 2023 Google LLC - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files - * (the "Software"), to deal in the Software without restriction, - * including without limitation the rights to use, copy, modify, merge, - * publish, distribute, sublicense, and/or sell copies of the Software, - * and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -import { - EvalSpace, - expressionIsScalar, - ExpressionType, - FieldValueType, - TypeDesc, -} from '../../model'; - -function mkFragType( - dataType: FieldValueType, - expressionType: ExpressionType = 'scalar', - evalSpace: EvalSpace = 'constant' -): TypeDesc { - return {dataType, expressionType, evalSpace}; -} - -/** - * Collects functions which operate on fragtype compatible objects - */ -export class FT { - /** - * Checks if a given type is in a list - * @param check The type to check (can be undefined) - * @param from List of types which are OK - */ - static in(check: TypeDesc | undefined, from: TypeDesc[]): boolean { - if (check) { - const found = from.find(okType => FT.eq(okType, check)); - return found !== undefined; - } - return false; - } - - /** - * Checks if a possibly undefined candidate matches a type - * @param good The real type - * @param checkThis The possibly undefined candidate - */ - static eq(good: TypeDesc, checkThis: TypeDesc | undefined): boolean { - return ( - checkThis !== undefined && - good.dataType === checkThis.dataType && - good.expressionType === checkThis.expressionType - ); - } - - /** - * Checks if a given type is in a list, ignoring aggregate - * @param check The type to check (can be undefined) - * @param from List of types which are OK - */ - static typeIn(check: TypeDesc | undefined, from: TypeDesc[]): boolean { - if (check) { - const found = from.find(okType => FT.typeEq(okType, check)); - return found !== undefined; - } - return false; - } - - /** - * Checks if the base types, ignoring aggregate, are equal - * @param left Left type - * @param right Right type - * @param nullOk True if a NULL is an acceptable match - */ - static typeEq( - left: TypeDesc, - right: TypeDesc, - nullOk = false, - errorOk = true - ): boolean { - const maybeEq = left.dataType === right.dataType; - const nullEq = - nullOk && (left.dataType === 'null' || right.dataType === 'null'); - const errorEq = - errorOk && (left.dataType === 'error' || right.dataType === 'error'); - return maybeEq || nullEq || errorEq; - } - - /** - * - * For error messages, returns a comma seperated list of readable names - * for a list of types. - * @param types List of type or objects with types - */ - static inspect(...types: (TypeDesc | undefined)[]): string { - const strings = types.map(type => { - if (type) { - let inspected: string = type.dataType; - if (!expressionIsScalar(type.expressionType)) { - inspected = `${type.expressionType} ${inspected}`; - } - return inspected; - } - return 'undefined'; - }); - return strings.join(','); - } - - static nullT = mkFragType('null'); - static numberT = mkFragType('number'); - static stringT = mkFragType('string'); - static dateT = mkFragType('date'); - static timestampT = mkFragType('timestamp'); - static boolT = mkFragType('boolean'); - static anyAtomicT = [ - FT.numberT, - FT.stringT, - FT.dateT, - FT.timestampT, - FT.boolT, - ]; - static aggregateBoolT = mkFragType('boolean', 'aggregate'); -} diff --git a/packages/malloy/src/lang/ast/parameters/has-parameter.ts b/packages/malloy/src/lang/ast/parameters/has-parameter.ts index fa1196b63..48ca39e20 100644 --- a/packages/malloy/src/lang/ast/parameters/has-parameter.ts +++ b/packages/malloy/src/lang/ast/parameters/has-parameter.ts @@ -28,7 +28,7 @@ import {MalloyElement} from '../types/malloy-element'; interface HasInit { name: string; - type?: string; + type?: CastType; default?: ConstantExpression; } @@ -55,21 +55,21 @@ export class HasParameter extends MalloyElement { const constant = this.default.constantValue(); if ( this.type && - this.type !== constant.dataType && - constant.dataType !== 'null' && - constant.dataType !== 'error' + this.type !== constant.type && + constant.type !== 'null' && + constant.type !== 'error' ) { this.default.logError( 'parameter-default-does-not-match-declared-type', `Default value for parameter does not match declared type \`${this.type}\`` ); } - if (constant.dataType === 'null') { + if (constant.type === 'null') { if (this.type) { return { + type: this.type, value: constant.value, name: this.name, - type: this.type, }; } else { this.default.logError( @@ -83,10 +83,10 @@ export class HasParameter extends MalloyElement { }; } } - if (!isCastType(constant.dataType) && constant.dataType !== 'error') { + if (!isCastType(constant.type) && constant.type !== 'error') { this.default.logError( 'parameter-illegal-default-type', - `Default value cannot have type \`${constant.dataType}\`` + `Default value cannot have type \`${constant.type}\`` ); return { value: constant.value, @@ -97,7 +97,7 @@ export class HasParameter extends MalloyElement { return { value: constant.value, name: this.name, - type: constant.dataType, + type: constant.type, }; } if (this.type === undefined) { diff --git a/packages/malloy/src/lang/ast/query-items/field-declaration.ts b/packages/malloy/src/lang/ast/query-items/field-declaration.ts index 3f9850bcf..517c0ae4b 100644 --- a/packages/malloy/src/lang/ast/query-items/field-declaration.ts +++ b/packages/malloy/src/lang/ast/query-items/field-declaration.ts @@ -31,9 +31,10 @@ import { AtomicFieldDef, TemporalTypeDef, isAtomic, + FieldDefType, } from '../../../model/malloy_types'; -import {FT} from '../fragtype-utils'; +import * as TDU from '../typedesc-utils'; import {ExprValue} from '../types/expr-value'; import {ExpressionDef} from '../types/expression-def'; import {FieldName, FieldSpace, QueryFieldSpace} from '../types/field-space'; @@ -118,7 +119,7 @@ export abstract class AtomicFieldDeclaration type: 'error', }; } - let retType = exprValue.dataType; + let retType = exprValue.type; if (retType === 'null') { this.expr.logWarning( 'null-typed-field-definition', @@ -188,8 +189,8 @@ export abstract class AtomicFieldDeclaration } const circularDef = exprFS instanceof DefSpace && exprFS.foundCircle; if (!circularDef) { - if (exprValue.dataType !== 'error') { - const badType = FT.inspect(exprValue); + if (exprValue.type !== 'error') { + const badType = TDU.inspect(exprValue); this.logError( 'invalid-type-for-field-definition', `Cannot define '${exprName}', unexpected type: ${badType}` @@ -336,12 +337,10 @@ export class FieldDefinitionValue extends SpaceField { // A source will call this when it defines the field private defInSource?: FieldDef; fieldDef(): FieldDef { - let def = this.defInSource; - if (def === undefined) { - this.defInSource = def; - def = this.exprDef.fieldDef(this.space, this.name); - } - return def!; + const def = + this.defInSource ?? this.exprDef.fieldDef(this.space, this.name); + this.defInSource = def; + return def; } // A query will call this when it defines the field @@ -365,4 +364,9 @@ export class FieldDefinitionValue extends SpaceField { } throw new Error(`Can't get typeDesc for ${typeFrom.type}`); } + + entryType(): FieldDefType { + const typeFrom = this.defInQuery || this.fieldDef(); + return typeFrom.type; + } } diff --git a/packages/malloy/src/lang/ast/query-items/typecheck_utils.ts b/packages/malloy/src/lang/ast/query-items/typecheck_utils.ts index d5395ce74..8cbfd6cb1 100644 --- a/packages/malloy/src/lang/ast/query-items/typecheck_utils.ts +++ b/packages/malloy/src/lang/ast/query-items/typecheck_utils.ts @@ -31,11 +31,11 @@ import {MessageCode} from '../../parse-log'; import {MalloyElement} from '../types/malloy-element'; export function typecheckProject(type: TypeDesc, logTo: MalloyElement) { - if (type.dataType === 'turtle' || !expressionIsScalar(type.expressionType)) { + if (type.type === 'turtle' || !expressionIsScalar(type.expressionType)) { let useInstead: string; let kind: string; let code: MessageCode; - if (type.dataType === 'turtle') { + if (type.type === 'turtle') { useInstead = 'a nest'; kind = 'a view'; code = 'select-of-view'; @@ -60,10 +60,10 @@ export function typecheckProject(type: TypeDesc, logTo: MalloyElement) { } export function typecheckIndex(type: TypeDesc, logTo: MalloyElement) { - if (type.dataType === 'turtle' || !expressionIsScalar(type.expressionType)) { + if (type.type === 'turtle' || !expressionIsScalar(type.expressionType)) { let kind: string; let code: MessageCode; - if (type.dataType === 'turtle') { + if (type.type === 'turtle') { kind = 'a view'; code = 'index-of-view'; } else if (expressionIsAnalytic(type.expressionType)) { @@ -122,7 +122,7 @@ export function typecheckMeasure(type: TypeDesc, logTo: MalloyElement) { } export function typecheckDeclare(type: TypeDesc, logTo: MalloyElement) { - if (type.dataType === 'turtle') { + if (type.type === 'turtle') { logTo.logError( 'view-in-declare', 'Views cannot be used in a declare block' @@ -136,14 +136,11 @@ export function typecheckDeclare(type: TypeDesc, logTo: MalloyElement) { } export function typecheckCalculate(type: TypeDesc, logTo: MalloyElement) { - if ( - type.dataType === 'turtle' || - !expressionIsAnalytic(type.expressionType) - ) { + if (type.type === 'turtle' || !expressionIsAnalytic(type.expressionType)) { let useInstead: string; let kind: string; let code: MessageCode; - if (type.dataType === 'turtle') { + if (type.type === 'turtle') { useInstead = 'a nest'; kind = 'a view'; code = 'calculate-of-view'; @@ -168,14 +165,11 @@ export function typecheckCalculate(type: TypeDesc, logTo: MalloyElement) { } export function typecheckAggregate(type: TypeDesc, logTo: MalloyElement) { - if ( - type.dataType === 'turtle' || - !expressionIsAggregate(type.expressionType) - ) { + if (type.type === 'turtle' || !expressionIsAggregate(type.expressionType)) { let useInstead: string; let kind: string; let code: MessageCode; - if (type.dataType === 'turtle') { + if (type.type === 'turtle') { useInstead = 'a nest'; kind = 'a view'; code = 'aggregate-of-view'; @@ -198,11 +192,11 @@ export function typecheckAggregate(type: TypeDesc, logTo: MalloyElement) { } export function typecheckGroupBy(type: TypeDesc, logTo: MalloyElement) { - if (type.dataType === 'turtle' || !expressionIsScalar(type.expressionType)) { + if (type.type === 'turtle' || !expressionIsScalar(type.expressionType)) { let useInstead: string; let kind: string; let code: MessageCode; - if (type.dataType === 'turtle') { + if (type.type === 'turtle') { useInstead = 'a nest'; kind = 'a view'; code = 'group-by-view'; diff --git a/packages/malloy/src/lang/ast/query-properties/filters.ts b/packages/malloy/src/lang/ast/query-properties/filters.ts index db6746f9e..c73c6b749 100644 --- a/packages/malloy/src/lang/ast/query-properties/filters.ts +++ b/packages/malloy/src/lang/ast/query-properties/filters.ts @@ -46,7 +46,7 @@ export class FilterElement extends MalloyElement { filterCondition(fs: FieldSpace): FilterCondition { const exprVal = this.expr.getExpression(fs); - if (exprVal.dataType !== 'boolean') { + if (exprVal.type !== 'boolean') { this.expr.logError( 'non-boolean-filter', 'Filter expression must have boolean value' diff --git a/packages/malloy/src/lang/ast/source-elements/named-source.ts b/packages/malloy/src/lang/ast/source-elements/named-source.ts index 802f8d752..010ea8cf0 100644 --- a/packages/malloy/src/lang/ast/source-elements/named-source.ts +++ b/packages/malloy/src/lang/ast/source-elements/named-source.ts @@ -193,8 +193,8 @@ export class NamedSource extends Source { parameterSpace ?? new ParameterSpace(parametersOut ?? []); const pVal = argument.value.getExpression(paramSpace); let value = pVal.value; - if (pVal.dataType !== parameter.type && isCastType(parameter.type)) { - value = castTo(parameter.type, pVal.value, pVal.dataType, true); + if (pVal.type !== parameter.type && isCastType(parameter.type)) { + value = castTo(parameter.type, pVal.value, pVal.type, true); } outArguments[name] = { ...parameter, diff --git a/packages/malloy/src/lang/ast/source-properties/join.ts b/packages/malloy/src/lang/ast/source-properties/join.ts index 7d67be9d9..60c82f21f 100644 --- a/packages/malloy/src/lang/ast/source-properties/join.ts +++ b/packages/malloy/src/lang/ast/source-properties/join.ts @@ -119,7 +119,7 @@ export class KeyJoin extends Join { f => (f.as || f.name) === inStruct.primaryKey ); if (pkey) { - if (pkey.type === exprX.dataType) { + if (pkey.type === exprX.type) { inStruct.join = 'one'; inStruct.onExpression = { node: '=', @@ -135,7 +135,7 @@ export class KeyJoin extends Join { } else { this.logError( 'join-on-primary-key-type-mismatch', - `join_one: with type mismatch with primary key: ${exprX.dataType}/${pkey.type}` + `join_one: with type mismatch with primary key: ${exprX.type}/${pkey.type}` ); } } else { @@ -179,7 +179,7 @@ export class ExpressionJoin extends Join { return; } const exprX = this.expr.getExpression(outer); - if (exprX.dataType !== 'boolean') { + if (exprX.type !== 'boolean') { this.logError( 'non-boolean-join-on', 'join conditions must be boolean expressions' diff --git a/packages/malloy/src/lang/ast/time-utils.ts b/packages/malloy/src/lang/ast/time-utils.ts index bb73e6fe7..ec8ae5e22 100644 --- a/packages/malloy/src/lang/ast/time-utils.ts +++ b/packages/malloy/src/lang/ast/time-utils.ts @@ -25,12 +25,13 @@ import { Expr, TemporalFieldType, TimestampUnit, - FieldValueType, CastType, TypecastExpr, TimeDeltaExpr, mkTemporal, isCastType, + ExpressionValueType, + isDateUnit, } from '../../model/malloy_types'; import {TimeResult} from './types/time-result'; @@ -56,7 +57,7 @@ export function timeOffset( export function castTo( castType: CastType | {raw: string}, from: Expr, - fromType: FieldValueType, + fromType: ExpressionValueType, safe = false ): TypecastExpr { let cast: TypecastExpr; @@ -99,7 +100,12 @@ export function mkTimeResult( tt: TimestampUnit | undefined ): TimeResult { if (tt) { - return {...t, timeframe: tt}; + if (t.type === 'timestamp') { + return {...t, timeframe: tt}; + } + if (isDateUnit(tt)) { + return {...t, timeframe: tt}; + } } return t; } diff --git a/packages/malloy/src/lang/ast/typedesc-utils.ts b/packages/malloy/src/lang/ast/typedesc-utils.ts new file mode 100644 index 000000000..8742699ab --- /dev/null +++ b/packages/malloy/src/lang/ast/typedesc-utils.ts @@ -0,0 +1,174 @@ +/* + * Copyright 2023 Google LLC + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import { + AtomicTypeDef, + EvalSpace, + expressionIsScalar, + ExpressionType, + ExpressionValueType, + TD, + TypeDesc, +} from '../../model'; + +function mkTypeDesc( + // The problem is that record and array, as currently defined, require a dialect + // which isn't available. In retrospect the dialect shouldn't be in the type, + // it should only be in the field, which I wil do eventually. + dataType: Exclude, + expressionType: ExpressionType = 'scalar', + evalSpace: EvalSpace = 'constant' +): TypeDesc { + return {type: dataType, expressionType, evalSpace}; +} + +export const nullT = mkTypeDesc('null'); +export const numberT = mkTypeDesc('number'); +export const stringT = mkTypeDesc('string'); +export const dateT = mkTypeDesc('date'); +export const timestampT = mkTypeDesc('timestamp'); +export const boolT = mkTypeDesc('boolean'); +export const errorT = mkTypeDesc('error'); +export const viewT = mkTypeDesc('turtle'); +export const aggregateBoolT = mkTypeDesc('boolean', 'aggregate'); +export const anyAtomicT = [numberT, stringT, dateT, timestampT, boolT]; + +/** + * Checks if a given type is in a list + * @param check The type to check (can be undefined) + * @param from List of types which are OK + */ +export function any(check: TypeDesc | undefined, from: TypeDesc[]): boolean { + if (check) { + const found = from.find(okType => eq(okType, check)); + return found !== undefined; + } + return false; +} + +/** + * Checks if a possibly undefined candidate matches a type + * @param good The real type + * @param checkThis The possibly undefined candidate + */ +export function eq(good: TypeDesc, checkThis: TypeDesc | undefined): boolean { + return ( + checkThis !== undefined && + TD.eq(good, checkThis) && + good.expressionType === checkThis.expressionType + ); +} + +/** + * Checks if a given type is in a list, ignoring aggregate + * @param check The type to check (can be undefined) + * @param from List of types which are OK + */ +export function typeIn(check: TypeDesc | undefined, from: TypeDesc[]): boolean { + if (check) { + const found = from.find(okType => typeEq(okType, check)); + return found !== undefined; + } + return false; +} + +/** + * Checks if the base types, ignoring expressionType, are equal + * @param left Left type + * @param right Right type + * @param nullOk True if a NULL is an acceptable match + */ +export function typeEq( + left: TypeDesc, + right: TypeDesc, + nullOk = false, + errorOk = true +): boolean { + const maybeEq = TD.eq(left, right); + const nullEq = nullOk && (left.type === 'null' || right.type === 'null'); + const errorEq = errorOk && (left.type === 'error' || right.type === 'error'); + return maybeEq || nullEq || errorEq; +} + +/** + * + * For error messages, returns a comma seperated list of readable names + * for a list of types. + * @param types List of type or objects with types + */ +export function inspect(...types: (TypeDesc | undefined)[]): string { + const strings = types.map(type => { + if (type) { + let inspected: string = type.type; + if (!expressionIsScalar(type.expressionType)) { + inspected = `${type.expressionType} ${inspected}`; + } + return inspected; + } + return 'undefined'; + }); + return strings.join(','); +} + +/** + * Used when using a TypeDesc or TypeDesc-like interface to + * create a field, don't copy the non type fields. + */ +export function atomicDef(td: AtomicTypeDef | TypeDesc): AtomicTypeDef { + if (TD.isAtomic(td)) { + switch (td.type) { + case 'array': { + return { + name: '', + type: 'array', + join: 'many', + elementTypeDef: td.elementTypeDef, + dialect: td.dialect, + fields: td.fields, + }; + } + case 'record': { + return { + name: '', + type: 'record', + join: 'one', + dialect: td.dialect, + fields: td.fields, + }; + } + case 'number': { + return td.numberType + ? {type: 'number', numberType: td.numberType} + : {type: 'number'}; + } + case 'sql native': { + return td.rawType + ? {type: 'sql native', rawType: td.rawType} + : {type: 'sql native'}; + } + default: + return {type: td.type}; + } + } + return {type: 'error'}; +} diff --git a/packages/malloy/src/lang/ast/types/expr-result.ts b/packages/malloy/src/lang/ast/types/expr-result.ts index adbcf72ec..baaa77495 100644 --- a/packages/malloy/src/lang/ast/types/expr-result.ts +++ b/packages/malloy/src/lang/ast/types/expr-result.ts @@ -24,7 +24,9 @@ import {Expr, TypeDesc} from '../../../model/malloy_types'; type MorphicValues = Record; -export interface ExprResult extends TypeDesc { +export interface WithValue { value: Expr; morphic?: MorphicValues; } + +export type ExprResult = TypeDesc & WithValue; diff --git a/packages/malloy/src/lang/ast/types/expr-value.ts b/packages/malloy/src/lang/ast/types/expr-value.ts index 20a07b05b..ec934afee 100644 --- a/packages/malloy/src/lang/ast/types/expr-value.ts +++ b/packages/malloy/src/lang/ast/types/expr-value.ts @@ -23,8 +23,8 @@ import { Expr, - FieldValueType, - TemporalFieldType, + ExpressionValueTypeDef, + TemporalTypeDef, TimestampUnit, maxOfExpressionTypes, mergeEvalSpaces, @@ -37,18 +37,15 @@ export type ExprValue = ExprResult | TimeResult; export function computedExprValue({ value, dataType, - rawType, from, }: { value: Expr; - dataType: FieldValueType; - rawType?: string; + dataType: ExpressionValueTypeDef; from: ExprValue[]; }): ExprValue { return { + ...dataType, value, - dataType, - rawType, expressionType: maxOfExpressionTypes(from.map(e => e.expressionType)), evalSpace: mergeEvalSpaces(...from.map(e => e.evalSpace)), }; @@ -57,33 +54,38 @@ export function computedExprValue({ export function computedTimeResult({ value, dataType, - rawType, from, timeframe, }: { value: Expr; - rawType?: string; - dataType: TemporalFieldType; + dataType: TemporalTypeDef; timeframe?: TimestampUnit; from: ExprValue[]; -}) { - return {...computedExprValue({value, dataType, rawType, from}), timeframe}; +}): TimeResult { + const xv = computedExprValue({value, dataType, from}); + const y: TimeResult = { + ...dataType, + expressionType: xv.expressionType, + evalSpace: xv.evalSpace, + value: xv.value, + }; + if (timeframe) { + y.timeframe = timeframe; + } + return y; } export function computedErrorExprValue({ dataType, - rawType, from, error, }: { error: string; - rawType?: string; - dataType?: FieldValueType; + dataType?: ExpressionValueTypeDef; from: ExprValue[]; -}) { +}): ExprValue { return computedExprValue({ - dataType: dataType || 'error', - rawType, + dataType: dataType ?? {type: 'error'}, value: {node: 'error', message: error}, from, }); @@ -91,8 +93,7 @@ export function computedErrorExprValue({ export function literalExprValue(options: { value: Expr; - rawType?: string; - dataType: FieldValueType; + dataType: ExpressionValueTypeDef; }): ExprValue { // Makes literal, output, scalar because from is empty return computedExprValue({...options, from: []}); @@ -101,17 +102,21 @@ export function literalExprValue(options: { export function literalTimeResult({ value, dataType, - rawType, timeframe, }: { value: Expr; - rawType?: string; - dataType: TemporalFieldType; + dataType: TemporalTypeDef; timeframe?: TimestampUnit; }): TimeResult { - return { - ...computedExprValue({value, dataType, rawType, from: []}), - dataType, - timeframe, + const xv = computedExprValue({value, dataType, from: []}); + const y: TimeResult = { + ...dataType, + expressionType: xv.expressionType, + evalSpace: xv.evalSpace, + value: xv.value, }; + if (timeframe) { + y.timeframe = timeframe; + } + return y; } diff --git a/packages/malloy/src/lang/ast/types/expression-def.ts b/packages/malloy/src/lang/ast/types/expression-def.ts index 6797ab35e..f339959bd 100644 --- a/packages/malloy/src/lang/ast/types/expression-def.ts +++ b/packages/malloy/src/lang/ast/types/expression-def.ts @@ -26,13 +26,12 @@ import { TimestampUnit, isDateUnit, isTemporalField, - FieldValueType, - ExpressionValueType, expressionIsAggregate, + TD, + LeafExpressionType, } from '../../../model/malloy_types'; - +import * as TDU from '../typedesc-utils'; import {errorFor} from '../ast-utils'; -import {FT} from '../fragtype-utils'; import { ExprValue, computedErrorExprValue, @@ -82,7 +81,7 @@ export abstract class ExpressionDef extends MalloyElement { * @param space Namespace for looking up field references */ abstract getExpression(fs: FieldSpace): ExprValue; - legalChildTypes = FT.anyAtomicT; + legalChildTypes = TDU.anyAtomicT; /** * Some operators want to give the right hand value a chance to @@ -105,15 +104,15 @@ export abstract class ExpressionDef extends MalloyElement { * @param eVal ...list of expressions that must match legalChildTypes */ typeCheck(eNode: ExpressionDef, eVal: ExprValue): boolean { - if (eVal.dataType !== 'error' && !FT.in(eVal, this.legalChildTypes)) { - if (eVal.dataType === 'sql native') { + if (eVal.type !== 'error' && !TDU.any(eVal, this.legalChildTypes)) { + if (eVal.type === 'sql native') { eNode.logError('sql-native-not-allowed-in-expression', { rawType: eVal.rawType, }); } else { eNode.logError( 'expression-type-error', - `'${this.elementType}' Can't use type ${FT.inspect(eVal)}` + `'${this.elementType}' Can't use type ${TDU.inspect(eVal)}` ); } return false; @@ -167,7 +166,7 @@ export abstract class ExpressionDef extends MalloyElement { export class ExprDuration extends ExpressionDef { elementType = 'duration'; - legalChildTypes = [FT.timestampT, FT.dateT]; + legalChildTypes = [TDU.timestampT, TDU.dateT]; constructor( readonly n: ExpressionDef, readonly timeframe: TimestampUnit @@ -182,12 +181,12 @@ export class ExprDuration extends ExpressionDef { ): ExprValue { const lhs = left.getExpression(fs); this.typeCheck(this, lhs); - if (isTemporalField(lhs.dataType) && (op === '+' || op === '-')) { + if (isTemporalField(lhs.type) && (op === '+' || op === '-')) { const num = this.n.getExpression(fs); - if (!FT.typeEq(num, FT.numberT)) { + if (!TDU.typeEq(num, TDU.numberT)) { this.logError( 'invalid-duration-quantity', - `Duration quantity needs number not '${num.dataType}` + `Duration quantity needs number not '${num.type}` ); return errorFor('illegal unit expression'); } @@ -198,21 +197,15 @@ export class ExprDuration extends ExpressionDef { if (isGranularResult(lhs) && lhs.timeframe === this.timeframe) { resultGranularity = lhs.timeframe; } - if (lhs.dataType === 'date' && !isDateUnit(this.timeframe)) { + if (lhs.type === 'date' && !isDateUnit(this.timeframe)) { return this.loggedErrorExpr( 'invalid-timeframe-for-time-offset', `Cannot offset date by ${this.timeframe}` ); } return computedTimeResult({ - dataType: lhs.dataType, - value: timeOffset( - lhs.dataType, - lhs.value, - op, - num.value, - this.timeframe - ), + dataType: {type: lhs.type}, + value: timeOffset(lhs.type, lhs.value, op, num.value, this.timeframe), timeframe: resultGranularity, from: [lhs, num], }); @@ -223,32 +216,34 @@ export class ExprDuration extends ExpressionDef { getExpression(fs: FieldSpace): ExprValue { const num = this.n.getExpression(fs); return computedErrorExprValue({ - dataType: 'duration', + dataType: {type: 'duration'}, error: 'Duration is not a value', from: [num], }); } } -function willMorphTo(ev: ExprValue, t: string): Expr | undefined { - if (ev.dataType === t) { +function willMorphTo(ev: ExprValue, t: MorphicType): Expr | undefined { + if (ev.type === t) { return ev.value; } return ev.morphic && ev.morphic[t]; } +export type MorphicType = 'date' | 'timestamp'; export function getMorphicValue( mv: ExprValue, - mt: FieldValueType + mt: MorphicType ): ExprValue | undefined { - if (mv.dataType === mt) { + if (mv.type === mt) { return mv; } if (mv.morphic && mv.morphic[mt]) { return { - ...mv, - dataType: mt, + type: mt, value: mv.morphic[mt], + expressionType: mv.expressionType, + evalSpace: mv.evalSpace, }; } } @@ -259,11 +254,11 @@ function timeCompare( op: CompareMalloyOperator, rhs: ExprValue ): Expr | undefined { - const leftIsTime = isTemporalField(lhs.dataType); - const rightIsTime = isTemporalField(rhs.dataType); + const leftIsTime = isTemporalField(lhs.type); + const rightIsTime = isTemporalField(rhs.type); const node = getExprNode(op); if (leftIsTime && rightIsTime) { - if (lhs.dataType !== rhs.dataType) { + if (lhs.type !== rhs.type) { const lval = willMorphTo(lhs, 'timestamp'); const rval = willMorphTo(rhs, 'timestamp'); if (lval && rval) { @@ -275,12 +270,12 @@ function timeCompare( } if ( (leftIsTime || rightIsTime) && - lhs.dataType !== 'null' && - rhs.dataType !== 'null' + lhs.type !== 'null' && + rhs.type !== 'null' ) { left.logError( 'time-comparison-type-mismatch', - `Cannot compare a ${lhs.dataType} to a ${rhs.dataType}` + `Cannot compare a ${lhs.type} to a ${rhs.type}` ); return {node: 'false'}; } @@ -288,15 +283,15 @@ function timeCompare( } function regexEqual(left: ExprValue, right: ExprValue): Expr | undefined { - if (left.dataType === 'string') { - if (right.dataType === 'regular expression') { + if (left.type === 'string') { + if (right.type === 'regular expression') { return { node: 'regexpMatch', kids: {expr: left.value, regex: right.value}, }; } - } else if (right.dataType === 'string') { - if (left.dataType === 'regular expression') { + } else if (right.type === 'string') { + if (left.type === 'regular expression') { return { node: 'regexpMatch', kids: {expr: right.value, regex: left.value}, @@ -313,11 +308,11 @@ function nullCompare( ): Expr | undefined { const not = op === '!=' || op === '!~'; const nullCmp = not ? 'is-not-null' : 'is-null'; - if (left.dataType === 'null' || right.dataType === 'null') { - if (left.dataType !== 'null') { + if (left.type === 'null' || right.type === 'null') { + if (left.type !== 'null') { return {node: nullCmp, e: left.value}; } - if (right.dataType !== 'null') { + if (right.type !== 'null') { return {node: nullCmp, e: right.value}; } return {node: not ? 'false' : 'true'}; @@ -339,15 +334,14 @@ function equality( if (err) return err; // Unsupported types can be compare with null - const checkUnsupport = - lhs.dataType === 'sql native' || rhs.dataType === 'sql native'; - if (checkUnsupport) { - const oneNull = lhs.dataType === 'null' || rhs.dataType === 'null'; - const rawMatch = lhs.rawType && lhs.rawType === rhs.rawType; - if (!(oneNull || rawMatch)) { + const lhRaw = TD.isSQL(lhs) ? lhs.rawType || 'typeless-left' : undefined; + const rhRaw = TD.isSQL(rhs) ? rhs.rawType || 'typeless-right' : undefined; + if (lhRaw || rhRaw) { + const oneNull = lhs.type === 'null' || rhs.type === 'null'; + if (!(oneNull || lhRaw === rhRaw)) { const noGo = unsupportError(left, lhs, right, rhs); if (noGo) { - return {...noGo, dataType: 'boolean'}; + return {...noGo, type: 'boolean'}; } } } @@ -356,11 +350,11 @@ function equality( kids: {left: lhs.value, right: rhs.value}, }; - if (lhs.dataType !== 'error' && rhs.dataType !== 'error') { + if (lhs.type !== 'error' && rhs.type !== 'error') { switch (op) { case '~': case '!~': { - if (lhs.dataType !== 'string' || rhs.dataType !== 'string') { + if (lhs.type !== 'string' || rhs.type !== 'string') { let regexCmp = regexEqual(lhs, rhs); if (regexCmp) { if (op[0] === '!') { @@ -384,7 +378,7 @@ function equality( } return computedExprValue({ - dataType: 'boolean', + dataType: {type: 'boolean'}, value, from: [lhs, rhs], }); @@ -404,7 +398,7 @@ function compare( const noCompare = unsupportError(left, lhs, right, rhs); if (noCompare) { - return {...noCompare, dataType: 'boolean'}; + return {...noCompare, type: 'boolean'}; } const value = timeCompare(left, lhs, op, rhs) || { node: getExprNode(op), @@ -412,7 +406,7 @@ function compare( }; return computedExprValue({ - dataType: 'boolean', + dataType: {type: 'boolean'}, value, from: [lhs, rhs], }); @@ -433,19 +427,19 @@ function numeric( const noGo = unsupportError(left, lhs, right, rhs); if (noGo) return noGo; - if (lhs.dataType !== 'number') { + if (lhs.type !== 'number') { left.logError( 'arithmetic-operation-type-mismatch', - `The '${op}' operator requires a number, not a '${lhs.dataType}'` + `The '${op}' operator requires a number, not a '${lhs.type}'` ); - } else if (rhs.dataType !== 'number') { + } else if (rhs.type !== 'number') { right.logError( 'arithmetic-operation-type-mismatch', - `The '${op}' operator requires a number, not a '${rhs.dataType}'` + `The '${op}' operator requires a number, not a '${rhs.type}'` ); } else { return computedExprValue({ - dataType: 'number', + dataType: {type: 'number'}, value: {node: op, kids: {left: lhs.value, right: rhs.value}}, from: [lhs, rhs], }); @@ -466,22 +460,22 @@ function delta( return noGo; } - const timeLHS = isTemporalField(lhs.dataType); + const timeLHS = isTemporalField(lhs.type); const err = errorCascade(timeLHS ? 'error' : 'number', lhs, rhs); if (err) return err; if (timeLHS) { let duration: ExpressionDef = right; - if (rhs.dataType !== 'duration') { + if (rhs.type !== 'duration') { if (isGranularResult(lhs)) { duration = new ExprDuration(right, lhs.timeframe); - } else if (lhs.dataType === 'date') { + } else if (lhs.type === 'date') { duration = new ExprDuration(right, 'day'); } else { return left.loggedErrorExpr( 'time-offset-type-mismatch', - `Can not offset time by '${rhs.dataType}'` + `Can not offset time by '${rhs.type}'` ); } } @@ -528,12 +522,12 @@ export function applyBinary( const err = errorCascade('number', num, denom); if (err) return err; - if (num.dataType !== 'number') { + if (num.type !== 'number') { left.logError( 'arithmetic-operation-type-mismatch', 'Numerator must be a number' ); - } else if (denom.dataType !== 'number') { + } else if (denom.type !== 'number') { right.logError( 'arithmetic-operation-type-mismatch', 'Denominator must be a number' @@ -544,7 +538,7 @@ export function applyBinary( kids: {left: num.value, right: denom.value}, }; return computedExprValue({ - dataType: 'number', + dataType: {type: 'number'}, value: divmod, from: [num, denom], }); @@ -558,12 +552,12 @@ export function applyBinary( } function errorCascade( - dataType: ExpressionValueType, + type: LeafExpressionType, ...es: ExprValue[] ): ExprValue | undefined { - if (es.some(e => e.dataType === 'error')) { + if (es.some(e => e.type === 'error')) { return computedExprValue({ - dataType, + dataType: {type}, value: {node: 'error', message: 'cascading error'}, from: es, }); @@ -580,16 +574,16 @@ function unsupportError( rhs: ExprValue ): ExprValue | undefined { const ret = computedExprValue({ - dataType: lhs.dataType, + dataType: lhs, value: {node: 'error', message: 'sql-native unsupported'}, from: [lhs, rhs], }); - if (lhs.dataType === 'sql native') { + if (lhs.type === 'sql native') { l.logError('sql-native-not-allowed-in-expression', {rawType: lhs.rawType}); - ret.dataType = rhs.dataType; + ret.type = rhs.type; return ret; } - if (rhs.dataType === 'sql native') { + if (rhs.type === 'sql native') { r.logError('sql-native-not-allowed-in-expression', {rawType: rhs.rawType}); return ret; } diff --git a/packages/malloy/src/lang/ast/types/global-name-space.ts b/packages/malloy/src/lang/ast/types/global-name-space.ts index 819bb4a29..a0d3bd651 100644 --- a/packages/malloy/src/lang/ast/types/global-name-space.ts +++ b/packages/malloy/src/lang/ast/types/global-name-space.ts @@ -26,7 +26,7 @@ import { getMalloyStandardFunctions, } from '../../../dialect'; import {getDialects} from '../../../dialect/dialect_map'; -import {FunctionDef, FunctionOverloadDef} from '../../../model'; +import {TD, FunctionDef, FunctionOverloadDef} from '../../../model'; import {ModelEntry} from './model-entry'; import {NameSpace} from './name-space'; @@ -70,9 +70,7 @@ function paramsEqual( param.allowedTypes.length === otherParam.allowedTypes.length && param.allowedTypes.every(t => otherParam.allowedTypes.some( - ot => - t.dataType === ot.dataType && - t.expressionType === ot.expressionType + ot => TD.eq(t, ot) && t.expressionType === ot.expressionType ) ) ); diff --git a/packages/malloy/src/lang/ast/types/granular-result.ts b/packages/malloy/src/lang/ast/types/granular-result.ts index 6e404260f..cc219de60 100644 --- a/packages/malloy/src/lang/ast/types/granular-result.ts +++ b/packages/malloy/src/lang/ast/types/granular-result.ts @@ -26,11 +26,12 @@ import {TimestampUnit} from '../../../model/malloy_types'; import {ExprValue} from './expr-value'; import {TimeResult} from './time-result'; -export interface GranularResult extends TimeResult { +export type GranularResult = TimeResult & { timeframe: TimestampUnit; -} +}; + export function isGranularResult(v: ExprValue): v is GranularResult { - if (v.dataType === 'date' || v.dataType === 'timestamp') { + if (v.type === 'date' || v.type === 'timestamp') { return (v as GranularResult).timeframe !== undefined; } return false; diff --git a/packages/malloy/src/lang/ast/types/space-field.ts b/packages/malloy/src/lang/ast/types/space-field.ts index 9430ec21a..b9887d278 100644 --- a/packages/malloy/src/lang/ast/types/space-field.ts +++ b/packages/malloy/src/lang/ast/types/space-field.ts @@ -34,21 +34,8 @@ export abstract class SpaceField extends SpaceEntry { readonly refType = 'field'; protected fieldTypeFromFieldDef(def: AtomicFieldDef): TypeDesc { - const ref: TypeDesc = { - dataType: def.type, - expressionType: 'scalar', - evalSpace: 'input', - }; - if (def.expressionType) { - ref.expressionType = def.expressionType; - } - if ( - ref.dataType === 'sql native' && - def.type === 'sql native' && - def.rawType - ) { - ref.rawType = def.rawType; - } + const expressionType = def.expressionType || 'scalar'; + const ref: TypeDesc = {...def, expressionType, evalSpace: 'input'}; return ref; } diff --git a/packages/malloy/src/lang/ast/types/space-param.ts b/packages/malloy/src/lang/ast/types/space-param.ts index 7aefba4c1..827100726 100644 --- a/packages/malloy/src/lang/ast/types/space-param.ts +++ b/packages/malloy/src/lang/ast/types/space-param.ts @@ -21,10 +21,11 @@ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import {Parameter, TypeDesc} from '../../../model/malloy_types'; +import {FieldDefType, Parameter, TypeDesc} from '../../../model/malloy_types'; import {SpaceEntry} from './space-entry'; import {HasParameter} from '../parameters/has-parameter'; +import * as TDU from '../typedesc-utils'; export abstract class SpaceParam extends SpaceEntry { abstract parameter(): Parameter; @@ -44,8 +45,15 @@ export class AbstractParameter extends SpaceParam { } typeDesc(): TypeDesc { - const type = this.parameter().type ?? 'error'; - return {dataType: type, expressionType: 'scalar', evalSpace: 'constant'}; + return { + ...TDU.atomicDef(this.parameter()), + expressionType: 'scalar', + evalSpace: 'constant', + }; + } + + entryType(): FieldDefType { + return this.parameter().type; } } @@ -60,11 +68,15 @@ export class DefinedParameter extends SpaceParam { typeDesc(): TypeDesc { return { - dataType: this.paramDef.type, + ...TDU.atomicDef(this.paramDef), expressionType: 'scalar', // TODO Not sure whether params are considered "input space". It seems like they // could be input or constant, depending on usage (same as above). evalSpace: 'input', }; } + + entryType(): FieldDefType { + return this.paramDef.type; + } } diff --git a/packages/malloy/src/lang/ast/types/time-result.ts b/packages/malloy/src/lang/ast/types/time-result.ts index 7d8cc9ae6..2fc3d2cb6 100644 --- a/packages/malloy/src/lang/ast/types/time-result.ts +++ b/packages/malloy/src/lang/ast/types/time-result.ts @@ -21,11 +21,11 @@ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import {TemporalFieldType, TimestampUnit} from '../../../model/malloy_types'; +import {TemporalTypeDef, TimestampUnit} from '../../../model/malloy_types'; import {ExprResult} from './expr-result'; -export interface TimeResult extends ExprResult { - dataType: TemporalFieldType; - timeframe?: TimestampUnit; -} +export type TimeResult = TemporalTypeDef & + ExprResult & { + timeframe?: TimestampUnit; + }; diff --git a/packages/malloy/src/lang/parse-log.ts b/packages/malloy/src/lang/parse-log.ts index a55c08838..d9e346f80 100644 --- a/packages/malloy/src/lang/parse-log.ts +++ b/packages/malloy/src/lang/parse-log.ts @@ -21,7 +21,7 @@ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import {DocumentLocation, FieldValueType} from '../model/malloy_types'; +import {DocumentLocation, ExpressionValueType} from '../model/malloy_types'; import {EventStream} from '../runtime_types'; export type LogSeverity = 'error' | 'warn' | 'debug'; @@ -90,21 +90,21 @@ export class BaseMessageLogger implements MessageLogger { type MessageParameterTypes = { 'pick-type-does-not-match': { - pickType: FieldValueType; - returnType: FieldValueType; + pickType: ExpressionValueType; + returnType: ExpressionValueType; }; 'pick-else-type-does-not-match': { - elseType: FieldValueType; - returnType: FieldValueType; + elseType: ExpressionValueType; + returnType: ExpressionValueType; }; 'pick-default-type-does-not-match': { - defaultType: FieldValueType; - returnType: FieldValueType; + defaultType: ExpressionValueType; + returnType: ExpressionValueType; }; 'pick-missing-else': {}; 'pick-missing-value': {}; 'pick-illegal-partial': {}; - 'pick-when-must-be-boolean': {whenType: FieldValueType}; + 'pick-when-must-be-boolean': {whenType: ExpressionValueType}; 'experiment-not-enabled': {experimentId: string}; 'experimental-dialect-not-enabled': {dialect: string}; 'sql-native-not-allowed-in-expression': { @@ -353,17 +353,17 @@ type MessageParameterTypes = { 'not-yet-implemented': string; 'sql-case': string; 'case-then-type-does-not-match': { - thenType: FieldValueType; - returnType: FieldValueType; + thenType: ExpressionValueType; + returnType: ExpressionValueType; }; 'case-else-type-does-not-match': { - elseType: FieldValueType; - returnType: FieldValueType; + elseType: ExpressionValueType; + returnType: ExpressionValueType; }; - 'case-when-must-be-boolean': {whenType: FieldValueType}; + 'case-when-must-be-boolean': {whenType: ExpressionValueType}; 'case-when-type-does-not-match': { - whenType: FieldValueType; - valueType: FieldValueType; + whenType: ExpressionValueType; + valueType: ExpressionValueType; }; 'or-choices-only': string; 'sql-in': string; diff --git a/packages/malloy/src/lang/test/literals.spec.ts b/packages/malloy/src/lang/test/literals.spec.ts index 89ff607ac..414d4230b 100644 --- a/packages/malloy/src/lang/test/literals.spec.ts +++ b/packages/malloy/src/lang/test/literals.spec.ts @@ -95,7 +95,7 @@ describe('literals', () => { const exprModel = new BetaExpression(expr); expect(exprModel).toTranslate(); const ir = exprModel.generated(); - expect(ir.dataType).toEqual(timeType); + expect(ir.type).toEqual(timeType); if (timeframe) { expect(isGranularResult(ir)).toBeTruthy(); if (isGranularResult(ir)) { diff --git a/packages/malloy/src/lang/test/parse-expects.ts b/packages/malloy/src/lang/test/parse-expects.ts index 57c70767f..be142d5bb 100644 --- a/packages/malloy/src/lang/test/parse-expects.ts +++ b/packages/malloy/src/lang/test/parse-expects.ts @@ -292,10 +292,8 @@ expect.extend({ return ok; } const d = exprModel.generated(); - const pass = d.dataType === returnType; - const msg = `Expression type ${d.dataType} ${ - pass ? '=' : '!=' - } ${returnType}`; + const pass = d.type === returnType; + const msg = `Expression type ${d.type} ${pass ? '=' : '!='} ${returnType}`; return {pass, message: () => msg}; }, toLog: function (s: TestSource, ...msgs: ProblemSpec[]) { diff --git a/packages/malloy/src/model/malloy_types.ts b/packages/malloy/src/model/malloy_types.ts index 3a1d0e0d7..84da40ac8 100644 --- a/packages/malloy/src/model/malloy_types.ts +++ b/packages/malloy/src/model/malloy_types.ts @@ -390,11 +390,11 @@ export interface Expression { type ConstantExpr = Expr; -export interface Parameter { - value: ConstantExpr | null; +interface ParameterInfo { name: string; - type: AtomicFieldType; + value: ConstantExpr | null; } +export type Parameter = AtomicTypeDef & ParameterInfo; export type Argument = Parameter; export function paramHasValue(p: Parameter): boolean { @@ -639,10 +639,6 @@ export interface FieldBase extends NamedObject, Expression, ResultMetadata { annotation?: Annotation; } -interface FieldAtomicBase extends FieldBase { - type: AtomicFieldType; -} - // this field definition represents something in the database. export function fieldIsIntrinsic(f: FieldDef): boolean { return isAtomicFieldType(f.type) && !hasExpression(f); @@ -1133,27 +1129,39 @@ export function isScalarArray(def: FieldDef | StructDef) { export type StructDef = SourceDef | RecordFieldDef | ArrayDef; -export type ExpressionValueType = - | AtomicFieldType +// "NonAtomic" are types that a name lookup or a computation might +// have which are not AtomicFieldDefs. I asked an AI for a word for +// for "non-atomic" and even the AI couldn't think of the right word. +export type NonAtomicType = + | Exclude + | 'turtle' // do NOT have the full type info, just noting the type | 'null' | 'duration' | 'any' | 'regular expression'; +export interface NonAtomicTypeDef { + type: NonAtomicType; +} -export type FieldValueType = ExpressionValueType | 'turtle' | JoinFieldTypes; +export type ExpressionValueType = AtomicFieldType | NonAtomicType; +export type ExpressionValueTypeDef = AtomicTypeDef | NonAtomicTypeDef; +export type LeafExpressionType = Exclude< + ExpressionValueType, + JoinElementType | 'turtle' +>; -export interface ExpressionTypeDesc { - dataType: FieldValueType; +export type TypeInfo = { expressionType: ExpressionType; - rawType?: string; evalSpace: EvalSpace; -} +}; + +export type TypeDesc = ExpressionValueTypeDef & TypeInfo; -export interface FunctionParamTypeDesc { - dataType: FieldValueType; +export type FunctionParamType = ExpressionValueTypeDef | {type: 'any'}; +export type FunctionParamTypeDesc = FunctionParamType & { expressionType: ExpressionType | undefined; evalSpace: EvalSpace; -} +}; export type EvalSpace = 'constant' | 'input' | 'output' | 'literal'; @@ -1174,13 +1182,6 @@ export function mergeEvalSpaces(...evalSpaces: EvalSpace[]): EvalSpace { return 'input'; } -export interface TypeDesc { - dataType: FieldValueType; - expressionType: ExpressionType; - rawType?: string; - evalSpace: EvalSpace; -} - export interface FunctionParameterDef { name: string; // These expression types are MAXIMUM types -- e.g. if you specify "scalar", @@ -1227,8 +1228,8 @@ export type LeafAtomicTypeDef = | ErrorTypeDef; export type AtomicTypeDef = LeafAtomicTypeDef | ArrayTypeDef | RecordTypeDef; -export type LeafAtomicDef = LeafAtomicTypeDef & FieldAtomicBase; -export type AtomicFieldDef = AtomicTypeDef & FieldAtomicBase; +export type LeafAtomicDef = LeafAtomicTypeDef & FieldBase; +export type AtomicFieldDef = AtomicTypeDef & FieldBase; export function isLeafAtomic( fd: FieldDef | QueryFieldDef | AtomicTypeDef @@ -1246,6 +1247,7 @@ export function isLeafAtomic( // Sources have fields like this ... export type FieldDef = AtomicFieldDef | JoinFieldDef | TurtleDef; +export type FieldDefType = AtomicFieldType | 'turtle' | JoinElementType; // Queries have fields like this .. @@ -1429,27 +1431,30 @@ export interface PrepareResultOptions { materializedTablePrefix?: string; } -type UTD = AtomicTypeDef | undefined; +type UTD = AtomicTypeDef | FunctionParamTypeDesc | undefined; +/** + * A set of utilities for asking questions TypeDef/TypeDesc + * (which is OK because TypeDesc is an extension of a TypeDef) + */ export const TD = { - isA: (td: UTD, ...tList: string[]) => td && tList.includes(td.type), - notA: (td: UTD, ...tList: string[]) => td && !tList.includes(td.type), - isString: (td: UTD): td is StringTypeDef => - td !== undefined && td.type === 'string', - isNumber: (td: UTD): td is NumberTypeDef => - td !== undefined && td.type === 'number', - isBoolean: (td: UTD): td is BooleanTypeDef => - td !== undefined && td.type === 'boolean', - isJSON: (td: UTD): td is JSONTypeDef => - td !== undefined && td.type === 'json', - isSQL: (td: UTD): td is NativeUnsupportedTypeDef => - td !== undefined && td.type === 'sql native', - isDate: (td: UTD): td is DateTypeDef => - td !== undefined && td.type === 'date', - isTimestamp: (td: UTD): td is TimestampTypeDef => - td !== undefined && td.type === 'timestamp', - isError: (td: UTD): td is ErrorTypeDef => - td !== undefined && td.type === 'error', - eq: function (x: UTD, y: UTD): boolean { + isAtomic(td: UTD): td is AtomicTypeDef { + return td !== undefined && isAtomicFieldType(td.type); + }, + isLeafAtomic(td: UTD): td is LeafAtomicTypeDef { + return td !== undefined && isLeafAtomic({type: td.type} as AtomicTypeDef); + }, + isString: (td: UTD): td is StringTypeDef => td?.type === 'string', + isNumber: (td: UTD): td is NumberTypeDef => td?.type === 'number', + isBoolean: (td: UTD): td is BooleanTypeDef => td?.type === 'boolean', + isJSON: (td: UTD): td is JSONTypeDef => td?.type === 'json', + isSQL: (td: UTD): td is NativeUnsupportedTypeDef => td?.type === 'sql native', + isDate: (td: UTD): td is DateTypeDef => td?.type === 'date', + isTimestamp: (td: UTD): td is TimestampTypeDef => td?.type === 'timestamp', + isTemporal(td: UTD): td is TimestampTypeDef { + return td?.type === 'timestamp' || td?.type === 'date'; + }, + isError: (td: UTD): td is ErrorTypeDef => td?.type === 'error', + eq(x: UTD, y: UTD): boolean { if (x === undefined || y === undefined) { return false; } @@ -1483,13 +1488,11 @@ export const TD = { } else if (x.type === 'record' && y.type === 'record') { return checkFields(x, y); } + if (x.type === 'sql native' && y.type === 'sql native') { + return x.rawType !== undefined && x.rawType === y.rawType; + } return x.type === y.type; }, - timestamp: (): TimestampTypeDef => ({type: 'timestamp'}), - date: (): DateTypeDef => ({type: 'date'}), - string: (): StringTypeDef => ({type: 'string'}), - number: (): NumberTypeDef => ({type: 'number', numberType: 'float'}), - error: (): ErrorTypeDef => ({type: 'error'}), }; // clang-format on