From 574b8aedc2f933217c9390d235b8de22edb7f51d Mon Sep 17 00:00:00 2001 From: Mark Sujew Date: Wed, 8 Mar 2023 15:56:48 +0100 Subject: [PATCH 1/3] Initial draft of the type system API --- .editorconfig | 8 +++ .eslintignore | 1 + .eslintrc.json | 75 +++++++++++++++++++++++ package.json | 39 ++++++++++++ packages/typir/package.json | 42 +++++++++++++ packages/typir/src/assignablity.ts | 16 +++++ packages/typir/src/base.ts | 22 +++++++ packages/typir/src/function-type.ts | 29 +++++++++ packages/typir/src/index.ts | 0 packages/typir/src/indexer.ts | 13 ++++ packages/typir/src/is.ts | 15 +++++ packages/typir/src/primitive.ts | 26 ++++++++ packages/typir/src/tuple-type.ts | 15 +++++ packages/typir/src/type-category.ts | 37 ++++++++++++ packages/typir/src/type-function.ts | 16 +++++ packages/typir/src/type-intersection.ts | 10 ++++ packages/typir/src/type-parameter.ts | 32 ++++++++++ packages/typir/src/type-system.ts | 34 +++++++++++ packages/typir/src/type-union.ts | 10 ++++ packages/typir/src/utils.ts | 3 + tsconfig.build.json | 21 +++++++ tsconfig.json | 80 +++++++++++++++++++++++++ vite.config.ts | 21 +++++++ 23 files changed, 565 insertions(+) create mode 100644 .editorconfig create mode 100644 .eslintignore create mode 100644 .eslintrc.json create mode 100644 package.json create mode 100644 packages/typir/package.json create mode 100644 packages/typir/src/assignablity.ts create mode 100644 packages/typir/src/base.ts create mode 100644 packages/typir/src/function-type.ts create mode 100644 packages/typir/src/index.ts create mode 100644 packages/typir/src/indexer.ts create mode 100644 packages/typir/src/is.ts create mode 100644 packages/typir/src/primitive.ts create mode 100644 packages/typir/src/tuple-type.ts create mode 100644 packages/typir/src/type-category.ts create mode 100644 packages/typir/src/type-function.ts create mode 100644 packages/typir/src/type-intersection.ts create mode 100644 packages/typir/src/type-parameter.ts create mode 100644 packages/typir/src/type-system.ts create mode 100644 packages/typir/src/type-union.ts create mode 100644 packages/typir/src/utils.ts create mode 100644 tsconfig.build.json create mode 100644 tsconfig.json create mode 100644 vite.config.ts diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..a9edfeb --- /dev/null +++ b/.editorconfig @@ -0,0 +1,8 @@ +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +insert_final_newline = true diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..4c43fe6 --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +*.js \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..a705bbb --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,75 @@ +{ + "root": true, + "parser": "@typescript-eslint/parser", + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended" + ], + "parserOptions": { + "ecmaVersion": 2017, + "sourceType": "module" + }, + "plugins": [ + "@typescript-eslint", + "header" + ], + "ignorePatterns": [ + "**/{node_modules,lib,bin}" + ], + "rules": { + // List of [ESLint rules](https://eslint.org/docs/rules/) + "arrow-parens": ["off", "as-needed"], // do not force arrow function parentheses + "constructor-super": "error", // checks the correct use of super() in sub-classes + "dot-notation": "error", // obj.a instead of obj['a'] when possible + "eqeqeq": "error", // ban '==', don't use 'smart' option! + "guard-for-in": "error", // needs obj.hasOwnProperty(key) checks + "new-parens": "error", // new Error() instead of new Error + "no-bitwise": "error", // bitwise operators &, | can be confused with &&, || + "no-caller": "error", // ECMAScript deprecated arguments.caller and arguments.callee + "no-cond-assign": "error", // assignments if (a = '1') are error-prone + "no-debugger": "error", // disallow debugger; statements + "no-eval": "error", // eval is considered unsafe + "no-inner-declarations": "off", // we need to have 'namespace' functions when using TS 'export =' + "no-labels": "error", // GOTO is only used in BASIC ;) + "no-multiple-empty-lines": ["error", {"max": 1}], // two or more empty lines need to be fused to one + "no-new-wrappers": "error", // there is no reason to wrap primitve values + "no-throw-literal": "error", // only throw Error but no objects {} + "no-trailing-spaces": "error", // trim end of lines + "no-unsafe-finally": "error", // safe try/catch/finally behavior + "no-var": "error", // use const and let instead of var + "space-before-function-paren": ["error", { // space in function decl: f() vs async () => {} + "anonymous": "never", + "asyncArrow": "always", + "named": "never" + }], + "semi": [2, "always"], // Always use semicolons at end of statement + "quotes": [2, "single", { "avoidEscape": true }], // Prefer single quotes + "use-isnan": "error", // isNaN(i) Number.isNaN(i) instead of i === NaN + "header/header": [ // Use MIT/Generated file header + 2, + "block", + { "pattern": "MIT License|DO NOT EDIT MANUALLY!" } + ], + // List of [@typescript-eslint rules](https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin#supported-rules) + "@typescript-eslint/adjacent-overload-signatures": "error", // grouping same method names + "@typescript-eslint/array-type": ["error", { // string[] instead of Array + "default": "array-simple" + }], + "@typescript-eslint/ban-types": "error", // bans types like String in favor of string + "@typescript-eslint/no-inferrable-types": "off", // don't blame decls like "index: number = 0", esp. in api signatures! + "@typescript-eslint/indent": "error", // consistent indentation + "@typescript-eslint/no-explicit-any": "error", // don't use :any type + "@typescript-eslint/no-misused-new": "error", // no constructors for interfaces or new for classes + "@typescript-eslint/no-namespace": "off", // disallow the use of custom TypeScript modules and namespaces + "@typescript-eslint/no-non-null-assertion": "off", // allow ! operator + "@typescript-eslint/no-parameter-properties": "error", // no property definitions in class constructors + "@typescript-eslint/no-unused-vars": ["error", { // disallow Unused Variables + "argsIgnorePattern": "^_" + }], + "@typescript-eslint/no-var-requires": "error", // use import instead of require + "@typescript-eslint/prefer-for-of": "error", // prefer for-of loop over arrays + "@typescript-eslint/prefer-namespace-keyword": "error", // prefer namespace over module in TypeScript + "@typescript-eslint/triple-slash-reference": "error", // ban /// , prefer imports + "@typescript-eslint/type-annotation-spacing": "error" // consistent space around colon ':' + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..6bad074 --- /dev/null +++ b/package.json @@ -0,0 +1,39 @@ +{ + "name": "typir-workspace", + "private": true, + "engineStrict": true, + "engines": { + "npm": ">= 7.7.0" + }, + "scripts": { + "clean": "shx rm -rf packages/**/lib packages/**/out packages/**/*.tsbuildinfo", + "build": "tsc -b tsconfig.build.json", + "watch": "tsc -b tsconfig.build.json -w", + "build:clean": "npm run clean && npm run build", + "lint": "npm run lint --workspaces", + "test": "vitest", + "test-ui": "vitest --ui", + "coverage": "vitest run --coverage" + }, + "author": "TypeFox GmbH", + "license": "MIT", + "devDependencies": { + "@types/node": "~16.18.11", + "@types/vscode": "~1.67.0", + "@typescript-eslint/eslint-plugin": "^5.51.0", + "@typescript-eslint/parser": "^5.51.0", + "@vitest/coverage-c8": "~0.28.4", + "@vitest/ui": "~0.28.4", + "concurrently": "^7.6.0", + "eslint": "^8.33.0", + "eslint-plugin-header": "^3.1.1", + "editorconfig": "~1.0.2", + "shx": "^0.3.4", + "typescript": "~4.9.5", + "vitest": "~0.28.4" + }, + "workspaces": [ + "packages/*", + "examples/*" + ] +} diff --git a/packages/typir/package.json b/packages/typir/package.json new file mode 100644 index 0000000..1f11872 --- /dev/null +++ b/packages/typir/package.json @@ -0,0 +1,42 @@ +{ + "name": "typir", + "version": "0.0.1", + "description": "General purpose type checking library", + "homepage": "https://langium.org", + "engines": { + "node": ">=14.0.0" + }, + "keywords": [ + "typesystem", + "typescript" + ], + "license": "MIT", + "files": [ + "lib", + "src" + ], + "main": "lib/index.js", + "types": "lib/index.d.ts", + "scripts": { + "clean": "shx rm -rf lib coverage", + "build": "tsc", + "watch": "tsc --watch", + "lint": "eslint src test --ext .ts", + "publish:next": "npm --no-git-tag-version version \"$(semver $npm_package_version -i minor)-next.$(git rev-parse --short HEAD)\" && npm publish --tag next", + "publish:latest": "npm publish --tag latest" + }, + "volta": { + "node": "16.19.0", + "npm": "8.19.3" + }, + "repository": { + "type": "git", + "url": "https://github.com/langium/langium", + "directory": "packages/langium" + }, + "bugs": "https://github.com/langium/langium/issues", + "author": { + "name": "TypeFox", + "url": "https://www.typefox.io" + } +} diff --git a/packages/typir/src/assignablity.ts b/packages/typir/src/assignablity.ts new file mode 100644 index 0000000..e6aea71 --- /dev/null +++ b/packages/typir/src/assignablity.ts @@ -0,0 +1,16 @@ +import { Type } from "./base"; + +export interface AssignabilityResult { + /** + * The failure of this result. If `undefined`, the assignability check succeeded. + */ + readonly failure?: AssignabilityFailure; +} + +export interface AssignabilityFailure { + from: string; + to: string; + nested?: AssignabilityFailure; +} + +export type AssignabilityCallback, To extends Type = From> = (types: { from: From, to: To }) => AssignabilityResult; \ No newline at end of file diff --git a/packages/typir/src/base.ts b/packages/typir/src/base.ts new file mode 100644 index 0000000..4392d56 --- /dev/null +++ b/packages/typir/src/base.ts @@ -0,0 +1,22 @@ +import type { TypeSystem } from "./type-system"; +import { Disposable } from "./utils"; + +export interface Type { + readonly literal?: T; + readonly members: Iterable>; + /** + * A reference to the original type system that produced this type + */ + readonly typeSystem: TypeSystem; +} + +export interface TypeMember { + name?: string; + literal?: T; + optional: boolean; + type: Type; +} + +export interface MemberCollection extends Iterable> { + push(...member: TypeMember[]): Disposable; +} diff --git a/packages/typir/src/function-type.ts b/packages/typir/src/function-type.ts new file mode 100644 index 0000000..8962406 --- /dev/null +++ b/packages/typir/src/function-type.ts @@ -0,0 +1,29 @@ +import { MemberCollection, Type, TypeMember } from "./base"; +import { TypeParameter } from "./type-parameter"; + +export interface FunctionType extends Type { + readonly name?: string; + readonly members: MemberCollection; + readonly typeParameters: TypeParameter[]; + readonly typeArguments: Type[]; + applyTypeArguments(args: Type[]): FunctionType; + readonly parameters: FunctionParameter; + readonly returnType: Type[]; +} + +export interface FunctionTypeOptions { + name?: string; + literal?: T; + members?: TypeMember[]; + typeParameters?: TypeParameter[]; + parameters?: FunctionParameter[]; + returnType?: Type[]; +} + +export interface FunctionParameter { + readonly name?: string + readonly literal?: T; + readonly type: Type; + readonly optional: boolean; + readonly spread: boolean; +} diff --git a/packages/typir/src/index.ts b/packages/typir/src/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/packages/typir/src/indexer.ts b/packages/typir/src/indexer.ts new file mode 100644 index 0000000..26bb80b --- /dev/null +++ b/packages/typir/src/indexer.ts @@ -0,0 +1,13 @@ +import { Type } from "./base"; + +export interface Indexer extends Type { + readonly: boolean; + writeonly: boolean; + parameters: IndexerParameter[]; +} + +export interface IndexerParameter { + readonly name?: string + readonly literal?: T; + readonly type: Type; +} \ No newline at end of file diff --git a/packages/typir/src/is.ts b/packages/typir/src/is.ts new file mode 100644 index 0000000..b707fba --- /dev/null +++ b/packages/typir/src/is.ts @@ -0,0 +1,15 @@ +import { PrimitiveType } from "./primitive"; + +export const Primitive = Symbol('Primitive'); + +export function isPrimitiveType(type: unknown): type is PrimitiveType { + return isType(type, Primitive); +} + +function isType(type: unknown, symbol: symbol): boolean { + if (typeof type !== 'object' || !type) { + return false; + } + const value = type as { '_type': symbol }; + return value._type === symbol; +} diff --git a/packages/typir/src/primitive.ts b/packages/typir/src/primitive.ts new file mode 100644 index 0000000..8e430bc --- /dev/null +++ b/packages/typir/src/primitive.ts @@ -0,0 +1,26 @@ +import { AssignabilityCallback } from "./assignablity"; +import { MemberCollection, Type, TypeMember } from "./base"; +import { Disposable } from "./utils"; + +export interface PrimitiveType extends Type { + readonly name: string + readonly members: MemberCollection; + constant(options: PrimitiveTypeConstantOptions): PrimitiveTypeConstant + assignable(to: PrimitiveType): Disposable; + assignable(callback: AssignabilityCallback>): Disposable; +} + +export interface PrimitiveTypeConstant extends Type { + type: PrimitiveType + value: unknown; +} + +export interface PrimitiveTypeConstantOptions { + value: unknown; + literal?: T; +} + +export type PrimitiveTypeOptions = string | { + name: string + members: TypeMember[] +} diff --git a/packages/typir/src/tuple-type.ts b/packages/typir/src/tuple-type.ts new file mode 100644 index 0000000..0ab2fef --- /dev/null +++ b/packages/typir/src/tuple-type.ts @@ -0,0 +1,15 @@ +import { Type } from "./base"; + +export interface TupleType extends Type { + types: Type[]; + /** + * Indicates that the last type in this tuple is spread. + */ + spread: boolean; +} + +export interface TupleTypeOptions { + literal?: T; + types: Type[]; + spread?: boolean; +} \ No newline at end of file diff --git a/packages/typir/src/type-category.ts b/packages/typir/src/type-category.ts new file mode 100644 index 0000000..b59b062 --- /dev/null +++ b/packages/typir/src/type-category.ts @@ -0,0 +1,37 @@ +import { AssignabilityCallback } from "./assignablity"; +import { MemberCollection, Type, TypeMember } from "./base"; +import { TypeParameter } from "./type-parameter"; +import type { TypeSystem } from "./type-system"; +import { Disposable } from "./utils"; + +export interface TypeCategory { + readonly name: string; + readonly typeSystem: TypeSystem; + create(options: TypeCategoryInstanceOptions): TypeCategoryInstance; + assignable(to: TypeCategory, callback: AssignabilityCallback>): Disposable; + castable(to: TypeCategory, callback: AssignabilityCallback>): Disposable; +} + +export interface TypeCategoryInstance extends Type, Disposable { + readonly name?: string; + readonly category: TypeCategory; + readonly members: MemberCollection; + readonly super: TypeCategoryInstance[]; + readonly typeParameters: TypeParameter[]; + readonly typeArguments: Type[]; + applyTypeArguments(args: Type[]): TypeCategoryInstance; + assignable(callback: AssignabilityCallback>): Disposable; + castable(callback: AssignabilityCallback>): Disposable; +} + +export interface TypeCategoryOptions { + name: string +} + +export interface TypeCategoryInstanceOptions { + name?: string + literal?: T + parameters?: TypeParameter[]; + members?: TypeMember[]; + typeParameters: TypeParameter[]; +} diff --git a/packages/typir/src/type-function.ts b/packages/typir/src/type-function.ts new file mode 100644 index 0000000..1f6196c --- /dev/null +++ b/packages/typir/src/type-function.ts @@ -0,0 +1,16 @@ +import { Type } from "./base"; +import { TypeParameter } from "./type-parameter"; + +export interface TypeFunction extends Type { + readonly name: string; + readonly parameters: TypeParameter[]; + readonly type: Type; + applyArguments(args: Type[]): Type; +} + +export interface TypeFunctionOptions { + readonly name: string; + readonly literal?: T; + readonly parameters?: TypeParameter[]; + readonly type: Type; +} diff --git a/packages/typir/src/type-intersection.ts b/packages/typir/src/type-intersection.ts new file mode 100644 index 0000000..35643d9 --- /dev/null +++ b/packages/typir/src/type-intersection.ts @@ -0,0 +1,10 @@ +import { Type } from "./base"; + +export interface TypeIntersection extends Type { + types: Type[]; +} + +export interface TypeIntersectionOptions { + literal?: T; + types: Type[]; +} \ No newline at end of file diff --git a/packages/typir/src/type-parameter.ts b/packages/typir/src/type-parameter.ts new file mode 100644 index 0000000..fb075a5 --- /dev/null +++ b/packages/typir/src/type-parameter.ts @@ -0,0 +1,32 @@ +import { Type } from "./base"; + +export interface TypeParameter extends Type { + name: string; + variance: TypeParameterVariance; + default?: Type; + constraints: Type[]; +} + +export interface TypeParameterOptions { + name: string; + variance?: TypeParameterVariance; + default?: Type; + literal?: T + constraints?: Type[]; +} + +/** + * The different type parameter variance modes. + * + * See [here](https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)) for an in-depth explanation. + */ +export enum TypeParameterVariance { + Invariance = 0, + Covariance = 1, + Contravariance = 2, + /** + * This value represents that the type parameter is both covariant and contravariant. + * It can be set using a bitwise operation on `Covariance & Contravariance` or accessing this enum field directly. + */ + Bivariance = 3 +} \ No newline at end of file diff --git a/packages/typir/src/type-system.ts b/packages/typir/src/type-system.ts new file mode 100644 index 0000000..6b2cbe0 --- /dev/null +++ b/packages/typir/src/type-system.ts @@ -0,0 +1,34 @@ +import { AssignabilityCallback, AssignabilityResult } from "./assignablity"; +import { Type } from "./base"; +import { FunctionType, FunctionTypeOptions } from "./function-type"; +import { PrimitiveType, PrimitiveTypeOptions } from "./primitive"; +import { TypeCategory, TypeCategoryOptions } from "./type-category"; +import { TypeFunction, TypeFunctionOptions } from "./type-function"; +import { TypeIntersection, TypeIntersectionOptions } from "./type-intersection"; +import { TypeParameter, TypeParameterOptions } from "./type-parameter"; +import { TypeUnion, TypeUnionOptions } from "./type-union"; +import { Disposable } from "./utils"; + +export function createTypeSystem(): TypeSystem { + throw new Error('Not implemented'); +} + +export interface TypeSystem { + isAssignable(from: Type, to: Type): AssignabilityResult; + isCastable(from: Type, to: Type): AssignabilityResult; + assignable(callback: AssignabilityCallback>): Disposable; + castable(callback: AssignabilityCallback>): Disposable; + + primitive(options: PrimitiveTypeOptions): PrimitiveType; + category(options: TypeCategoryOptions): TypeCategory; + function(options: FunctionTypeOptions): FunctionType; + typeFunction(options: TypeFunctionOptions): TypeFunction; + typeParameter(options: TypeParameterOptions): TypeParameter; + typeUnion(options: TypeUnionOptions): TypeUnion; + typeIntersection(options: TypeIntersectionOptions): TypeIntersection; + customType, Options = never>(factory: CustomTypeFactory, options?: Options): OutType; + + optionalType(type: Type): Type; +} + +export type CustomTypeFactory = (typeSystem: TypeSystem, options: Options) => OutType; \ No newline at end of file diff --git a/packages/typir/src/type-union.ts b/packages/typir/src/type-union.ts new file mode 100644 index 0000000..f88a5eb --- /dev/null +++ b/packages/typir/src/type-union.ts @@ -0,0 +1,10 @@ +import { Type } from "./base"; + +export interface TypeUnion extends Type { + types: Type[]; +} + +export interface TypeUnionOptions { + literal?: T; + types: Type[]; +} \ No newline at end of file diff --git a/packages/typir/src/utils.ts b/packages/typir/src/utils.ts new file mode 100644 index 0000000..de44f5c --- /dev/null +++ b/packages/typir/src/utils.ts @@ -0,0 +1,3 @@ +export interface Disposable { + dispose(): void; +} \ No newline at end of file diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 0000000..af3dcdb --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,21 @@ +{ + "extends": "./tsconfig.json", + "files": [], + "references": [ + { "path": "packages/langium" }, + { "path": "packages/langium/tsconfig.test.json" }, + { "path": "packages/langium-cli" }, + { "path": "packages/langium-cli/tsconfig.test.json" }, + { "path": "packages/langium-sprotty" }, + { "path": "packages/generator-langium" }, + { "path": "packages/generator-langium/tsconfig.test.json" }, + { "path": "examples/arithmetics" }, + { "path": "examples/arithmetics/tsconfig.test.json" }, + { "path": "examples/domainmodel" }, + { "path": "examples/domainmodel/tsconfig.test.json" }, + { "path": "examples/requirements" }, + { "path": "examples/requirements/tsconfig.test.json" }, + { "path": "examples/statemachine" }, + { "path": "examples/statemachine/tsconfig.test.json" } + ] + } diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..cda6dcd --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,80 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + + /* Basic Options */ + // "incremental": true, /* Enable incremental compilation */ + "target": "ES2017", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ + "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ + "lib": ["ESNext"], /* Specify library files to be included in the compilation. */ + // "allowJs": true, /* Allow javascript files to be compiled. */ + // "checkJs": true, /* Report errors in .js files. */ + // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ + "declaration": true, /* Generates corresponding '.d.ts' file. */ + "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ + "sourceMap": true, /* Generates corresponding '.map' file. */ + // "outFile": "./", /* Concatenate and emit output to single file. */ + "outDir": "lib", /* Redirect output structure to the directory. */ + // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + "composite": true, /* Enable project compilation */ + // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ + // "removeComments": true, /* Do not emit comments to output. */ + // "noEmit": true, /* Do not emit outputs. */ + // "importHelpers": true, /* Import emit helpers from 'tslib'. */ + "downlevelIteration": false, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + + /* Strict Type-Checking Options */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* Enable strict null checks. */ + // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ + "strictPropertyInitialization": false, /* Enable strict checking of property initialization in classes. */ + // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ + + /* Additional Checks */ + "noUnusedLocals": true, /* Report errors on unused locals. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + "noImplicitOverride": true, /* Report error when a method is overridden without the `override` keyword. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ + + /* Module Resolution Options */ + "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ + // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ + // "typeRoots": [], /* List of folders to include type definitions from. */ + "types": [ + "node" + ], /* Type declaration files to be included in compilation. */ + // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + + /* Source Map Options */ + // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ + // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ + + /* Experimental Options */ + // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ + // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + + /* Advanced Options */ + "skipLibCheck": true, /* Skip type checking of declaration files. */ + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "lib", + "node_modules" + ] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..feaea84 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,21 @@ +/****************************************************************************** + * Copyright 2021 TypeFox GmbH + * This program and the accompanying materials are made available under the + * terms of the MIT License, which is available in the project root. + ******************************************************************************/ + +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + coverage: { + provider: 'c8', + reporter: ['text', 'html'], + include: ['packages/typir/src'], + exclude: ['**/generated'], + }, + deps: { + interopDefault: true + } + } +}); From e099bcb967c04a18f8eeacc4904b39bf7f545e28 Mon Sep 17 00:00:00 2001 From: Benjamin Friedman Wilson Date: Tue, 5 Mar 2024 15:00:28 +0100 Subject: [PATCH 2/3] ben's rough proposal --- proposal.ts | 192 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 proposal.ts diff --git a/proposal.ts b/proposal.ts new file mode 100644 index 0000000..9bcdb92 --- /dev/null +++ b/proposal.ts @@ -0,0 +1,192 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable header/header */ +/** + * High level typir spec + */ + +type TypirOptions = { + enableTypeInference?: boolean; + enableTypeChecking?: boolean; + enableTypeRelationships?: boolean; + enableRuleJudgments?: boolean; +}; + +// Define the base visitor interface +interface TypirVisitor { + visit(obj: any): void; +} + +class Typir { + private name: string; + private options: TypirOptions; + + public typeGraph: Map>; + + constructor(name: string, options: TypirOptions = {}) { + this.name = name; + this.options = { + enableTypeInference: true, + enableTypeChecking: true, + enableTypeRelationships: true, + enableRuleJudgments: true, + ...options, + }; + + this.typeGraph = new Map(); + } + + // Other methods and functionalities can be added here + // Method to load a type into the environment + loadType(typeName: string): any { + // Logic to load type based on options + console.log(`Loading type '${typeName}' into environment of ${this.name}`); + return { typeName }; // Placeholder for the actual type + } + + // Method to load a symbol with type binding into the environment + loadSymbol(symbolName: string, type: any): any { + // Logic to load symbol with type binding based on options + console.log(`Loading symbol '${symbolName}' with type binding into environment of ${this.name}`); + return { symbolName, type }; // Placeholder for the actual symbol + } + + // Method to define a type relationship + defineTypeRelationship(type: any, relationship: string): void { + // Logic to define type relationship based on options + console.log(`Defining relationship '${relationship}' for type '${type.typeName}' in environment of ${this.name}`); + } + + // Method to define a rule judgment for type inference + defineRuleJudgment(relationship: string, rule: (e1: any, e2: any) => any): void { + // Logic to define rule judgment based on options + console.log(`Defining rule judgment '${relationship}' in environment of ${this.name}`); + } + + // Method to add subtyping relationship between two types + addSubType(parentType: any, childType: any): void { + if (!this.typeGraph.has(parentType)) { + this.typeGraph.set(parentType, new Set()); + } + + this.typeGraph.get(parentType)!.add(childType); + console.log(`Added subtyping relationship: ${parentType.typeName} is assignable to ${childType.typeName}`); + } + + // Method to check if a type is assignable to another type + isAssignable(fromType: any, toType: any): boolean { + if (!this.typeGraph.has(toType)) { + return false; + } + + const reachableTypes = this.typeGraph.get(toType)!; + return reachableTypes.has(fromType); + } + + inferType(expression: any): any { + // Logic to infer type based on options + console.log(`Inferring type for expression in environment of ${this.name}`); + return { typeName: 'InferredType' }; // Placeholder for the inferred type + } + + // Accept a visitor and apply it to the given object + // performs typechecking, type inference, etc. + typeCheck(visitor: TypirVisitor, obj: any): void { + visitor.visit(obj); + } + + // Get the constructed type graph + getTypeGraph(): Map> { + return this.typeGraph; + } +} + + + + +type AstType = { + $type: 'model'; + left: AstType | number; + op: string; + right: AstType | number; +} | { + $type: 'lit'; + value: number; +}; + +// Example concrete visitor implementing TypirVisitor +class ExampleASTVisitor implements TypirVisitor { + private typirInstance: Typir; + + constructor(typirInstance: Typir) { + this.typirInstance = typirInstance; + } + + visit(obj: AstType): void { + // traverse this aST object, and do something with it + // leads to side-effects in the typir instance + // such as populating the graph, defining relationships, etc. + // allows defining types that correspond to the AST node types, whatever they may be + } +} + +///////// Example Usage + +const typirInstance = new Typir('TS-1', { + enableTypeInference: true, + enableTypeChecking: true, + enableTypeRelationships: true, + enableRuleJudgments: true, +}); + +// +// injecting types into the environment +// +const intType = typirInstance.loadType('Int'); +const boolType = typirInstance.loadType('Bool'); + +const animalType = typirInstance.loadType('Animal'); +const dogType = typirInstance.loadType('Dog'); +const catType = typirInstance.loadType('Cat'); + +// +// binding symbols with types in the environment +// +const xSymbol = typirInstance.loadSymbol('x', intType); +const ySymbol = typirInstance.loadSymbol('y', boolType); + +// +// setting relationships between types +// this actions should update the type graph, or any other internal representation of the environment +// +typirInstance.addSubType(animalType, dogType); +typirInstance.addSubType(animalType, catType); + +// assignability checks after the sub-type relationship above +const isAssignable1 = typirInstance.isAssignable(dogType, animalType); +console.log(`Is Dog assignable to Animal? ${isAssignable1}`); // true + +const isAssignable2 = typirInstance.isAssignable(catType, dogType); +console.log(`Is Cat assignable to Dog? ${isAssignable2}`); // false + +// some ast +const myAst: AstType = { + $type: 'model', + left: { + $type: 'lit', + value: 5 + }, + op: '+', + right: { + $type: 'lit', + value: 10 + } +}; + +// inject the visitor into the typir instance, and get the visitor to traverse the AST +// result is that the typir instance is updated with the relationships and types defined in the visitor +// and then we can use the typir instance to do type inference, type checking, etc. +const visitor = new ExampleASTVisitor(typirInstance); +typirInstance.typeCheck(visitor, myAst); + +// check if the myAst is correct From c9ec2dcd31d212e920c814748d1c678c87266abb Mon Sep 17 00:00:00 2001 From: Benjamin Friedman Wilson Date: Tue, 5 Mar 2024 16:27:43 +0100 Subject: [PATCH 3/3] small tweaks --- proposal.ts | 50 +++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/proposal.ts b/proposal.ts index 9bcdb92..2cde894 100644 --- a/proposal.ts +++ b/proposal.ts @@ -51,6 +51,8 @@ class Typir { return { symbolName, type }; // Placeholder for the actual symbol } + // helpers..... + // Method to define a type relationship defineTypeRelationship(type: any, relationship: string): void { // Logic to define type relationship based on options @@ -89,6 +91,11 @@ class Typir { return { typeName: 'InferredType' }; // Placeholder for the inferred type } + + + + + // Accept a visitor and apply it to the given object // performs typechecking, type inference, etc. typeCheck(visitor: TypirVisitor, obj: any): void { @@ -101,9 +108,7 @@ class Typir { } } - - - +// Say we have some AST type, could be Langium or another... type AstType = { $type: 'model'; left: AstType | number; @@ -115,7 +120,9 @@ type AstType = { }; // Example concrete visitor implementing TypirVisitor +// Which accepts an AST object and updates the typir instance w/ types, relationships, etc. class ExampleASTVisitor implements TypirVisitor { + private typirInstance: Typir; constructor(typirInstance: Typir) { @@ -123,18 +130,47 @@ class ExampleASTVisitor implements TypirVisitor { } visit(obj: AstType): void { + switch (obj.$type) { + case 'model': + this.visitModel(obj); + break; + case 'lit': + this.visitLit(obj); + break; + } // traverse this aST object, and do something with it // leads to side-effects in the typir instance // such as populating the graph, defining relationships, etc. // allows defining types that correspond to the AST node types, whatever they may be } + + visitModel(obj: AstType & { $type: 'model' }): void { + // do something with the model node + // & update the typir instance + } + + visitLit(obj: AstType & { $type: 'lit' }): void { + // do something with the lit node + // & update the typir instance + } + + visitBinaryExpr(e1, op, e2) { + // e1 -> T1 + // e2 -> T2 + // + // + // e = e1 op e2 e1 : T1, e2 : T2 + // ------------------------------------ T1 = T2 + // e : T1 + // + // T1 = T2 + } } ///////// Example Usage -const typirInstance = new Typir('TS-1', { - enableTypeInference: true, - enableTypeChecking: true, +const typirInstance = new Typir('TypeFox-1', { + // ... various config options enableTypeRelationships: true, enableRuleJudgments: true, }); @@ -187,6 +223,6 @@ const myAst: AstType = { // result is that the typir instance is updated with the relationships and types defined in the visitor // and then we can use the typir instance to do type inference, type checking, etc. const visitor = new ExampleASTVisitor(typirInstance); -typirInstance.typeCheck(visitor, myAst); // check if the myAst is correct +typirInstance.typeCheck(visitor, myAst);