Skip to content

Commit

Permalink
Various improvements for v0.1 (#46)
Browse files Browse the repository at this point in the history
- functions and operators can be annotated with validations now
- Typir module:
    - reorganized the grouping of services
    - don't merge Langium+Typir services, but provide the Typir services via an "typir"-property/service within the Langium services
    - override only selected services of a group
    - extracted missing service interface
- extracted logic to resolve TypeSelectors into its own service to make the implementation exchangable
- the module now looks into the kind registry, whether the kind is already initialized, before a new one is created (this partially fulfills #44)
- removed dependencies to 'langium/lsp' from Typir-Langium in order to use it also in non-LSP projects
- switched to Langium v3.3, simplified API, removed duplicated code for a fix which is part of Langium 3.3 now
- added missing READMEs, ChangeLog, added content, added EclipseCon slides
- associate domain elements with Typir types
- implemented overloaded functions and methods in LOX by using Typir already during Linking!
- reduced code for the "Tiny Typir" example a bit
  • Loading branch information
JohannesMeierSE authored Dec 19, 2024
1 parent cfbe123 commit b1cb1fb
Show file tree
Hide file tree
Showing 76 changed files with 1,831 additions and 1,571 deletions.
30 changes: 30 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Typir Change Log

We roughly follow the ideas of [semantic versioning](https://semver.org/).
Note that the versions "0.x.0" probably will include breaking changes.


## v0.1.0 (December 2024)

This is the first official release of Typir.
It serves as first version to experiment with Typir and to gather feedback to guide and improve the upcoming versions. We are looking forward to your feedback!

- [Linked issues and PRs](https://github.com/TypeFox/typir/milestone/2)
- Core implementations of the following [type-checking services](/packages/typir/src/services/):
- Assignability
- Equality
- Conversion (implicit/coercion and explicit/casting)
- Type inference
- Sub-typing
- Validation
- Caching
- [Predefined types](/packages/typir/src/kinds/) to reuse:
- Primitives
- Functions (with overloading)
- Classes (nominally typed)
- Top, bottom
- (some more are under development)
- Operators (which are mapped to Functions, with overloading)
- Application examples:
- LOX (without lambdas)
- OX
17 changes: 17 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
# Contributing

TODO


## Communication

The following communication channels are available:

- [GitHub issues](https://github.com/TypeFox/typir/issues) - for bug reports, feature requests, etc.
- [GitHub discussions](https://github.com/TypeFox/typir/discussions) - for questions, ideas, announcements, etc.
- [Weekly Langium dev meeting](https://github.com/eclipse-langium/langium/discussions/564?sort=new) - While Typir is independent from Langium in general, you might meet some Typir developers at the Langium dev meetings.

In case you have a question, please look into the provided resources and documentations first.
If you don't find any answer there, feel free to use the discussions to get help.


## Release Process

The release process for Typir is described in [RELEASE.md](./RELEASE.md).
78 changes: 74 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,90 @@
# Typir

Engineering types for software languages in the web.
<div id="badges" align="center">

[![npm](https://img.shields.io/npm/v/typir)](https://www.npmjs.com/package/typir)
[![Build](https://github.com/TypeFox/typir/actions/workflows/actions.yml/badge.svg)](https://github.com/TypeFox/typir/actions/workflows/actions.yml)
[![Github Discussions](https://img.shields.io/badge/github-discussions-blue?logo=github)](https://github.com/TypeFox/typir/discussions)
[![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-ready--to--code-FFAE33?logo=gitpod)](https://gitpod.io/#https://github.com/TypeFox/typir)

</div>

---

Typir is a library for type systems and type checking for software languages in the web.

Typir is OpenSource, written in TypeScript, and follows pragmatic approaches for easing type checking in practical language engineering projects by providing default implementations for recurring problems.
As a stand-alone library, Typir provides a TypeScript-API for language engineers without an additional, external DSL for formalizing types.


## Core Features

Typir provides these core features:

- Predefined types:
- primitives
- functions (with overloading)
- classes
- top, bottom
- (more are planned)
- Solutions for: circular type definitions, caching, operators
- Meaningful and customizable error messages
- The provided default implementations are customizable by dependency injection

Typir does intentionally _not_ include ...

- rules engines and constraint solving
- formal proofs
- external DSLs for formalizing types


## NPM workspace

This repository is a NPM workspace. It contains the following packages:

- [Typir](./packages/typir/README.md) - the core package of Typir
- [Typir-Langium](./packages/typir-langium/README.md) - a integration of Typir for [Langium](https://github.com/eclipse-langium/langium)
- [Typir](./packages/typir/README.md) - the core package of Typir with default implementations for type checking services and some predefined types
- [Typir-Langium](./packages/typir-langium/README.md) - a binding of Typir for [Langium](https://github.com/eclipse-langium/langium), a language workbench for developing textual DSLs in the web,
in order to ease type checking for Langium-based languages

This repository contains the following stand-alone applications, which demonstrate how to use Typir for type checking:

- [LOX](./examples/lox/README.md) - static type checking for LOX, implemented with Typir-Langium
- [OX](./examples/ox/README.md) - a reduced version of LOX, implemented with Typir-Langium


## Tiny Typir Example

[TODO](/packages/typir/test/api-example.test.ts)


## Resources

Typir is presented in these talks:

- [LangDev'24](https://langdevcon.org/2024/program#26): [Video](https://www.youtube.com/watch?v=CL8EbJYeyTE), [slides](/resources/talks/2024-10-17-LangDev.pdf) (2024-10-17)
- [OCX/EclipseCon'24](https://www.ocxconf.org/event/778b82cc-6834-48a4-a58e-f883c5a7b8c9/agenda?session=23b97df9-0435-4fab-8a01-e0a9cf3e3831&shareLink=true): [Video](https://www.youtube.com/watch?v=WLzXAhcl-aY&list=PLy7t4z5SYNaRRGVdF83feN-_uHLwvGvgw&index=23), [slides](/resources/talks/2024-10-24-EclipseCon.pdf) (2024-10-24)


## Roadmap

The roadmap of Typir is organized with [milestones in GitHub](https://github.com/TypeFox/typir/milestones).

The roadmap include, among other, these features:

- More predefined types: structurally typed classes, lambdas, generics, constrained primitive types (e.g. numbers with upper and lower bound), ...
- Calculate types, e.g. operators whose return types depend on their current input types
- Optimized APIs to register rules for inference and validation

For the released versions of Typir, see the [CHANGELOG.md](/CHANGELOG.md).


## Contributing

Please read the [CONTRIBUTING.md](./CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to us.

We also have a release process described in [RELEASE.md](./RELEASE.md).


## License

[MIT License](/LICENSE)
Typir is fully [MIT licensed](/LICENSE).
17 changes: 17 additions & 0 deletions examples/lox/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Typir applied to LOX

This package contains an adapted version of [LOX](https://craftinginterpreters.com/the-lox-language.html), [realized with Langium](https://github.com/TypeFox/langium-lox) and statically type-checked with [Typir](https://typir.org/).

Typir is used here to make LOX a statically typed language:

- Variables have one type, which is either explicitly declared (e.g. `var v1: string`) or derived from the initial value (e.g. `var v2: 2 <= 3`).
- Lox supports these types here:
- primitives: boolean, string, number, void
- Classes (nominally typed)
- Lambdas (not yet supported)
- We keep `nil`, but it can be assigned only to variables with a class or lambda as type.
Variables with primitive type and without explicit initial value have the primitive types default value.

For examples written in LOX, look at some [collected examples](./examples/) or the [test cases](./test/).

To compare the current implementation for type checking with Typir with an implementation without Typir, have a look into [this repository](https://github.com/TypeFox/langium-lox/tree/main/langium/src/language-server/type-system).
28 changes: 0 additions & 28 deletions examples/lox/examples/basic.lox
Original file line number Diff line number Diff line change
Expand Up @@ -88,34 +88,6 @@ fun returnSum(a: number, b: number): number {
return a + b;
}

// Closures

fun identity(a: (number, number) => number): (number, number) => number {
return a;
}

print identity(returnSum)(1, 2); // prints "3";

fun outerFunction(): void {
fun localFunction(): void {
print "I'm local!";
}
localFunction();
}

fun returnFunction(): () => void {
var outside = "outside";

fun inner(): void {
print outside;
}

return inner;
}

var fn = returnFunction();
fn();

// Classes WIP

class SuperClass {
Expand Down
4 changes: 2 additions & 2 deletions examples/lox/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@
},
"dependencies": {
"commander": "~12.1.0",
"langium": "~3.2.0",
"langium": "~3.3.0",
"typir-langium": "~0.0.2",
"vscode-languageclient": "~9.0.1",
"vscode-languageserver": "~9.0.1"
},
"devDependencies": {
"@types/vscode": "~1.94.0",
"langium-cli": "~3.2.0"
"langium-cli": "~3.3.0"
},
"files": [
"bin",
Expand Down
63 changes: 63 additions & 0 deletions examples/lox/src/language/lox-linker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/******************************************************************************
* Copyright 2024 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 { AstNodeDescription, DefaultLinker, LinkingError, ReferenceInfo } from 'langium';
import { isType } from '../../../../packages/typir/lib/graph/type-node.js';
import { TypirServices } from '../../../../packages/typir/lib/typir.js';
import { isClass, isFunctionDeclaration, isMemberCall, isMethodMember } from './generated/ast.js';
import { LoxServices } from './lox-module.js';

export class LoxLinker extends DefaultLinker {
protected readonly typir: TypirServices;

constructor(services: LoxServices) {
super(services);
this.typir = services.typir;
}

override getCandidate(refInfo: ReferenceInfo): AstNodeDescription | LinkingError {
const container = refInfo.container;
if (isMemberCall(container) && container.explicitOperationCall) {
// handle overloaded functions/methods
const scope = this.scopeProvider.getScope(refInfo);
const calledDescriptions = scope.getAllElements().filter(d => d.name === refInfo.reference.$refText).toArray(); // same name
if (calledDescriptions.length === 1) {
return calledDescriptions[0]; // no overloaded functions/methods
} if (calledDescriptions.length >= 2) {
// in case of overloaded functions/methods, do type inference for given arguments
const argumentTypes = container.arguments.map(arg => this.typir.Inference.inferType(arg)).filter(isType);
if (argumentTypes.length === container.arguments.length) { // for all given arguments, a type is inferred
for (const calledDescription of calledDescriptions) {
const called = this.loadAstNode(calledDescription);
if (isClass(called)) {
// special case: call of the constructur, without any arguments/parameters
return calledDescription; // there is only one constructor without any parameters
}
if ((isMethodMember(called) || isFunctionDeclaration(called)) && called.parameters.length === container.arguments.length) { // same number of arguments
// infer expected types of parameters
const parameterTypes = called.parameters.map(p => this.typir.Inference.inferType(p)).filter(isType);
if (parameterTypes.length === called.parameters.length) { // for all parameters, a type is inferred
if (argumentTypes.every((arg, index) => this.typir.Assignability.isAssignable(arg, parameterTypes[index]))) {
return calledDescription;
}
}
}
}
}
// no matching method is found, return the first found method => linking works + validation issues regarding the wrong parameter values can be shown!
return calledDescriptions[0];

// the following approach does not work, since the container's cross-references are required for type inference, but they are not yet resolved
// const type = this.typir.Inference.inferType(container);
// if (isFunctionType(type)) {
// return type.associatedDomainElement;
// }
}
return this.createLinkingError(refInfo);
}
return super.getCandidate(refInfo);
}
}
45 changes: 25 additions & 20 deletions examples/lox/src/language/lox-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,43 +4,53 @@
* terms of the MIT License, which is available in the project root.
******************************************************************************/

import { Module, PartialLangiumCoreServices, createDefaultCoreModule, inject } from 'langium';
import { LangiumSharedCoreServices, Module, PartialLangiumCoreServices, createDefaultCoreModule, inject } from 'langium';
import { DefaultSharedModuleContext, LangiumServices, LangiumSharedServices, createDefaultSharedModule } from 'langium/lsp';
import { LangiumServicesForTypirBinding, createLangiumModuleForTypirBinding, initializeLangiumTypirServices } from 'typir-langium';
import { LoxGeneratedModule, LoxGeneratedSharedModule } from './generated/module.js';
import { LoxScopeProvider } from './lox-scope.js';
import { LoxValidationRegistry, LoxValidator } from './lox-validator.js';
import { createLoxTypirModule } from './type-system/lox-type-checking.js';
import { createLoxTypirModule } from './lox-type-checking.js';
import { LoxLinker } from './lox-linker.js';

/**
* Declaration of custom services - add your own service classes here.
*/
export type LoxAddedServices = {
validation: {
LoxValidator: LoxValidator
}
},
typir: LangiumServicesForTypirBinding,
}

/**
* Union of Langium default services and your custom services - use this as constructor parameter
* of custom service classes.
*/
export type LoxServices = LangiumServices & LoxAddedServices & LangiumServicesForTypirBinding
export type LoxServices = LangiumServices & LoxAddedServices

/**
* Dependency injection module that overrides Langium default services and contributes the
* declared custom services. The Langium defaults can be partially specified to override only
* selected services, while the custom services must be fully specified.
*/
export const LoxModule: Module<LoxServices, PartialLangiumCoreServices & LoxAddedServices> = {
validation: {
ValidationRegistry: (services) => new LoxValidationRegistry(services),
LoxValidator: () => new LoxValidator()
},
references: {
ScopeProvider: (services) => new LoxScopeProvider(services)
}
};
export function createLoxModule(shared: LangiumSharedCoreServices): Module<LoxServices, PartialLangiumCoreServices & LoxAddedServices> {
return {
validation: {
ValidationRegistry: (services) => new LoxValidationRegistry(services),
LoxValidator: () => new LoxValidator()
},
// For type checking with Typir, inject and merge these modules:
typir: () => inject(Module.merge(
createLangiumModuleForTypirBinding(shared), // the Typir default services
createLoxTypirModule(shared), // custom Typir services for LOX
)),
references: {
ScopeProvider: (services) => new LoxScopeProvider(services),
Linker: (services) => new LoxLinker(services),
},
};
}

/**
* Create the full set of services required by Langium.
Expand All @@ -53,9 +63,6 @@ export const LoxModule: Module<LoxServices, PartialLangiumCoreServices & LoxAdde
* - Langium default language-specific services
* - Services generated by langium-cli
* - Services specified in this file
* For type checking with Typir, merge two more modules:
* - Typir default services
* - custom services for LOX
*
* @param context Optional module context with the LSP connection
* @returns An object wrapping the shared services and the language-specific services
Expand All @@ -71,11 +78,9 @@ export function createLoxServices(context: DefaultSharedModuleContext): {
const Lox = inject(
createDefaultCoreModule({ shared }),
LoxGeneratedModule,
LoxModule,
createLangiumModuleForTypirBinding(shared),
createLoxTypirModule(shared),
createLoxModule(shared),
);
shared.ServiceRegistry.register(Lox);
initializeLangiumTypirServices(Lox);
initializeLangiumTypirServices(Lox, Lox.typir);
return { shared, Lox };
}
Loading

0 comments on commit b1cb1fb

Please sign in to comment.