Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Ben's Proposal #3

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
root = true

[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
insert_final_newline = true
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.js
75 changes: 75 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -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<string>
"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 /// <reference />, prefer imports
"@typescript-eslint/type-annotation-spacing": "error" // consistent space around colon ':'
}
}
39 changes: 39 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -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/*"
]
}
42 changes: 42 additions & 0 deletions packages/typir/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
16 changes: 16 additions & 0 deletions packages/typir/src/assignablity.ts
Original file line number Diff line number Diff line change
@@ -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<From extends Type<unknown>, To extends Type<unknown> = From> = (types: { from: From, to: To }) => AssignabilityResult;
22 changes: 22 additions & 0 deletions packages/typir/src/base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { TypeSystem } from "./type-system";
import { Disposable } from "./utils";

export interface Type<T> {
readonly literal?: T;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is T and the literal? Would AstNode be a value for T in the context of Langium? If a type like string is the type for multiple literals, do I have multiple Type instances representing string, one for each literal?

readonly members: Iterable<TypeMember<T>>;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it really make sense to have members, even when lots of types don't have any members, e.g. all primitive types like int, string, ...? For functions, I would prefer to call them "parameters" instead of "members" ...

/**
* A reference to the original type system that produced this type
*/
readonly typeSystem: TypeSystem<T>;
}

export interface TypeMember<T> {
name?: string;
literal?: T;
optional: boolean;
type: Type<T>;
}

export interface MemberCollection<T> extends Iterable<TypeMember<T>> {
push(...member: TypeMember<T>[]): Disposable;
}
29 changes: 29 additions & 0 deletions packages/typir/src/function-type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { MemberCollection, Type, TypeMember } from "./base";
import { TypeParameter } from "./type-parameter";

export interface FunctionType<T> extends Type<T> {
readonly name?: string;
readonly members: MemberCollection<T>;
readonly typeParameters: TypeParameter<T>[];
readonly typeArguments: Type<T>[];
applyTypeArguments(args: Type<T>[]): FunctionType<T>;
readonly parameters: FunctionParameter<T>;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I am lost: What are members in contrast to parameters?

(What about FunctionParameter<T> extends TypeMember<T>?)

readonly returnType: Type<T>[];
}

export interface FunctionTypeOptions<T> {
name?: string;
literal?: T;
members?: TypeMember<T>[];
typeParameters?: TypeParameter<T>[];
parameters?: FunctionParameter<T>[];
returnType?: Type<T>[];
}

export interface FunctionParameter<T> {
readonly name?: string
readonly literal?: T;
readonly type: Type<T>;
readonly optional: boolean;
readonly spread: boolean;
}
Empty file added packages/typir/src/index.ts
Empty file.
13 changes: 13 additions & 0 deletions packages/typir/src/indexer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Type } from "./base";

export interface Indexer<T> extends Type<T> {
readonly: boolean;
writeonly: boolean;
parameters: IndexerParameter<T>[];
}

export interface IndexerParameter<T> {
readonly name?: string
readonly literal?: T;
readonly type: Type<T>;
}
15 changes: 15 additions & 0 deletions packages/typir/src/is.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { PrimitiveType } from "./primitive";

export const Primitive = Symbol('Primitive');

export function isPrimitiveType<T>(type: unknown): type is PrimitiveType<T> {
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;
}
26 changes: 26 additions & 0 deletions packages/typir/src/primitive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { AssignabilityCallback } from "./assignablity";
import { MemberCollection, Type, TypeMember } from "./base";
import { Disposable } from "./utils";

export interface PrimitiveType<T> extends Type<T> {
readonly name: string
readonly members: MemberCollection<T>;
constant(options: PrimitiveTypeConstantOptions<T>): PrimitiveTypeConstant<T>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In which scenario do we need constants for primitives?

Why don't we have constants/instances for types of other kinds like functions?

assignable(to: PrimitiveType<T>): Disposable;
assignable(callback: AssignabilityCallback<PrimitiveType<T>>): Disposable;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't we have assignable for function types?

}

export interface PrimitiveTypeConstant<T> extends Type<T> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did I understand correctly, that PrimitiveTypeConstant is an instance/element of the set defined by a PrimitiveType?

type: PrimitiveType<T>
value: unknown;
}

export interface PrimitiveTypeConstantOptions<T> {
value: unknown;
literal?: T;
}

export type PrimitiveTypeOptions<T> = string | {
name: string
members: TypeMember<T>[]
}
15 changes: 15 additions & 0 deletions packages/typir/src/tuple-type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Type } from "./base";

export interface TupleType<T> extends Type<T> {
types: Type<T>[];
/**
* Indicates that the last type in this tuple is spread.
*/
spread: boolean;
}

export interface TupleTypeOptions<T> {
literal?: T;
types: Type<T>[];
spread?: boolean;
}
37 changes: 37 additions & 0 deletions packages/typir/src/type-category.ts
Original file line number Diff line number Diff line change
@@ -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<T> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is a type category? Could you add some comments?

readonly name: string;
readonly typeSystem: TypeSystem<T>;
create(options: TypeCategoryInstanceOptions<T>): TypeCategoryInstance<T>;
assignable(to: TypeCategory<T>, callback: AssignabilityCallback<TypeCategoryInstance<T>>): Disposable;
castable(to: TypeCategory<T>, callback: AssignabilityCallback<TypeCategoryInstance<T>>): Disposable;
}

export interface TypeCategoryInstance<T> extends Type<T>, Disposable {
readonly name?: string;
readonly category: TypeCategory<T>;
readonly members: MemberCollection<T>;
readonly super: TypeCategoryInstance<T>[];
readonly typeParameters: TypeParameter<T>[];
readonly typeArguments: Type<T>[];
applyTypeArguments(args: Type<T>[]): TypeCategoryInstance<T>;
assignable(callback: AssignabilityCallback<Type<T>>): Disposable;
castable(callback: AssignabilityCallback<Type<T>>): Disposable;
}

export interface TypeCategoryOptions {
name: string
}

export interface TypeCategoryInstanceOptions<T> {
name?: string
literal?: T
parameters?: TypeParameter<T>[];
members?: TypeMember<T>[];
typeParameters: TypeParameter<T>[];
}
16 changes: 16 additions & 0 deletions packages/typir/src/type-function.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Type } from "./base";
import { TypeParameter } from "./type-parameter";

export interface TypeFunction<T> extends Type<T> {
readonly name: string;
readonly parameters: TypeParameter<T>[];
readonly type: Type<T>;
applyArguments(args: Type<T>[]): Type<T>;
}

export interface TypeFunctionOptions<T> {
readonly name: string;
readonly literal?: T;
readonly parameters?: TypeParameter<T>[];
readonly type: Type<T>;
}
10 changes: 10 additions & 0 deletions packages/typir/src/type-intersection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Type } from "./base";

export interface TypeIntersection<T> extends Type<T> {
types: Type<T>[];
}

export interface TypeIntersectionOptions<T> {
literal?: T;
types: Type<T>[];
}
Loading