Repository: elm-tooling/elm-language-server
Pull request #447 (2021-01-13 14:30:58)

Compiler api refactoring
Renames:
- `IElmWorkspace` -> `IProgram`
- `ITreeContainer` -> `ISourceFile`

Moves all compiler related code into `./src/compiler` that can be exported as a npm package. 

Here's a few things that will need to happen before we can ship this api:
- Remove all LSP specific stuff from these files. (`Program` uses `connection` a lot)
- Refine the api: What should be exposed, what should be internal, what should the language server do instead 
- Discuss tradeoffs of moving the compiler api to a separate package. (Can't use `/** @internal */` methods, but wouldn't have dependency on LSP)

~~The name "analyzer" is still up for discussion.~~ I have decided that using "compiler" is a better name. The API will probably be shipped through the `elm-language-server` package, so we can use "compiler" internally.

Razzeee: Code looks good, but I haven't reviewed all. It needs a rebase, and I'm not sold on the compiler naming, but if we want to change that it should be a follow up. 

Patch:
diff --git a/src/util/types/binder.ts b/src/compiler/binder.ts
similarity index 93%
rename from src/util/types/binder.ts
rename to src/compiler/binder.ts
index 31f03b9..4166947 100644
--- a/src/util/types/binder.ts
+++ b/src/compiler/binder.ts
@@ -1,9 +1,9 @@
-import { ITreeContainer } from "../../forest";
+import { ISourceFile } from "./forest";
 import { SyntaxNode } from "web-tree-sitter";
-import { MultiMap } from "../multiMap";
-import { IExposed, IExposing, NodeType, TreeUtils } from "../treeUtils";
-import { Utils } from "../utils";
-import { SyntaxNodeMap } from "./syntaxNodeMap";
+import { MultiMap } from "../util/multiMap";
+import { IExposed, IExposing, NodeType, TreeUtils } from "../util/treeUtils";
+import { Utils } from "../util/utils";
+import { SyntaxNodeMap } from "./utils/syntaxNodeMap";
 import { Diagnostics, error } from "./diagnostics";
 
 export type SymbolMap = MultiMap<string, ISymbol>;
@@ -17,8 +17,8 @@ export interface ISymbol {
   type: NodeType;
 }
 
-export function bindTreeContainer(treeContainer: ITreeContainer): void {
-  if (treeContainer.symbolLinks) {
+export function bindTreeContainer(sourceFile: ISourceFile): void {
+  if (sourceFile.symbolLinks) {
     return;
   }
 
@@ -26,11 +26,11 @@ export function bindTreeContainer(treeContainer: ITreeContainer): void {
   const nonShadowableNames = new Set<string>();
   let container: SymbolMap;
   let parent: SyntaxNode;
-  const treeCursor = treeContainer.tree.walk();
+  const treeCursor = sourceFile.tree.walk();
 
   bind();
-  treeContainer.symbolLinks = symbolLinks;
-  treeContainer.nonShadowableNames = nonShadowableNames;
+  sourceFile.symbolLinks = symbolLinks;
+  sourceFile.nonShadowableNames = nonShadowableNames;
 
   // Bind exposing must happen after symbolLinks is bound
   bindExposing();
@@ -84,7 +84,7 @@ export function bindTreeContainer(treeContainer: ITreeContainer): void {
         bindImportClause(node);
         break;
       case "ERROR":
-        treeContainer.parseDiagnostics.push(error(node, Diagnostics.Parsing));
+        sourceFile.parseDiagnostics.push(error(node, Diagnostics.Parsing));
         break;
       default:
         forEachChild(bind);
@@ -246,7 +246,7 @@ export function bindTreeContainer(treeContainer: ITreeContainer): void {
   }
 
   function bindExposing(): void {
-    const tree = treeContainer.tree;
+    const tree = sourceFile.tree;
     const exposed: IExposing = new Map<string, IExposed>();
     const moduleDeclaration = TreeUtils.findModuleDeclaration(tree);
     if (moduleDeclaration) {
@@ -335,10 +335,7 @@ export function bindTreeContainer(treeContainer: ITreeContainer): void {
           );
 
           for (const value of exposedOperators) {
-            const functionNode = TreeUtils.findOperator(
-              treeContainer,
-              value.text,
-            );
+            const functionNode = TreeUtils.findOperator(sourceFile, value.text);
 
             if (functionNode) {
               exposed.set(value.text, {
@@ -427,6 +424,6 @@ export function bindTreeContainer(treeContainer: ITreeContainer): void {
       }
     }
 
-    treeContainer.exposing = exposed;
+    sourceFile.exposing = exposed;
   }
 }
diff --git a/src/util/types/diagnostics.ts b/src/compiler/diagnostics.ts
similarity index 97%
rename from src/util/types/diagnostics.ts
rename to src/compiler/diagnostics.ts
index 6d507fc..975c7ad 100644
--- a/src/util/types/diagnostics.ts
+++ b/src/compiler/diagnostics.ts
@@ -1,8 +1,8 @@
 /* eslint-disable @typescript-eslint/naming-convention */
 import { DiagnosticSeverity, Range } from "vscode-languageserver";
 import { SyntaxNode } from "web-tree-sitter";
-import { PositionUtil } from "../../positionUtil";
-import { DiagnosticSource } from "../../providers/diagnostics/diagnosticSource";
+import { PositionUtil } from "../positionUtil";
+import { DiagnosticSource } from "../providers/diagnostics/diagnosticSource";
 
 export interface Diagnostic {
   code: string;
diff --git a/src/elmPackageCache.ts b/src/compiler/elmPackageCache.ts
similarity index 96%
rename from src/elmPackageCache.ts
rename to src/compiler/elmPackageCache.ts
index 8ac2fa1..9917642 100644
--- a/src/elmPackageCache.ts
+++ b/src/compiler/elmPackageCache.ts
@@ -1,10 +1,10 @@
 import { readdir } from "fs";
-import * as utils from "./util/elmUtils";
-import { ElmJson } from "./elmWorkspace";
+import * as utils from "./utils/elmUtils";
+import { ElmJson } from "./program";
 import { promisify } from "util";
-import { IConstraint, IVersion } from "./util/elmUtils";
-import { MultiMap } from "./util/multiMap";
-import * as path from "./util/path";
+import { IConstraint, IVersion } from "./utils/elmUtils";
+import { MultiMap } from "../util/multiMap";
+import * as path from "../util/path";
 
 const readDir = promisify(readdir);
 
diff --git a/src/forest.ts b/src/compiler/forest.ts
similarity index 77%
rename from src/forest.ts
rename to src/compiler/forest.ts
index 0a151a6..8ebd447 100644
--- a/src/forest.ts
+++ b/src/compiler/forest.ts
@@ -1,12 +1,12 @@
 import { SyntaxNode, Tree } from "web-tree-sitter";
 import { Imports } from "./imports";
-import { IExposing, TreeUtils } from "./util/treeUtils";
-import { SyntaxNodeMap } from "./util/types/syntaxNodeMap";
-import { SymbolMap } from "./util/types/binder";
-import { Diagnostic } from "./util/types/diagnostics";
-import { ElmProject } from "./elmWorkspace";
+import { IExposing, TreeUtils } from "../util/treeUtils";
+import { SyntaxNodeMap } from "./utils/syntaxNodeMap";
+import { SymbolMap } from "./binder";
+import { Diagnostic } from "./diagnostics";
+import { ElmProject } from "./program";
 
-export interface ITreeContainer {
+export interface ISourceFile {
   uri: string;
   writeable: boolean;
   referenced: boolean;
@@ -28,9 +28,9 @@ export interface ITreeContainer {
 }
 
 export interface IForest {
-  treeMap: Map<string, ITreeContainer>;
+  treeMap: Map<string, ISourceFile>;
   getTree(uri: string): Tree | undefined;
-  getByUri(uri: string): ITreeContainer | undefined;
+  getByUri(uri: string): ISourceFile | undefined;
   setTree(
     uri: string,
     writeable: boolean,
@@ -39,17 +39,14 @@ export interface IForest {
     isTestFile: boolean,
     project?: ElmProject,
     packageName?: string,
-  ): ITreeContainer;
+  ): ISourceFile;
   removeTree(uri: string): void;
   synchronize(): void;
   invalidateResolvedModules(): void;
 }
 
 export class Forest implements IForest {
-  public treeMap: Map<string, ITreeContainer> = new Map<
-    string,
-    ITreeContainer
-  >();
+  public treeMap: Map<string, ISourceFile> = new Map<string, ISourceFile>();
 
   constructor(private rootProject: ElmProject) {}
 
@@ -57,7 +54,7 @@ export class Forest implements IForest {
     return this.getByUri(uri)?.tree;
   }
 
-  public getByUri(uri: string): ITreeContainer | undefined {
+  public getByUri(uri: string): ISourceFile | undefined {
     return this.treeMap.get(uri);
   }
 
@@ -69,10 +66,10 @@ export class Forest implements IForest {
     isTestFile: boolean,
     project: ElmProject = this.rootProject,
     maintainerAndPackageName?: string,
-  ): ITreeContainer {
+  ): ISourceFile {
     tree.uri = uri;
 
-    const treeContainer: ITreeContainer = {
+    const sourceFile: ISourceFile = {
       maintainerAndPackageName,
       referenced,
       tree,
@@ -83,9 +80,9 @@ export class Forest implements IForest {
       parseDiagnostics: [],
     };
 
-    this.treeMap.set(uri, treeContainer);
+    this.treeMap.set(uri, sourceFile);
 
-    return treeContainer;
+    return sourceFile;
   }
 
   public removeTree(uri: string): void {
@@ -117,12 +114,12 @@ export class Forest implements IForest {
   }
 
   public invalidateResolvedModules(): void {
-    this.treeMap.forEach((treeContainer) => {
-      treeContainer.resolvedModules = undefined;
+    this.treeMap.forEach((sourceFile) => {
+      sourceFile.resolvedModules = undefined;
     });
   }
 
-  private resolveModules(sourceFile: ITreeContainer): Map<string, string> {
+  private resolveModules(sourceFile: ISourceFile): Map<string, string> {
     const importClauses = [
       ...Imports.getVirtualImports(),
       ...(TreeUtils.findAllImportClauseNodes(sourceFile.tree) ?? []),
@@ -149,7 +146,7 @@ export class Forest implements IForest {
     return resolvedModules;
   }
 
-  private getModuleMap(sourceFile: ITreeContainer): Map<string, string> {
+  private getModuleMap(sourceFile: ISourceFile): Map<string, string> {
     return sourceFile.isTestFile
       ? sourceFile.project.testModuleToUriMap
       : sourceFile.project.moduleToUriMap;
diff --git a/src/imports.ts b/src/compiler/imports.ts
similarity index 95%
rename from src/imports.ts
rename to src/compiler/imports.ts
index 3577851..bcc390b 100644
--- a/src/imports.ts
+++ b/src/compiler/imports.ts
@@ -1,8 +1,8 @@
 import Parser, { SyntaxNode } from "web-tree-sitter";
-import { IForest, ITreeContainer } from "./forest";
-import { IExposed, IExposing, NodeType, TreeUtils } from "./util/treeUtils";
+import { IForest, ISourceFile } from "./forest";
+import { IExposed, IExposing, NodeType, TreeUtils } from "../util/treeUtils";
 import { container } from "tsyringe";
-import { MultiMap } from "./util/multiMap";
+import { MultiMap } from "../util/multiMap";
 import { performance } from "perf_hooks";
 
 export let importsTime = 0;
@@ -32,22 +32,19 @@ export class Imports extends MultiMap<string, IImport> {
   }
   private static cachedVirtualImports: SyntaxNode[];
 
-  public static getImports(
-    treeContainer: ITreeContainer,
-    forest: IForest,
-  ): Imports {
+  public static getImports(sourceFile: ISourceFile, forest: IForest): Imports {
     const start = performance.now();
     const result = new Imports();
 
     const importNodes = [
       ...Imports.getVirtualImports(),
-      ...(TreeUtils.findAllImportClauseNodes(treeContainer.tree) ?? []),
+      ...(TreeUtils.findAllImportClauseNodes(sourceFile.tree) ?? []),
     ];
 
     importNodes.forEach((importNode) => {
       const moduleName = importNode.childForFieldName("moduleName")?.text;
       if (moduleName) {
-        const uri = treeContainer.resolvedModules?.get(moduleName);
+        const uri = sourceFile.resolvedModules?.get(moduleName);
 
         if (!uri) {
           return;
@@ -319,7 +316,7 @@ export class Imports extends MultiMap<string, IImport> {
   private static exposedNodesToImports(
     exposedNode: IExposed,
     moduleName: string,
-    foundModule: ITreeContainer,
+    foundModule: ISourceFile,
     includeUnionConstructors = false,
   ): IImport[] {
     return [
diff --git a/src/util/types/operatorPrecedence.ts b/src/compiler/operatorPrecedence.ts
similarity index 95%
rename from src/util/types/operatorPrecedence.ts
rename to src/compiler/operatorPrecedence.ts
index ffd94bd..fb82941 100644
--- a/src/util/types/operatorPrecedence.ts
+++ b/src/compiler/operatorPrecedence.ts
@@ -1,5 +1,5 @@
-import { Expression } from "./expressionTree";
-import { SyntaxNodeMap } from "./syntaxNodeMap";
+import { Expression } from "./utils/expressionTree";
+import { SyntaxNodeMap } from "./utils/syntaxNodeMap";
 
 export type OperatorAssociativity = "LEFT" | "RIGHT" | "NON";
 
diff --git a/src/elmWorkspace.ts b/src/compiler/program.ts
similarity index 94%
rename from src/elmWorkspace.ts
rename to src/compiler/program.ts
index e444c8f..ff8c301 100644
--- a/src/elmWorkspace.ts
+++ b/src/compiler/program.ts
@@ -6,27 +6,27 @@ import util from "util";
 import { Connection } from "vscode-languageserver";
 import { URI } from "vscode-uri";
 import Parser, { Tree } from "web-tree-sitter";
-import { ICancellationToken } from "./cancellation";
+import { ICancellationToken } from "../cancellation";
 import { ElmPackageCache, IElmPackageCache } from "./elmPackageCache";
-import { Forest, IForest, ITreeContainer } from "./forest";
-import * as utils from "./util/elmUtils";
-import { IVersion } from "./util/elmUtils";
-import * as path from "./util/path";
-import { normalizeUri } from "./util/path";
+import { Forest, IForest, ISourceFile } from "./forest";
+import * as utils from "./utils/elmUtils";
+import { IVersion } from "./utils/elmUtils";
+import * as path from "../util/path";
+import { normalizeUri } from "../util/path";
 import {
   IPossibleImportsCache,
   PossibleImportsCache,
-} from "./util/possibleImportsCache";
-import { Settings } from "./util/settings";
-import { Diagnostic } from "./util/types/diagnostics";
-import { TypeCache } from "./util/types/typeCache";
+} from "../util/possibleImportsCache";
+import { Settings } from "../util/settings";
+import { Diagnostic } from "./diagnostics";
+import { TypeCache } from "./typeCache";
 import {
   createTypeChecker,
   DefinitionResult,
   TypeChecker,
-} from "./util/types/typeChecker";
+} from "./typeChecker";
 import chokidar from "chokidar";
-import { CommandManager } from "./commandManager";
+import { CommandManager } from "../commandManager";
 
 const readFile = util.promisify(fs.readFile);
 
@@ -77,16 +77,16 @@ interface IElmPackageJson {
   };
 }
 
-export interface IElmWorkspace {
+export interface IProgram {
   init(progressCallback: (percent: number) => void): void;
   hasDocument(uri: URI): boolean;
   isInSourceDirectory(uri: string): boolean;
   getSourceDirectoryOfFile(uri: string): string | undefined;
-  getSourceFile(uri: string): ITreeContainer | undefined;
+  getSourceFile(uri: string): ISourceFile | undefined;
   getSourceFileOfImportableModule(
-    sourceFile: ITreeContainer,
+    sourceFile: ISourceFile,
     importableModuleName: string,
-  ): ITreeContainer | undefined;
+  ): ISourceFile | undefined;
   getForest(synchronize?: boolean): IForest;
   getRootPath(): URI;
   getTypeCache(): TypeCache;
@@ -95,20 +95,20 @@ export interface IElmWorkspace {
   getPossibleImportsCache(): IPossibleImportsCache;
   getOperatorsCache(): Map<string, DefinitionResult>;
   getSemanticDiagnostics(
-    sourceFile: ITreeContainer,
+    sourceFile: ISourceFile,
     cancellationToken?: ICancellationToken,
   ): Diagnostic[];
   getSemanticDiagnosticsAsync(
-    sourceFile: ITreeContainer,
+    sourceFile: ISourceFile,
     cancellationToken?: ICancellationToken,
   ): Promise<Diagnostic[]>;
-  getSyntacticDiagnostics(sourceFile: ITreeContainer): Diagnostic[];
+  getSyntacticDiagnostics(sourceFile: ISourceFile): Diagnostic[];
   getSuggestionDiagnostics(
-    sourceFile: ITreeContainer,
+    sourceFile: ISourceFile,
     cancellationToken?: ICancellationToken,
   ): Diagnostic[];
   getImportableModules(
-    sourceFile: ITreeContainer,
+    sourceFile: ISourceFile,
   ): { moduleName: string; uri: string }[];
 }
 
@@ -141,7 +141,7 @@ export interface IProgramHost {
   watchFile(uri: string, callback: () => void): void;
 }
 
-export class ElmWorkspace implements IElmWorkspace {
+export class Program implements IProgram {
   private parser: Parser;
   private connection: Connection;
   private settings: Settings;
@@ -195,14 +195,14 @@ export class ElmWorkspace implements IElmWorkspace {
     ].find((elmFolder) => uri.startsWith(elmFolder));
   }
 
-  public getSourceFile(uri: string): ITreeContainer | undefined {
+  public getSourceFile(uri: string): ISourceFile | undefined {
     return this.getForest().getByUri(uri);
   }
 
   public getSourceFileOfImportableModule(
-    sourceFile: ITreeContainer,
+    sourceFile: ISourceFile,
     importableModuleName: string,
-  ): ITreeContainer | undefined {
+  ): ISourceFile | undefined {
     let moduleUri = sourceFile.project.moduleToUriMap.get(importableModuleName);
 
     if (!moduleUri && sourceFile.isTestFile) {
@@ -259,7 +259,7 @@ export class ElmWorkspace implements IElmWorkspace {
   }
 
   public getSemanticDiagnostics(
-    sourceFile: ITreeContainer,
+    sourceFile: ISourceFile,
     cancellationToken?: ICancellationToken,
   ): Diagnostic[] {
     const cached = this.diagnosticsCache.get(sourceFile.uri);
@@ -278,7 +278,7 @@ export class ElmWorkspace implements IElmWorkspace {
   }
 
   public async getSemanticDiagnosticsAsync(
-    sourceFile: ITreeContainer,
+    sourceFile: ISourceFile,
     cancellationToken?: ICancellationToken,
   ): Promise<Diagnostic[]> {
     const cached = this.diagnosticsCache.get(sourceFile.uri);
@@ -296,14 +296,14 @@ export class ElmWorkspace implements IElmWorkspace {
     return diagnostics;
   }
 
-  public getSyntacticDiagnostics(sourceFile: ITreeContainer): Diagnostic[] {
+  public getSyntacticDiagnostics(sourceFile: ISourceFile): Diagnostic[] {
     // Getting the type checker will bind the file if its not
     this.getTypeChecker();
     return sourceFile.parseDiagnostics;
   }
 
   public getSuggestionDiagnostics(
-    sourceFile: ITreeContainer,
+    sourceFile: ISourceFile,
     cancellationToken?: ICancellationToken,
   ): Diagnostic[] {
     return this.getTypeChecker().getSuggestionDiagnostics(
@@ -313,7 +313,7 @@ export class ElmWorkspace implements IElmWorkspace {
   }
 
   public getImportableModules(
-    sourceFile: ITreeContainer,
+    sourceFile: ISourceFile,
   ): { moduleName: string; uri: string }[] {
     return Array.from(sourceFile.project.moduleToUriMap.entries()).map(
       ([moduleName, uri]) => ({
diff --git a/src/util/references.ts b/src/compiler/references.ts
similarity index 96%
rename from src/util/references.ts
rename to src/compiler/references.ts
index 425ba08..ffe8e06 100644
--- a/src/util/references.ts
+++ b/src/compiler/references.ts
@@ -1,28 +1,28 @@
-import { IElmWorkspace } from "../elmWorkspace";
-import { ITreeContainer } from "../forest";
+import { IProgram } from "./program";
+import { ISourceFile } from "./forest";
 import { SyntaxNode, Tree } from "web-tree-sitter";
-import { IReferenceNode } from "./referenceNode";
-import { TreeUtils } from "./treeUtils";
-import { Utils } from "./utils";
-import { IImport, Imports } from "../imports";
+import { IReferenceNode } from "../util/referenceNode";
+import { TreeUtils } from "../util/treeUtils";
+import { Utils } from "../util/utils";
+import { IImport, Imports } from "./imports";
 
 export class References {
   public static find(
     definitionNode: IReferenceNode | undefined,
-    elmWorkspace: IElmWorkspace,
+    program: IProgram,
   ): { node: SyntaxNode; uri: string }[] {
     const references: { node: SyntaxNode; uri: string }[] = [];
 
-    const forest = elmWorkspace.getForest();
-    const checker = elmWorkspace.getTypeChecker();
+    const forest = program.getForest();
+    const checker = program.getTypeChecker();
 
     if (definitionNode) {
       const refSourceTree = forest.getByUri(definitionNode.uri);
 
       if (refSourceTree) {
         const imports: { [uri: string]: Imports } = {};
-        forest.treeMap.forEach((treeContainer) => {
-          imports[treeContainer.uri] = checker.getAllImports(treeContainer);
+        forest.treeMap.forEach((sourceFile) => {
+          imports[sourceFile.uri] = checker.getAllImports(sourceFile);
         });
 
         const moduleNameNode = TreeUtils.getModuleNameNode(refSourceTree.tree);
@@ -658,7 +658,7 @@ export class References {
                     fieldName.text,
                     definitionNode,
                     refSourceTree,
-                    elmWorkspace,
+                    program,
                   ),
                 );
 
@@ -680,7 +680,7 @@ export class References {
                           fieldName.text,
                           definitionNode,
                           treeToCheck,
-                          elmWorkspace,
+                          program,
                         ),
                       );
                     }
@@ -700,12 +700,12 @@ export class References {
 
   public static findOperator(
     node: SyntaxNode,
-    elmWorkspace: IElmWorkspace,
+    program: IProgram,
   ): SyntaxNode | undefined {
     const functionNameNode = TreeUtils.getFunctionNameNodeFromDefinition(node);
 
     if (functionNameNode) {
-      const infixRef = elmWorkspace
+      const infixRef = program
         .getForest()
         .getByUri(node.tree.uri)
         ?.symbolLinks?.get(node.tree.rootNode)
@@ -801,25 +801,22 @@ export class References {
   private static getFieldReferences(
     fieldName: string,
     definition: { node: SyntaxNode; uri: string },
-    treeContainer: ITreeContainer,
-    elmWorkspace: IElmWorkspace,
+    sourceFile: ISourceFile,
+    program: IProgram,
   ): { node: SyntaxNode; uri: string }[] {
     const references: { node: SyntaxNode; uri: string }[] = [];
 
-    const fieldUsages = References.findFieldUsages(
-      treeContainer.tree,
-      fieldName,
-    );
+    const fieldUsages = References.findFieldUsages(sourceFile.tree, fieldName);
 
     fieldUsages.forEach((field) => {
-      const fieldDef = elmWorkspace
+      const fieldDef = program
         .getTypeChecker()
-        .findDefinition(field, treeContainer);
+        .findDefinition(field, sourceFile);
 
       if (fieldDef?.node.id === definition.node.id) {
         references.push({
           node: field,
-          uri: treeContainer.uri,
+          uri: sourceFile.uri,
         });
       }
     });
diff --git a/src/util/types/typeCache.ts b/src/compiler/typeCache.ts
similarity index 96%
rename from src/util/types/typeCache.ts
rename to src/compiler/typeCache.ts
index 2a68be0..1a610f9 100644
--- a/src/util/types/typeCache.ts
+++ b/src/compiler/typeCache.ts
@@ -1,7 +1,7 @@
 import { SyntaxNode } from "web-tree-sitter";
-import { MultiMap } from "../multiMap";
-import { TreeUtils } from "../treeUtils";
-import { SyntaxNodeMap } from "./syntaxNodeMap";
+import { MultiMap } from "../util/multiMap";
+import { TreeUtils } from "../util/treeUtils";
+import { SyntaxNodeMap } from "./utils/syntaxNodeMap";
 import { InferenceResult } from "./typeInference";
 
 type CacheKey =
diff --git a/src/util/types/typeChecker.ts b/src/compiler/typeChecker.ts
similarity index 86%
rename from src/util/types/typeChecker.ts
rename to src/compiler/typeChecker.ts
index eac57c2..a02b8dd 100644
--- a/src/util/types/typeChecker.ts
+++ b/src/compiler/typeChecker.ts
@@ -1,6 +1,6 @@
 /* eslint-disable @typescript-eslint/naming-convention */
 import { SyntaxNode } from "web-tree-sitter";
-import { flatMap, NodeType, TreeUtils } from "../treeUtils";
+import { flatMap, NodeType, TreeUtils } from "../util/treeUtils";
 import {
   Expression,
   EValueDeclaration,
@@ -9,8 +9,8 @@ import {
   ETypeDeclaration,
   EUnionVariant,
   EPortAnnotation,
-} from "./expressionTree";
-import { IElmWorkspace } from "../../elmWorkspace";
+} from "./utils/expressionTree";
+import { IProgram } from "./program";
 import { container } from "tsyringe";
 import { Connection } from "vscode-languageserver";
 import {
@@ -19,19 +19,19 @@ import {
   InferenceScope,
   InferenceResult,
 } from "./typeInference";
-import { ITreeContainer } from "../../forest";
-import { IImport, Imports } from "../../imports";
+import { ISourceFile } from "./forest";
+import { IImport, Imports } from "./imports";
 import { TypeRenderer } from "./typeRenderer";
 import { performance } from "perf_hooks";
 import { bindTreeContainer } from "./binder";
-import { Sequence } from "../sequence";
-import { Utils } from "../utils";
+import { Sequence } from "../util/sequence";
+import { Utils } from "../util/utils";
 import { TypeExpression } from "./typeExpression";
-import { ICancellationToken } from "../../cancellation";
+import { ICancellationToken } from "../cancellation";
 import { Diagnostic, Diagnostics, error } from "./diagnostics";
-import { isKernelProject, nameIsKernel } from "../elmUtils";
+import { isKernelProject, nameIsKernel } from "./utils/elmUtils";
 import { existsSync } from "fs";
-import * as path from "../path";
+import * as path from "../util/path";
 import { URI } from "vscode-uri";
 
 export let bindTime = 0;
@@ -66,40 +66,40 @@ export interface TypeChecker {
   findType: (node: SyntaxNode) => Type;
   findDefinition: (
     node: SyntaxNode,
-    treeContainer: ITreeContainer,
+    sourceFile: ISourceFile,
   ) => DefinitionResult | undefined;
   findDefinitionShallow: (
     node: SyntaxNode,
-    treeContainer: ITreeContainer,
+    sourceFile: ISourceFile,
   ) => DefinitionResult | undefined;
-  getAllImports: (treeContainer: ITreeContainer) => Imports;
+  getAllImports: (sourceFile: ISourceFile) => Imports;
   getQualifierForName: (
-    treeContainer: ITreeContainer,
+    sourceFile: ISourceFile,
     module: string,
     name: string,
   ) => string | undefined;
-  typeToString: (t: Type, treeContainer?: ITreeContainer) => string;
+  typeToString: (t: Type, sourceFile?: ISourceFile) => string;
   getDiagnostics: (
-    treeContainer: ITreeContainer,
+    sourceFile: ISourceFile,
     cancellationToken?: ICancellationToken,
   ) => Diagnostic[];
   getDiagnosticsAsync: (
-    treeContainer: ITreeContainer,
+    sourceFile: ISourceFile,
     token?: ICancellationToken,
     cancelCallback?: () => boolean,
   ) => Promise<Diagnostic[]>;
   getSuggestionDiagnostics: (
-    treeContainer: ITreeContainer,
+    sourceFile: ISourceFile,
     cancellationToken?: ICancellationToken,
   ) => Diagnostic[];
   findImportModuleNameNode: (
     moduleNameOrAlias: string,
-    treeContainer: ITreeContainer,
+    sourceFile: ISourceFile,
   ) => SyntaxNode | undefined;
 }
 
-export function createTypeChecker(workspace: IElmWorkspace): TypeChecker {
-  const forest = workspace.getForest();
+export function createTypeChecker(program: IProgram): TypeChecker {
+  const forest = program.getForest();
   const imports = new Map<string, Imports>();
 
   const diagnostics = new DiagnosticsCollection();
@@ -109,8 +109,8 @@ export function createTypeChecker(workspace: IElmWorkspace): TypeChecker {
   const checkedNodes = new Set<number>();
 
   const start = performance.now();
-  forest.treeMap.forEach((treeContainer) => {
-    bindTreeContainer(treeContainer);
+  forest.treeMap.forEach((sourceFile) => {
+    bindTreeContainer(sourceFile);
   });
   bindTime = performance.now() - start;
 
@@ -164,7 +164,7 @@ export function createTypeChecker(workspace: IElmWorkspace): TypeChecker {
         const inferenceResult = InferenceScope.valueDeclarationInference(
           declaration,
           uri,
-          workspace,
+          program,
           new Set<EValueDeclaration>(),
         );
 
@@ -202,7 +202,7 @@ export function createTypeChecker(workspace: IElmWorkspace): TypeChecker {
       ) {
         const inferenceResult = TypeExpression.typeAliasDeclarationInference(
           typeAliasDeclaration,
-          workspace,
+          program,
         );
 
         if (node.type === "type_alias_declaration") {
@@ -218,8 +218,7 @@ export function createTypeChecker(workspace: IElmWorkspace): TypeChecker {
           : undefined;
 
       if (unionVariant && unionVariant.nodeType === "UnionVariant") {
-        return TypeExpression.unionVariantInference(unionVariant, workspace)
-          .type;
+        return TypeExpression.unionVariantInference(unionVariant, program).type;
       }
 
       const typeDeclaration = mapSyntaxNodeToExpression(
@@ -229,7 +228,7 @@ export function createTypeChecker(workspace: IElmWorkspace): TypeChecker {
       if (typeDeclaration && typeDeclaration.nodeType === "TypeDeclaration") {
         const inferenceResult = TypeExpression.typeDeclarationInference(
           typeDeclaration,
-          workspace,
+          program,
         );
 
         return findTypeOrParentType(node, inferenceResult) ?? TUnknown;
@@ -238,7 +237,7 @@ export function createTypeChecker(workspace: IElmWorkspace): TypeChecker {
       const portAnnotation = mapSyntaxNodeToExpression(node);
 
       if (portAnnotation && portAnnotation.nodeType === "PortAnnotation") {
-        return TypeExpression.portAnnotationInference(portAnnotation, workspace)
+        return TypeExpression.portAnnotationInference(portAnnotation, program)
           .type;
       }
 
@@ -251,29 +250,29 @@ export function createTypeChecker(workspace: IElmWorkspace): TypeChecker {
   }
 
   function getDiagnostics(
-    treeContainer: ITreeContainer,
+    sourceFile: ISourceFile,
     token?: ICancellationToken,
   ): Diagnostic[] {
     try {
       cancellationToken = token;
 
-      checkNode(treeContainer.tree.rootNode);
+      checkNode(sourceFile.tree.rootNode);
 
-      return diagnostics.get(treeContainer.uri);
+      return diagnostics.get(sourceFile.uri);
     } finally {
       cancellationToken = undefined;
     }
   }
 
   function getDiagnosticsAsync(
-    treeContainer: ITreeContainer,
+    sourceFile: ISourceFile,
     token?: ICancellationToken,
     cancelCallback?: () => boolean,
   ): Promise<Diagnostic[]> {
     cancellationToken = token;
 
     return new Promise((resolve, reject) => {
-      const children = treeContainer.tree.rootNode.children;
+      const children = sourceFile.tree.rootNode.children;
       let index = 0;
 
       const goNext = (): void => {
@@ -282,7 +281,7 @@ export function createTypeChecker(workspace: IElmWorkspace): TypeChecker {
           setImmediate(checkOne);
         } else {
           cancellationToken = undefined;
-          resolve(diagnostics.get(treeContainer.uri));
+          resolve(diagnostics.get(sourceFile.uri));
         }
       };
 
@@ -308,38 +307,38 @@ export function createTypeChecker(workspace: IElmWorkspace): TypeChecker {
   }
 
   function getSuggestionDiagnostics(
-    treeContainer: ITreeContainer,
+    sourceFile: ISourceFile,
     token?: ICancellationToken,
   ): Diagnostic[] {
     try {
       cancellationToken = token;
 
-      checkNode(treeContainer.tree.rootNode);
+      checkNode(sourceFile.tree.rootNode);
 
-      return suggestionDiagnostics.get(treeContainer.uri);
+      return suggestionDiagnostics.get(sourceFile.uri);
     } finally {
       cancellationToken = undefined;
     }
   }
 
-  function getAllImports(treeContainer: ITreeContainer): Imports {
-    const cached = imports.get(treeContainer.uri);
+  function getAllImports(sourceFile: ISourceFile): Imports {
+    const cached = imports.get(sourceFile.uri);
 
     if (cached) {
       return cached;
     }
 
-    const allImports = Imports.getImports(treeContainer, forest);
-    imports.set(treeContainer.uri, allImports);
+    const allImports = Imports.getImports(sourceFile, forest);
+    imports.set(sourceFile.uri, allImports);
     return allImports;
   }
 
   function findImport(
-    treeContainer: ITreeContainer,
+    sourceFile: ISourceFile,
     name: string,
     filter?: (imp: IImport) => boolean,
   ): DefinitionResult | undefined {
-    const possibleImport = getAllImports(treeContainer).get(name, filter);
+    const possibleImport = getAllImports(sourceFile).get(name, filter);
 
     if (possibleImport) {
       return {
@@ -351,18 +350,18 @@ export function createTypeChecker(workspace: IElmWorkspace): TypeChecker {
   }
 
   function findImportOfType(
-    treeContainer: ITreeContainer,
+    sourceFile: ISourceFile,
     name: string,
     type: NodeType,
   ): DefinitionResult | undefined {
-    return findImport(treeContainer, name, (imp) => imp.type === type);
+    return findImport(sourceFile, name, (imp) => imp.type === type);
   }
 
   function findDefinition(
     nodeAtPosition: SyntaxNode,
-    treeContainer: ITreeContainer,
+    sourceFile: ISourceFile,
   ): DefinitionResult | undefined {
-    const definition = findDefinitionShallow(nodeAtPosition, treeContainer);
+    const definition = findDefinitionShallow(nodeAtPosition, sourceFile);
 
     if (
       definition?.node.type === "lower_pattern" &&
@@ -370,7 +369,7 @@ export function createTypeChecker(workspace: IElmWorkspace): TypeChecker {
     ) {
       const innerDefinition = findDefinitionShallow(
         definition.node.firstNamedChild,
-        treeContainer,
+        sourceFile,
       );
 
       if (innerDefinition) {
@@ -383,9 +382,9 @@ export function createTypeChecker(workspace: IElmWorkspace): TypeChecker {
 
   function findDefinitionShallow(
     nodeAtPosition: SyntaxNode,
-    treeContainer: ITreeContainer,
+    sourceFile: ISourceFile,
   ): DefinitionResult | undefined {
-    const uri = treeContainer.uri;
+    const uri = sourceFile.uri;
     const nodeText = nodeAtPosition.text;
     const nodeParent = nodeAtPosition.parent;
 
@@ -395,9 +394,7 @@ export function createTypeChecker(workspace: IElmWorkspace): TypeChecker {
 
     const nodeParentType = nodeParent.type;
 
-    const rootSymbols = treeContainer.symbolLinks?.get(
-      treeContainer.tree.rootNode,
-    );
+    const rootSymbols = sourceFile.symbolLinks?.get(sourceFile.tree.rootNode);
 
     if (
       nodeParentType === "upper_case_qid" &&
@@ -418,7 +415,7 @@ export function createTypeChecker(workspace: IElmWorkspace): TypeChecker {
     ) {
       const upperCaseQid = nodeParent;
       const upperCaseQidText = upperCaseQid.text;
-      return findImportOfType(treeContainer, upperCaseQidText, "Module");
+      return findImportOfType(sourceFile, upperCaseQidText, "Module");
     } else if (
       (nodeParentType === "exposed_value" &&
         nodeParent.parent?.parent?.type === "module_declaration") ||
@@ -457,7 +454,7 @@ export function createTypeChecker(workspace: IElmWorkspace): TypeChecker {
         nodeParentType === "exposed_type") &&
       nodeParent.parent?.parent?.type === "import_clause"
     ) {
-      return findImport(treeContainer, nodeText);
+      return findImport(sourceFile, nodeText);
     } else if (nodeParentType === "union_variant") {
       const definitionNode = nodeParent;
       return {
@@ -501,7 +498,7 @@ export function createTypeChecker(workspace: IElmWorkspace): TypeChecker {
 
       if (isTypeUsage) {
         definitionFromOtherFile = findImportOfType(
-          treeContainer,
+          sourceFile,
           upperCaseQidText,
           "Type",
         );
@@ -510,7 +507,7 @@ export function createTypeChecker(workspace: IElmWorkspace): TypeChecker {
         }
       } else {
         definitionFromOtherFile = findImportOfType(
-          treeContainer,
+          sourceFile,
           upperCaseQidText,
           "UnionConstructor",
         );
@@ -520,7 +517,7 @@ export function createTypeChecker(workspace: IElmWorkspace): TypeChecker {
       }
 
       definitionFromOtherFile = findImportOfType(
-        treeContainer,
+        sourceFile,
         upperCaseQidText,
         "TypeAlias",
       );
@@ -534,11 +531,11 @@ export function createTypeChecker(workspace: IElmWorkspace): TypeChecker {
 
         const moduleNameOrAlias = nodeParent.text.substring(0, endPos);
         const moduleName =
-          findImportModuleNameNode(moduleNameOrAlias, treeContainer)?.text ??
+          findImportModuleNameNode(moduleNameOrAlias, sourceFile)?.text ??
           moduleNameOrAlias;
 
         const definitionFromOtherFile = findImportOfType(
-          treeContainer,
+          sourceFile,
           moduleName,
           "Module",
         );
@@ -548,8 +545,8 @@ export function createTypeChecker(workspace: IElmWorkspace): TypeChecker {
       }
 
       definitionFromOtherFile = findImportOfType(
-        treeContainer,
-        findImportModuleNameNode(upperCaseQidText, treeContainer)?.text ??
+        sourceFile,
+        findImportModuleNameNode(upperCaseQidText, sourceFile)?.text ??
           upperCaseQidText,
         "Module",
       );
@@ -573,7 +570,7 @@ export function createTypeChecker(workspace: IElmWorkspace): TypeChecker {
         return {
           node: nodeParent,
           nodeType: "Function",
-          uri: treeContainer.uri,
+          uri: sourceFile.uri,
         };
       }
     } else if (
@@ -595,7 +592,7 @@ export function createTypeChecker(workspace: IElmWorkspace): TypeChecker {
         (node) => node.parent ?? undefined,
       )
         .map((node) =>
-          treeContainer.symbolLinks
+          sourceFile.symbolLinks
             ?.get(node)
             ?.get(
               nodeAtPositionText,
@@ -608,7 +605,7 @@ export function createTypeChecker(workspace: IElmWorkspace): TypeChecker {
         return {
           node: localBinding.node,
           nodeType: localBinding.type,
-          uri: treeContainer.uri,
+          uri: sourceFile.uri,
         };
       } else {
         const nodeParentText = nodeParent.text;
@@ -623,11 +620,11 @@ export function createTypeChecker(workspace: IElmWorkspace): TypeChecker {
               ?.map((node) => node.text)
               .join(".") ?? "";
           const moduleName =
-            findImportModuleNameNode(moduleNameOrAlias, treeContainer)?.text ??
+            findImportModuleNameNode(moduleNameOrAlias, sourceFile)?.text ??
             moduleNameOrAlias;
 
           const moduleDefinitionFromOtherFile = findImportOfType(
-            treeContainer,
+            sourceFile,
             moduleName,
             "Module",
           );
@@ -638,7 +635,7 @@ export function createTypeChecker(workspace: IElmWorkspace): TypeChecker {
         }
 
         const portDefinitionFromOtherFile = findImportOfType(
-          treeContainer,
+          sourceFile,
           nodeParentText,
           "Port",
         );
@@ -648,7 +645,7 @@ export function createTypeChecker(workspace: IElmWorkspace): TypeChecker {
         }
 
         const functionDefinitionFromOtherFile = findImportOfType(
-          treeContainer,
+          sourceFile,
           nodeParentText,
           "Function",
         );
@@ -658,13 +655,13 @@ export function createTypeChecker(workspace: IElmWorkspace): TypeChecker {
         }
       }
     } else if (nodeAtPosition.type === "operator_identifier") {
-      const operatorsCache = workspace.getOperatorsCache();
+      const operatorsCache = program.getOperatorsCache();
       const cached = operatorsCache.get(nodeText);
       if (cached) {
         return cached;
       }
 
-      const definitionNode = TreeUtils.findOperator(treeContainer, nodeText);
+      const definitionNode = TreeUtils.findOperator(sourceFile, nodeText);
       if (definitionNode) {
         const result: DefinitionResult = {
           node: definitionNode,
@@ -675,7 +672,7 @@ export function createTypeChecker(workspace: IElmWorkspace): TypeChecker {
         return result;
       } else {
         const definitionFromOtherFile = findImportOfType(
-          treeContainer,
+          sourceFile,
           nodeText,
           "Operator",
         );
@@ -742,8 +739,8 @@ export function createTypeChecker(workspace: IElmWorkspace): TypeChecker {
       }
 
       return findImportOfType(
-        treeContainer,
-        findImportModuleNameNode(fullModuleName, treeContainer)?.text ??
+        sourceFile,
+        findImportModuleNameNode(fullModuleName, sourceFile)?.text ??
           fullModuleName,
         "Module",
       );
@@ -806,12 +803,12 @@ export function createTypeChecker(workspace: IElmWorkspace): TypeChecker {
   }
 
   function getQualifierForName(
-    treeContainer: ITreeContainer,
+    sourceFile: ISourceFile,
     module: string,
     name: string,
   ): string | undefined {
     const found = findImport(
-      treeContainer,
+      sourceFile,
       name,
       (imp) =>
         imp.fromModuleName === module &&
@@ -823,8 +820,7 @@ export function createTypeChecker(workspace: IElmWorkspace): TypeChecker {
       return "";
     }
 
-    const moduleImport = findImportModuleNameNode(module, treeContainer)
-      ?.parent;
+    const moduleImport = findImportModuleNameNode(module, sourceFile)?.parent;
 
     if (!moduleImport) {
       return;
@@ -842,8 +838,8 @@ export function createTypeChecker(workspace: IElmWorkspace): TypeChecker {
     return `${module}.`;
   }
 
-  function typeToString(t: Type, treeContainer?: ITreeContainer): string {
-    return new TypeRenderer(typeChecker, treeContainer).render(t);
+  function typeToString(t: Type, sourceFile?: ISourceFile): string {
+    return new TypeRenderer(typeChecker, sourceFile).render(t);
   }
 
   /**
@@ -851,11 +847,11 @@ export function createTypeChecker(workspace: IElmWorkspace): TypeChecker {
    */
   function findImportModuleNameNode(
     moduleNameOrAlias: string,
-    treeContainer: ITreeContainer,
+    sourceFile: ISourceFile,
   ): SyntaxNode | undefined {
     return (
-      treeContainer.symbolLinks
-        ?.get(treeContainer.tree.rootNode)
+      sourceFile.symbolLinks
+        ?.get(sourceFile.tree.rootNode)
         ?.get(moduleNameOrAlias, (s) => s.type === "Import")
         ?.node.childForFieldName("moduleName") ?? undefined
     );
@@ -903,7 +899,7 @@ export function createTypeChecker(workspace: IElmWorkspace): TypeChecker {
     const result = InferenceScope.valueDeclarationInference(
       declaration,
       valueDeclaration.tree.uri,
-      workspace,
+      program,
       new Set(),
       /* recursionAllowed */ false,
       cancellationToken,
@@ -938,7 +934,7 @@ export function createTypeChecker(workspace: IElmWorkspace): TypeChecker {
     if (moduleNameNode) {
       const moduleName = moduleNameNode.text;
       if (
-        !workspace.getSourceFileOfImportableModule(
+        !program.getSourceFileOfImportableModule(
           getSourceFileOfNode(importClause),
           moduleName,
         )
@@ -966,14 +962,14 @@ export function createTypeChecker(workspace: IElmWorkspace): TypeChecker {
   function checkTypeAliasDeclaration(typeAliasDeclaration: SyntaxNode): void {
     TypeExpression.typeAliasDeclarationInference(
       mapSyntaxNodeToExpression(typeAliasDeclaration) as ETypeAliasDeclaration,
-      workspace,
+      program,
     ).diagnostics.forEach((diagnostic) => diagnostics.add(diagnostic));
   }
 
   function checkTypeDeclaration(typeDeclaration: SyntaxNode): void {
     TypeExpression.typeDeclarationInference(
       mapSyntaxNodeToExpression(typeDeclaration) as ETypeDeclaration,
-      workspace,
+      program,
     ).diagnostics.forEach((diagnostic) => diagnostics.add(diagnostic));
 
     // Need to check union variants
@@ -983,18 +979,18 @@ export function createTypeChecker(workspace: IElmWorkspace): TypeChecker {
   function checkUnionVariant(unionVariant: SyntaxNode): void {
     TypeExpression.unionVariantInference(
       mapSyntaxNodeToExpression(unionVariant) as EUnionVariant,
-      workspace,
+      program,
     ).diagnostics.forEach((diagnostic) => diagnostics.add(diagnostic));
   }
 
   function checkPortAnnotation(portAnnotation: SyntaxNode): void {
     TypeExpression.portAnnotationInference(
       mapSyntaxNodeToExpression(portAnnotation) as EPortAnnotation,
-      workspace,
+      program,
     ).diagnostics.forEach((diagnostic) => diagnostics.add(diagnostic));
   }
 
-  function getSourceFileOfNode(node: SyntaxNode): ITreeContainer {
+  function getSourceFileOfNode(node: SyntaxNode): ISourceFile {
     const treeContainer = forest.getByUri(node.tree.uri);
 
     if (!treeContainer) {
diff --git a/src/util/types/typeExpression.ts b/src/compiler/typeExpression.ts
similarity index 91%
rename from src/util/types/typeExpression.ts
rename to src/compiler/typeExpression.ts
index f046ffa..ba73427 100644
--- a/src/util/types/typeExpression.ts
+++ b/src/compiler/typeExpression.ts
@@ -28,13 +28,13 @@ import {
   mapTypeDeclaration,
   mapTypeAliasDeclaration,
   mapTypeAnnotation,
-} from "./expressionTree";
-import { TreeUtils } from "../treeUtils";
+} from "./utils/expressionTree";
+import { TreeUtils } from "../util/treeUtils";
 import { TypeReplacement } from "./typeReplacement";
-import { SyntaxNodeMap } from "./syntaxNodeMap";
-import { Utils } from "../utils";
-import { RecordFieldReferenceTable } from "./recordFieldReferenceTable";
-import { IElmWorkspace } from "../../elmWorkspace";
+import { SyntaxNodeMap } from "./utils/syntaxNodeMap";
+import { Utils } from "../util/utils";
+import { RecordFieldReferenceTable } from "./utils/recordFieldReferenceTable";
+import { IProgram } from "./program";
 import { Diagnostic, Diagnostics, error } from "./diagnostics";
 
 export class TypeExpression {
@@ -53,20 +53,20 @@ export class TypeExpression {
 
   constructor(
     private root: Expression,
-    private workspace: IElmWorkspace,
+    private program: IProgram,
     private rigidVars: boolean,
     private activeAliases: Set<ETypeAliasDeclaration> = new Set(),
   ) {}
 
   public static typeDeclarationInference(
     e: ETypeDeclaration,
-    workspace: IElmWorkspace,
+    program: IProgram,
   ): InferenceResult {
     const setter = (): InferenceResult => {
       mapTypeDeclaration(e); // Fill in values only when needed
       const inferenceResult = new TypeExpression(
         e,
-        workspace,
+        program,
         /* rigidVars */ false,
       ).inferTypeDeclaration(e);
 
@@ -78,12 +78,12 @@ export class TypeExpression {
       };
     };
 
-    if (!workspace.getForest().getByUri(e.tree.uri)?.writeable) {
-      return workspace
+    if (!program.getForest().getByUri(e.tree.uri)?.writeable) {
+      return program
         .getTypeCache()
         .getOrSet("PACKAGE_TYPE_AND_TYPE_ALIAS", e, setter);
     } else {
-      return workspace
+      return program
         .getTypeCache()
         .getOrSet("PROJECT_TYPE_AND_TYPE_ALIAS", e, setter);
     }
@@ -91,14 +91,14 @@ export class TypeExpression {
 
   public static typeAliasDeclarationInference(
     e: ETypeAliasDeclaration,
-    workspace: IElmWorkspace,
+    program: IProgram,
     activeAliases = new Set<ETypeAliasDeclaration>(),
   ): InferenceResult {
     const setter = (): InferenceResult => {
       mapTypeAliasDeclaration(e);
       const inferenceResult = new TypeExpression(
         e,
-        workspace,
+        program,
         /* rigidVars */ false,
         activeAliases,
       ).inferTypeAliasDeclaration(e);
@@ -111,12 +111,12 @@ export class TypeExpression {
       };
     };
 
-    if (!workspace.getForest().getByUri(e.tree.uri)?.writeable) {
-      return workspace
+    if (!program.getForest().getByUri(e.tree.uri)?.writeable) {
+      return program
         .getTypeCache()
         .getOrSet("PACKAGE_TYPE_AND_TYPE_ALIAS", e, setter);
     } else {
-      return workspace
+      return program
         .getTypeCache()
         .getOrSet("PROJECT_TYPE_AND_TYPE_ALIAS", e, setter);
     }
@@ -124,28 +124,28 @@ export class TypeExpression {
 
   public static typeAnnotationInference(
     e: ETypeAnnotation,
-    workspace: IElmWorkspace,
+    program: IProgram,
     rigid = true,
   ): InferenceResult | undefined {
     const setter = (): InferenceResult => {
       mapTypeAnnotation(e);
       const inferenceResult = new TypeExpression(
         e,
-        workspace,
+        program,
         /* rigidVars */ true,
       ).inferTypeExpression(e.typeExpression!);
 
       const type = TypeReplacement.replace(inferenceResult.type, new Map());
       TypeReplacement.freeze(inferenceResult.type);
 
-      workspace.getTypeCache().trackTypeAnnotation(e);
+      program.getTypeCache().trackTypeAnnotation(e);
 
       return { ...inferenceResult, type };
     };
 
-    const result = !workspace.getForest().getByUri(e.tree.uri)?.writeable
-      ? workspace.getTypeCache().getOrSet("PACKAGE_TYPE_ANNOTATION", e, setter)
-      : workspace.getTypeCache().getOrSet("PROJECT_TYPE_ANNOTATION", e, setter);
+    const result = !program.getForest().getByUri(e.tree.uri)?.writeable
+      ? program.getTypeCache().getOrSet("PACKAGE_TYPE_ANNOTATION", e, setter)
+      : program.getTypeCache().getOrSet("PROJECT_TYPE_ANNOTATION", e, setter);
 
     if (!rigid) {
       result.type = TypeReplacement.flexify(result.type);
@@ -156,11 +156,11 @@ export class TypeExpression {
 
   public static unionVariantInference(
     e: EUnionVariant,
-    workspace: IElmWorkspace,
+    program: IProgram,
   ): InferenceResult {
     const inferenceResult = new TypeExpression(
       e,
-      workspace,
+      program,
       /* rigidVars */ false,
     ).inferUnionConstructor(e);
     TypeReplacement.freeze(inferenceResult.type);
@@ -173,11 +173,11 @@ export class TypeExpression {
 
   public static portAnnotationInference(
     e: EPortAnnotation,
-    workspace: IElmWorkspace,
+    program: IProgram,
   ): InferenceResult {
     const inferenceResult = new TypeExpression(
       e,
-      workspace,
+      program,
       /* rigidVars */ false,
     ).inferPortAnnotation(e);
     TypeReplacement.freeze(inferenceResult.type);
@@ -307,7 +307,7 @@ export class TypeExpression {
   private typeVariableType(typeVariable: ETypeVariable): Type {
     const definition = findDefinition(
       typeVariable.firstNamedChild,
-      this.workspace,
+      this.program,
     );
 
     // The type variable doesn't reference anything
@@ -349,7 +349,7 @@ export class TypeExpression {
     const type =
       TypeExpression.typeAnnotationInference(
         annotation as ETypeAnnotation,
-        this.workspace,
+        this.program,
         /* rigid */ true,
       )?.expressionTypes.get(definition.expr) ?? TUnknown;
 
@@ -376,7 +376,7 @@ export class TypeExpression {
       fieldExpressions,
     );
 
-    const baseTypeDefinition = findDefinition(record.baseType, this.workspace)
+    const baseTypeDefinition = findDefinition(record.baseType, this.program)
       ?.expr;
 
     const baseType = baseTypeDefinition
@@ -409,7 +409,7 @@ export class TypeExpression {
 
     const definition = findDefinition(
       typeRef.firstNamedChild?.lastNamedChild,
-      this.workspace,
+      this.program,
     );
 
     let declaredType: Type = TUnknown;
@@ -418,7 +418,7 @@ export class TypeExpression {
         case "TypeDeclaration":
           declaredType = TypeExpression.typeDeclarationInference(
             definition.expr,
-            this.workspace,
+            this.program,
           ).type;
           break;
         case "TypeAliasDeclaration":
@@ -449,7 +449,7 @@ export class TypeExpression {
             } else {
               declaredType = TypeExpression.typeAliasDeclarationInference(
                 definition.expr,
-                this.workspace,
+                this.program,
                 new Set(this.activeAliases.values()),
               ).type;
             }
@@ -511,8 +511,8 @@ export class TypeExpression {
     );
 
     return TUnion(
-      this.workspace.getForest().getByUri(typeDeclaration.tree.uri)
-        ?.moduleName ?? "",
+      this.program.getForest().getByUri(typeDeclaration.tree.uri)?.moduleName ??
+        "",
       typeDeclaration.name,
       params,
     );
diff --git a/src/util/types/typeInference.ts b/src/compiler/typeInference.ts
similarity index 97%
rename from src/util/types/typeInference.ts
rename to src/compiler/typeInference.ts
index f2b6f42..136dd53 100644
--- a/src/util/types/typeInference.ts
+++ b/src/compiler/typeInference.ts
@@ -1,14 +1,14 @@
 /* eslint-disable @typescript-eslint/naming-convention */
 import { SyntaxNode } from "web-tree-sitter";
-import { flatMap, TreeUtils } from "../treeUtils";
-import { References } from "../references";
+import { flatMap, TreeUtils } from "../util/treeUtils";
+import { References } from "./references";
 import {
   BinaryExprTree,
   IOperatorPrecedence,
   Operand,
   Binary,
 } from "./operatorPrecedence";
-import { DisjointSet } from "./disjointSet";
+import { DisjointSet } from "./utils/disjointSet";
 import { TypeReplacement } from "./typeReplacement";
 import {
   Expression,
@@ -36,16 +36,16 @@ import {
   EFieldAccessExpr,
   ENegateExpr,
   ETypeAnnotation,
-} from "./expressionTree";
-import { SyntaxNodeMap } from "./syntaxNodeMap";
+} from "./utils/expressionTree";
+import { SyntaxNodeMap } from "./utils/syntaxNodeMap";
 import { TypeExpression } from "./typeExpression";
-import { IElmWorkspace } from "../../elmWorkspace";
-import { Sequence } from "../sequence";
-import { Utils } from "../utils";
-import { RecordFieldReferenceTable } from "./recordFieldReferenceTable";
+import { IProgram } from "./program";
+import { Sequence } from "../util/sequence";
+import { Utils } from "../util/utils";
+import { RecordFieldReferenceTable } from "./utils/recordFieldReferenceTable";
 import { TypeChecker } from "./typeChecker";
 import { performance } from "perf_hooks";
-import { ICancellationToken } from "../../cancellation";
+import { ICancellationToken } from "../cancellation";
 import {
   Diagnostic,
   Diagnostics,
@@ -53,7 +53,7 @@ import {
   errorWithEndNode,
 } from "./diagnostics";
 import fromEntries from "fromentries";
-import { isKernelProject, nameIsKernel } from "../elmUtils";
+import { isKernelProject, nameIsKernel } from "./utils/elmUtils";
 
 export let inferTime = 0;
 export function resetInferTime(): void {
@@ -524,7 +524,7 @@ export class InferenceScope {
 
   constructor(
     private uri: string,
-    private elmWorkspace: IElmWorkspace,
+    private program: IProgram,
     private nonShadowableNames: Set<string>,
     private activeScopes: Set<EValueDeclaration>,
     private recursionAllowed: boolean,
@@ -542,7 +542,7 @@ export class InferenceScope {
       (scope: InferenceScope) => scope.parent,
     );
 
-    this.typeChecker = elmWorkspace.getTypeChecker();
+    this.typeChecker = program.getTypeChecker();
   }
 
   private getBinding(e: Expression): Type | undefined {
@@ -554,7 +554,7 @@ export class InferenceScope {
   public static valueDeclarationInference(
     declaration: EValueDeclaration,
     uri: string,
-    program: IElmWorkspace,
+    program: IProgram,
     activeScopes: Set<EValueDeclaration>,
     recursionAllowed = false,
     cancellationToken?: ICancellationToken,
@@ -874,7 +874,7 @@ export class InferenceScope {
     const result = callback(
       new InferenceScope(
         this.uri,
-        this.elmWorkspace,
+        this.program,
         new Set(this.nonShadowableNames.values()),
         activeScopes,
         recursionAllowed,
@@ -979,11 +979,11 @@ export class InferenceScope {
 
   private inferReferenceElement(e: Expression): Type {
     const definition =
-      findDefinition(e.firstNamedChild?.lastNamedChild, this.elmWorkspace) ??
-      findDefinition(e.firstNamedChild, this.elmWorkspace);
+      findDefinition(e.firstNamedChild?.lastNamedChild, this.program) ??
+      findDefinition(e.firstNamedChild, this.program);
 
     if (!definition) {
-      const sourceFile = this.elmWorkspace.getSourceFile(e.tree.uri);
+      const sourceFile = this.program.getSourceFile(e.tree.uri);
       if (
         nameIsKernel(e.text) &&
         sourceFile &&
@@ -1037,13 +1037,13 @@ export class InferenceScope {
       case "UnionVariant": {
         return TypeExpression.unionVariantInference(
           definition.expr,
-          this.elmWorkspace,
+          this.program,
         ).type;
       }
       case "TypeAliasDeclaration": {
         const ty = TypeExpression.typeAliasDeclarationInference(
           definition.expr,
-          this.elmWorkspace,
+          this.program,
         ).type;
         if (ty.nodeType === "Record" && Object.keys(ty.fields).length > 0) {
           return TFunction(Object.values(ty.fields), ty);
@@ -1059,7 +1059,7 @@ export class InferenceScope {
         if (typeAlias && typeAlias.nodeType === "TypeAliasDeclaration") {
           const fields = (TypeExpression.typeAliasDeclarationInference(
             typeAlias,
-            this.elmWorkspace,
+            this.program,
           ).type as TRecord)?.fields;
 
           if (fields) {
@@ -1072,7 +1072,7 @@ export class InferenceScope {
       case "PortAnnotation": {
         return TypeExpression.portAnnotationInference(
           definition.expr,
-          this.elmWorkspace,
+          this.program,
         ).type;
       }
       default:
@@ -1102,7 +1102,7 @@ export class InferenceScope {
         mapSyntaxNodeToExpression(
           declaration.typeAnnotation,
         ) as ETypeAnnotation,
-        this.elmWorkspace,
+        this.program,
         false,
       )?.type;
     }
@@ -1124,7 +1124,7 @@ export class InferenceScope {
         ? InferenceScope.valueDeclarationInference(
             declaration,
             declaration.tree.uri,
-            this.elmWorkspace,
+            this.program,
             this.activeScopes,
             this.recursionAllowed,
             this.cancellationToken,
@@ -1330,13 +1330,13 @@ export class InferenceScope {
     e: EOperator,
   ): [Type, IOperatorPrecedence] {
     // Find operator reference
-    const definition = findDefinition(e, this.elmWorkspace);
+    const definition = findDefinition(e, this.program);
 
     const opDeclaration = mapSyntaxNodeToExpression(definition?.expr.parent);
 
     const infixDeclarationExpr = mapSyntaxNodeToExpression(
       opDeclaration
-        ? References.findOperator(opDeclaration, this.elmWorkspace)
+        ? References.findOperator(opDeclaration, this.program)
         : undefined,
     );
 
@@ -1386,10 +1386,7 @@ export class InferenceScope {
     operatorFunction: EOperatorAsFunctionExpr,
   ): Type {
     // Find operator reference
-    const definition = findDefinition(
-      operatorFunction.operator,
-      this.elmWorkspace,
-    );
+    const definition = findDefinition(operatorFunction.operator, this.program);
 
     const opDeclaration = mapSyntaxNodeToExpression(definition?.expr.parent);
 
@@ -1420,9 +1417,7 @@ export class InferenceScope {
 
     if (targetTy?.nodeType === "Var") {
       if (targetTy.rigid) {
-        const typeString = this.elmWorkspace
-          .getTypeChecker()
-          .typeToString(targetTy);
+        const typeString = this.program.getTypeChecker().typeToString(targetTy);
         this.diagnostics.push(
           error(expr.target, Diagnostics.RecordBaseId, typeString),
         );
@@ -1651,7 +1646,7 @@ export class InferenceScope {
           mapSyntaxNodeToExpression(
             valueDeclaration.typeAnnotation,
           ) as ETypeAnnotation,
-          this.elmWorkspace,
+          this.program,
           true,
         )
       : undefined;
@@ -1815,7 +1810,7 @@ export class InferenceScope {
         const actualType = TTuple(this.uniqueVars(patterns.length));
         this.diagnostics.push(
           typeMismatchError(
-            this.elmWorkspace.getTypeChecker(),
+            this.program.getTypeChecker(),
             tuplePattern,
             actualType,
             ty,
@@ -1839,7 +1834,7 @@ export class InferenceScope {
   ): void {
     const variant = findDefinition(
       unionPattern.constructor.lastNamedChild,
-      this.elmWorkspace,
+      this.program,
     );
 
     if (variant?.expr.nodeType !== "UnionVariant") {
@@ -1856,7 +1851,7 @@ export class InferenceScope {
 
     const variantType = TypeExpression.unionVariantInference(
       variant.expr,
-      this.elmWorkspace,
+      this.program,
     ).type;
 
     if (!variantType || variantType.nodeType === "Unknown") {
@@ -1930,7 +1925,7 @@ export class InferenceScope {
       if (ty.nodeType !== "Unknown") {
         this.diagnostics.push(
           typeMismatchError(
-            this.elmWorkspace.getTypeChecker(),
+            this.program.getTypeChecker(),
             listPattern,
             TList(TVar("a")),
             ty,
@@ -1992,7 +1987,7 @@ export class InferenceScope {
 
         this.diagnostics.push(
           typeMismatchError(
-            this.elmWorkspace.getTypeChecker(),
+            this.program.getTypeChecker(),
             pattern,
             actualTy,
             ty,
@@ -2064,7 +2059,7 @@ export class InferenceScope {
       const errorExpr = expr.nodeType === "LetInExpr" ? expr.body : expr;
       this.diagnostics.push(
         typeMismatchError(
-          this.elmWorkspace.getTypeChecker(),
+          this.program.getTypeChecker(),
           errorExpr,
           t1,
           t2,
diff --git a/src/util/types/typeRenderer.ts b/src/compiler/typeRenderer.ts
similarity index 94%
rename from src/util/types/typeRenderer.ts
rename to src/compiler/typeRenderer.ts
index 917c343..60ca2d4 100644
--- a/src/util/types/typeRenderer.ts
+++ b/src/compiler/typeRenderer.ts
@@ -1,4 +1,4 @@
-import { ITreeContainer } from "../../forest";
+import { ISourceFile } from "./forest";
 import { TypeChecker } from "./typeChecker";
 import {
   getTypeclassName,
@@ -13,7 +13,7 @@ export class TypeRenderer {
 
   constructor(
     private typeChecker: TypeChecker,
-    private treeContainer?: ITreeContainer,
+    private sourceFile?: ISourceFile,
   ) {}
 
   public render(t: Type): string {
@@ -71,10 +71,10 @@ export class TypeRenderer {
         .join(" ")}`;
     }
 
-    if (this.treeContainer) {
+    if (this.sourceFile) {
       return `${
         this.typeChecker.getQualifierForName(
-          this.treeContainer,
+          this.sourceFile,
           t.module,
           t.name,
         ) ?? ""
diff --git a/src/util/types/typeReplacement.ts b/src/compiler/typeReplacement.ts
similarity index 99%
rename from src/util/types/typeReplacement.ts
rename to src/compiler/typeReplacement.ts
index 5a6239c..5afedbc 100644
--- a/src/util/types/typeReplacement.ts
+++ b/src/compiler/typeReplacement.ts
@@ -1,5 +1,5 @@
 import { performance } from "perf_hooks";
-import { RecordFieldReferenceTable } from "./recordFieldReferenceTable";
+import { RecordFieldReferenceTable } from "./utils/recordFieldReferenceTable";
 import {
   TVar,
   Type,
diff --git a/src/util/types/disjointSet.ts b/src/compiler/utils/disjointSet.ts
similarity index 93%
rename from src/util/types/disjointSet.ts
rename to src/compiler/utils/disjointSet.ts
index f5d1df8..f580853 100644
--- a/src/util/types/disjointSet.ts
+++ b/src/compiler/utils/disjointSet.ts
@@ -1,4 +1,4 @@
-import { Type, TVar } from "./typeInference";
+import { Type, TVar } from "../typeInference";
 
 export class DisjointSet {
   private map: Map<TVar, Type> = new Map<TVar, Type>();
diff --git a/src/util/elmUtils.ts b/src/compiler/utils/elmUtils.ts
similarity index 98%
rename from src/util/elmUtils.ts
rename to src/compiler/utils/elmUtils.ts
index d7efaf3..0356f1b 100644
--- a/src/util/elmUtils.ts
+++ b/src/compiler/utils/elmUtils.ts
@@ -1,10 +1,10 @@
 import execa, { ExecaSyncReturnValue } from "execa";
-import * as path from "./path";
+import * as path from "../../util/path";
 import { Connection, CompletionItemKind } from "vscode-languageserver";
 import { URI } from "vscode-uri";
 import { IElmPackageCache } from "../elmPackageCache";
-import { IClientSettings } from "./settings";
-import { ElmProject } from "../elmWorkspace";
+import { IClientSettings } from "../../util/settings";
+import { ElmProject } from "../program";
 
 export const isWindows = process.platform === "win32";
 
diff --git a/src/util/types/expressionTree.ts b/src/compiler/utils/expressionTree.ts
similarity index 98%
rename from src/util/types/expressionTree.ts
rename to src/compiler/utils/expressionTree.ts
index 5d33490..b500613 100644
--- a/src/util/types/expressionTree.ts
+++ b/src/compiler/utils/expressionTree.ts
@@ -1,8 +1,8 @@
 import { SyntaxNode } from "web-tree-sitter";
-import { OperatorAssociativity } from "./operatorPrecedence";
-import { TreeUtils } from "../treeUtils";
-import { Utils } from "../utils";
-import { IElmWorkspace } from "../../elmWorkspace";
+import { OperatorAssociativity } from "../operatorPrecedence";
+import { TreeUtils } from "../../util/treeUtils";
+import { Utils } from "../../util/utils";
+import { IProgram } from "../program";
 import { performance } from "perf_hooks";
 /* eslint-disable @typescript-eslint/naming-convention */
 
@@ -787,22 +787,22 @@ export function mapTypeAnnotation(typeAnnotation: ETypeAnnotation): void {
 
 export function findDefinition(
   e: SyntaxNode | undefined | null,
-  elmWorkspace: IElmWorkspace,
+  program: IProgram,
 ): { expr: Expression; uri: string } | undefined {
   if (!e) {
     return;
   }
 
-  const treeContainer = elmWorkspace.getForest().getByUri(e.tree.uri);
+  const sourceFile = program.getForest().getByUri(e.tree.uri);
 
-  if (!treeContainer) {
+  if (!sourceFile) {
     return;
   }
 
   const start = performance.now();
-  const definition = elmWorkspace
+  const definition = program
     .getTypeChecker()
-    .findDefinitionShallow(e, treeContainer);
+    .findDefinitionShallow(e, sourceFile);
   definitionTime += performance.now() - start;
 
   const mappedNode = mapSyntaxNodeToExpression(definition?.node);
diff --git a/src/util/types/recordFieldReferenceTable.ts b/src/compiler/utils/recordFieldReferenceTable.ts
similarity index 100%
rename from src/util/types/recordFieldReferenceTable.ts
rename to src/compiler/utils/recordFieldReferenceTable.ts
diff --git a/src/util/types/syntaxNodeMap.ts b/src/compiler/utils/syntaxNodeMap.ts
similarity index 100%
rename from src/util/types/syntaxNodeMap.ts
rename to src/compiler/utils/syntaxNodeMap.ts
diff --git a/src/util/types/syntaxNodeSet.ts b/src/compiler/utils/syntaxNodeSet.ts
similarity index 100%
rename from src/util/types/syntaxNodeSet.ts
rename to src/compiler/utils/syntaxNodeSet.ts
diff --git a/src/elmToolingJsonManager.ts b/src/elmToolingJsonManager.ts
index 704540c..684f0d7 100644
--- a/src/elmToolingJsonManager.ts
+++ b/src/elmToolingJsonManager.ts
@@ -1,6 +1,6 @@
 import path from "path";
 import { URI } from "vscode-uri";
-import { ITreeContainer } from "./forest";
+import { ISourceFile } from "./compiler/forest";
 import { NonEmptyArray } from "./util/utils";
 import util from "util";
 import * as fs from "fs";
@@ -10,7 +10,7 @@ const readFile = util.promisify(fs.readFile);
 export class ElmToolingJsonManager {
   public async getEntrypoints(
     workspaceRootPath: string,
-    sourceFile: ITreeContainer,
+    sourceFile: ISourceFile,
   ): Promise<[NonEmptyArray<string>, string]> {
     const elmToolingPath = path.join(workspaceRootPath, "elm-tooling.json");
     const defaultRelativePathToFile = path.relative(
diff --git a/src/providers/astProvider.ts b/src/providers/astProvider.ts
index ccd2a8c..d7d197d 100644
--- a/src/providers/astProvider.ts
+++ b/src/providers/astProvider.ts
@@ -14,7 +14,7 @@ import { ElmWorkspaceMatcher } from "../util/elmWorkspaceMatcher";
 import { Position, Range } from "vscode-languageserver-textdocument";
 import { TextDocumentEvents } from "../util/textDocumentEvents";
 import { TreeUtils } from "../util/treeUtils";
-import { ITreeContainer } from "../forest";
+import { ISourceFile } from "../compiler/forest";
 import {
   IDidChangeTextDocumentParams,
   IDidOpenTextDocumentParams,
@@ -27,11 +27,11 @@ export class ASTProvider {
   private documentEvents: TextDocumentEvents;
 
   private treeChangeEvent = new Emitter<{
-    treeContainer: ITreeContainer;
+    sourceFile: ISourceFile;
     declaration?: SyntaxNode;
   }>();
   readonly onTreeChange: Event<{
-    treeContainer: ITreeContainer;
+    sourceFile: ISourceFile;
     declaration?: SyntaxNode;
   }> = this.treeChangeEvent.event;
 
@@ -71,7 +71,7 @@ export class ASTProvider {
     const document: VersionedTextDocumentIdentifier = params.textDocument;
 
     // Source file could be undefined here
-    const sourceFile = <ITreeContainer | undefined>params.sourceFile;
+    const sourceFile = <ISourceFile | undefined>params.sourceFile;
 
     let tree = sourceFile?.tree;
 
@@ -158,7 +158,7 @@ export class ASTProvider {
             .getSourceDirectoryOfFile(document.uri)
             ?.endsWith("tests") ?? false;
 
-      const treeContainer = forest.setTree(
+      const sourceFile = forest.setTree(
         pendingRenameUri ?? document.uri,
         true,
         true,
@@ -166,13 +166,13 @@ export class ASTProvider {
         isTestFile,
       );
 
-      // The workspace now needs to be synchronized
+      // The program now needs to be synchronized
       params.program.markAsDirty();
 
       setImmediate(() => {
         if (tree) {
           this.treeChangeEvent.fire({
-            treeContainer,
+            sourceFile,
             declaration: changedDeclaration,
           });
         }
diff --git a/src/providers/codeAction/addMissingRecordFieldCodeAction.ts b/src/providers/codeAction/addMissingRecordFieldCodeAction.ts
index 75d3ac8..df785bd 100644
--- a/src/providers/codeAction/addMissingRecordFieldCodeAction.ts
+++ b/src/providers/codeAction/addMissingRecordFieldCodeAction.ts
@@ -1,12 +1,12 @@
 import { CodeAction, Range, TextEdit } from "vscode-languageserver";
 import { SyntaxNode } from "web-tree-sitter";
 import { CodeActionProvider } from "..";
-import { ITreeContainer } from "../../forest";
+import { ISourceFile } from "../../compiler/forest";
 import { getSpaces } from "../../util/refactorEditUtils";
 import { TreeUtils } from "../../util/treeUtils";
-import { Diagnostics } from "../../util/types/diagnostics";
-import { TypeChecker } from "../../util/types/typeChecker";
-import { Type } from "../../util/types/typeInference";
+import { Diagnostics } from "../../compiler/diagnostics";
+import { TypeChecker } from "../../compiler/typeChecker";
+import { Type } from "../../compiler/typeInference";
 import { ICodeActionParams } from "../paramsExtensions";
 
 const errorCodes = [Diagnostics.RecordField.code];
@@ -98,7 +98,7 @@ function createFields(
   fields: [string, Type][],
   targetRecord: SyntaxNode,
   checker: TypeChecker,
-  sourceFile: ITreeContainer,
+  sourceFile: ISourceFile,
 ): { [uri: string]: TextEdit[] } {
   const type = checker.findType(targetRecord);
 
diff --git a/src/providers/codeAction/addTypeAnnotationCodeAction.ts b/src/providers/codeAction/addTypeAnnotationCodeAction.ts
index 325bd17..60473d8 100644
--- a/src/providers/codeAction/addTypeAnnotationCodeAction.ts
+++ b/src/providers/codeAction/addTypeAnnotationCodeAction.ts
@@ -7,7 +7,7 @@ import {
 import { PositionUtil } from "../../positionUtil";
 import { getSpaces } from "../../util/refactorEditUtils";
 import { TreeUtils } from "../../util/treeUtils";
-import { Diagnostics } from "../../util/types/diagnostics";
+import { Diagnostics } from "../../compiler/diagnostics";
 import {
   CodeActionProvider,
   IRefactorCodeAction,
diff --git a/src/providers/codeAction/extractFunctionCodeAction.ts b/src/providers/codeAction/extractFunctionCodeAction.ts
index 59ebe03..0268d23 100644
--- a/src/providers/codeAction/extractFunctionCodeAction.ts
+++ b/src/providers/codeAction/extractFunctionCodeAction.ts
@@ -2,7 +2,7 @@ import { CodeActionKind, Position, TextEdit } from "vscode-languageserver";
 import { SyntaxNode } from "web-tree-sitter";
 import { RefactorEditUtils } from "../../util/refactorEditUtils";
 import { TreeUtils } from "../../util/treeUtils";
-import { TFunction } from "../../util/types/typeInference";
+import { TFunction } from "../../compiler/typeInference";
 import {
   CodeActionProvider,
   IRefactorCodeAction,
diff --git a/src/providers/codeAction/importCodeAction.ts b/src/providers/codeAction/importCodeAction.ts
index dbf5a55..ea74481 100644
--- a/src/providers/codeAction/importCodeAction.ts
+++ b/src/providers/codeAction/importCodeAction.ts
@@ -1,11 +1,11 @@
 import { CodeAction, TextEdit } from "vscode-languageserver";
 import { Range } from "vscode-languageserver-textdocument";
 import { SyntaxNode } from "web-tree-sitter";
-import { ITreeContainer } from "../../forest";
+import { ISourceFile } from "../../compiler/forest";
 import { ImportUtils, IPossibleImport } from "../../util/importUtils";
 import { RefactorEditUtils } from "../../util/refactorEditUtils";
 import { TreeUtils } from "../../util/treeUtils";
-import { Diagnostics } from "../../util/types/diagnostics";
+import { Diagnostics } from "../../compiler/diagnostics";
 import { CodeActionProvider } from "../codeActionProvider";
 import { ICodeActionParams } from "../paramsExtensions";
 
@@ -102,7 +102,7 @@ function getPossibleImports(
 }
 
 function getEditFromPossibleImport(
-  sourceFile: ITreeContainer,
+  sourceFile: ISourceFile,
   range: Range,
   possibleImport: IPossibleImport,
 ): TextEdit | undefined {
diff --git a/src/providers/codeAction/installPackageCodeAction.ts b/src/providers/codeAction/installPackageCodeAction.ts
index ea60508..4df8d2b 100644
--- a/src/providers/codeAction/installPackageCodeAction.ts
+++ b/src/providers/codeAction/installPackageCodeAction.ts
@@ -3,12 +3,12 @@ import { container } from "tsyringe";
 import { CodeAction, Connection } from "vscode-languageserver";
 import { URI } from "vscode-uri";
 import { CommandManager } from "../../commandManager";
-import { ElmPackageCache } from "../../elmPackageCache";
-import { execCmdSync } from "../../util/elmUtils";
+import { ElmPackageCache } from "../../compiler/elmPackageCache";
+import { execCmdSync } from "../../compiler/utils/elmUtils";
 import { ElmWorkspaceMatcher } from "../../util/elmWorkspaceMatcher";
 import { Settings } from "../../util/settings";
 import { TreeUtils } from "../../util/treeUtils";
-import { Diagnostics } from "../../util/types/diagnostics";
+import { Diagnostics } from "../../compiler/diagnostics";
 import { CodeActionProvider } from "../codeActionProvider";
 import { ICodeActionParams } from "../paramsExtensions";
 import { comparePackageRanking } from "../ranking";
diff --git a/src/providers/codeAction/makeDeclarationFromUsageCodeAction.ts b/src/providers/codeAction/makeDeclarationFromUsageCodeAction.ts
index 0e29207..028b88c 100644
--- a/src/providers/codeAction/makeDeclarationFromUsageCodeAction.ts
+++ b/src/providers/codeAction/makeDeclarationFromUsageCodeAction.ts
@@ -2,7 +2,7 @@ import { CodeAction, Range } from "vscode-languageserver";
 import { TextEdit } from "vscode-languageserver-textdocument";
 import { RefactorEditUtils } from "../../util/refactorEditUtils";
 import { TreeUtils } from "../../util/treeUtils";
-import { Diagnostics } from "../../util/types/diagnostics";
+import { Diagnostics } from "../../compiler/diagnostics";
 import { CodeActionProvider } from "../codeActionProvider";
 import { ICodeActionParams } from "../paramsExtensions";
 
diff --git a/src/providers/codeActionProvider.ts b/src/providers/codeActionProvider.ts
index 134757c..a7ea73c 100644
--- a/src/providers/codeActionProvider.ts
+++ b/src/providers/codeActionProvider.ts
@@ -14,14 +14,13 @@ import {
   TextEdit,
 } from "vscode-languageserver";
 import { URI } from "vscode-uri";
-import { ElmPackageCache } from "../elmPackageCache";
-import { ElmJson, IElmWorkspace } from "../elmWorkspace";
-import { ITreeContainer } from "../forest";
+import { ElmJson, IProgram } from "../compiler/program";
+import { ISourceFile } from "../compiler/forest";
 import { ElmWorkspaceMatcher } from "../util/elmWorkspaceMatcher";
 import { MultiMap } from "../util/multiMap";
 import { Settings } from "../util/settings";
 import { flatMap } from "../util/treeUtils";
-import { Diagnostic } from "../util/types/diagnostics";
+import { Diagnostic } from "../compiler/diagnostics";
 import {
   convertFromAnalyzerDiagnostic,
   DiagnosticsProvider,
@@ -33,6 +32,7 @@ import { diagnosticsEquals } from "./diagnostics/fileDiagnostics";
 import { ExposeUnexposeHandler } from "./handlers/exposeUnexposeHandler";
 import { MoveRefactoringHandler } from "./handlers/moveRefactoringHandler";
 import { ICodeActionParams } from "./paramsExtensions";
+import { ElmPackageCache } from "../compiler/elmPackageCache";
 
 export interface ICodeActionRegistration {
   errorCodes: string[];
@@ -279,8 +279,8 @@ export class CodeActionProvider {
 
   private onCodeActionResolve(
     codeAction: IRefactorCodeAction,
-    program: IElmWorkspace,
-    sourceFile: ITreeContainer,
+    program: IProgram,
+    sourceFile: ISourceFile,
   ): IRefactorCodeAction {
     const result = CodeActionProvider.refactorRegistrations
       .get(codeAction.data.refactorName)
diff --git a/src/providers/codeLensProvider.ts b/src/providers/codeLensProvider.ts
index 0bba534..99fe9d8 100644
--- a/src/providers/codeLensProvider.ts
+++ b/src/providers/codeLensProvider.ts
@@ -10,10 +10,10 @@ import {
 } from "vscode-languageserver";
 import { URI } from "vscode-uri";
 import { SyntaxNode, Tree } from "web-tree-sitter";
-import { IElmWorkspace } from "../elmWorkspace";
-import { ITreeContainer } from "../forest";
+import { IProgram } from "../compiler/program";
+import { ISourceFile } from "../compiler/forest";
 import { ElmWorkspaceMatcher } from "../util/elmWorkspaceMatcher";
-import { References } from "../util/references";
+import { References } from "../compiler/references";
 import { Settings } from "../util/settings";
 import { TreeUtils } from "../util/treeUtils";
 import { ICodeLensParams } from "./paramsExtensions";
@@ -78,17 +78,16 @@ export class CodeLensProvider {
 
   protected handleCodeLensResolveRequest = (
     codelens: ICodeLens,
-    program: IElmWorkspace,
-    sourceFile: ITreeContainer,
+    program: IProgram,
+    sourceFile: ISourceFile,
   ): ICodeLens => {
     const data = codelens.data;
     this.connection.console.info(
       `A code lens resolve was requested for ${data.uri}`,
     );
     const checker = program.getTypeChecker();
-    const treeContainer = sourceFile;
-    if (treeContainer) {
-      const tree = treeContainer.tree;
+    if (sourceFile) {
+      const tree = sourceFile.tree;
 
       switch (data.codeLensType) {
         case "exposed": {
@@ -119,7 +118,7 @@ export class CodeLensProvider {
           );
           const definitionNode = checker.findDefinition(
             nodeAtPosition,
-            treeContainer,
+            sourceFile,
           );
 
           const references = References.find(definitionNode, program);
diff --git a/src/providers/completionProvider.ts b/src/providers/completionProvider.ts
index 904b78d..3155782 100644
--- a/src/providers/completionProvider.ts
+++ b/src/providers/completionProvider.ts
@@ -13,10 +13,10 @@ import {
 } from "vscode-languageserver";
 import { URI } from "vscode-uri";
 import { SyntaxNode, Tree } from "web-tree-sitter";
-import { IElmWorkspace } from "../elmWorkspace";
-import { ITreeContainer } from "../forest";
+import { IProgram } from "../compiler/program";
+import { ISourceFile } from "../compiler/forest";
 import { comparePosition, PositionUtil } from "../positionUtil";
-import { getEmptyTypes } from "../util/elmUtils";
+import { getEmptyTypes } from "../compiler/utils/elmUtils";
 import { ElmWorkspaceMatcher } from "../util/elmWorkspaceMatcher";
 import { HintHelper } from "../util/hintHelper";
 import { ImportUtils, IPossibleImport } from "../util/importUtils";
@@ -24,9 +24,9 @@ import { RefactorEditUtils } from "../util/refactorEditUtils";
 import { TreeUtils } from "../util/treeUtils";
 import RANKING_LIST from "./ranking";
 import { DiagnosticsProvider } from ".";
-import { TypeChecker } from "../util/types/typeChecker";
+import { TypeChecker } from "../compiler/typeChecker";
 import escapeStringRegexp from "escape-string-regexp";
-import { TRecord } from "../util/types/typeInference";
+import { TRecord } from "../compiler/typeInference";
 import { ICompletionParams } from "./paramsExtensions";
 
 export type CompletionResult =
@@ -70,10 +70,10 @@ export class CompletionProvider {
     const completions: CompletionItem[] = [];
 
     const checker = params.program.getTypeChecker();
-    const treeContainer = params.sourceFile;
+    const sourceFile = params.sourceFile;
 
-    if (treeContainer) {
-      const tree = treeContainer?.tree;
+    if (sourceFile) {
+      const tree = sourceFile?.tree;
 
       const nodeAtPosition = TreeUtils.getNamedDescendantForPosition(
         tree.rootNode,
@@ -297,7 +297,7 @@ export class CompletionProvider {
       } else if (nodeAtPosition.parent?.parent?.type === "record_expr") {
         return this.getRecordCompletions(
           nodeAtPosition,
-          treeContainer,
+          sourceFile,
           replaceRange,
           params.program,
         );
@@ -355,7 +355,7 @@ export class CompletionProvider {
       if (targetNode) {
         const moduleCompletions = this.getSubmodulesOrValues(
           targetNode,
-          treeContainer,
+          sourceFile,
           params.program,
           replaceRange,
           targetWord,
@@ -367,7 +367,7 @@ export class CompletionProvider {
 
         const recordCompletions = this.getRecordCompletions(
           targetNode,
-          treeContainer,
+          sourceFile,
           replaceRange,
           params.program,
         );
@@ -393,7 +393,7 @@ export class CompletionProvider {
       completions.push(
         ...this.getCompletionsFromOtherFile(
           checker,
-          treeContainer,
+          sourceFile,
           replaceRange,
           targetWord,
         ),
@@ -435,8 +435,8 @@ export class CompletionProvider {
   }
 
   private getImportableModules(
-    program: IElmWorkspace,
-    sourceFile: ITreeContainer,
+    program: IProgram,
+    sourceFile: ISourceFile,
     range: Range,
     targetModule?: string,
   ): CompletionItem[] {
@@ -468,8 +468,8 @@ export class CompletionProvider {
   }
 
   private getExposedFromModule(
-    program: IElmWorkspace,
-    sourceFile: ITreeContainer,
+    program: IProgram,
+    sourceFile: ISourceFile,
     exposingListNode: SyntaxNode,
     range: Range,
   ): CompletionItem[] | undefined {
@@ -548,13 +548,13 @@ export class CompletionProvider {
 
   private getCompletionsFromOtherFile(
     checker: TypeChecker,
-    treeContainer: ITreeContainer,
+    sourceFile: ISourceFile,
     range: Range,
     inputText: string,
   ): CompletionItem[] {
     const completions: CompletionItem[] = [];
 
-    checker.getAllImports(treeContainer).forEach((element): void => {
+    checker.getAllImports(sourceFile).forEach((element): void => {
       const markdownDocumentation = HintHelper.createHint(element.node);
       let sortPrefix = "d";
       if (element.maintainerAndPackageName) {
@@ -575,7 +575,7 @@ export class CompletionProvider {
 
       const importNode = checker.findImportModuleNameNode(
         element.fromModuleName,
-        treeContainer,
+        sourceFile,
       )?.parent;
 
       // Check if a value is already imported for this module using the exposing list
@@ -775,24 +775,24 @@ export class CompletionProvider {
 
   private getRecordCompletions(
     node: SyntaxNode,
-    treeContainer: ITreeContainer,
+    sourceFile: ISourceFile,
     range: Range,
-    elmWorkspace: IElmWorkspace,
+    program: IProgram,
   ): CompletionItem[] {
-    const checker = elmWorkspace.getTypeChecker();
+    const checker = program.getTypeChecker();
 
     const result: CompletionItem[] = [];
     let typeDeclarationNode = TreeUtils.getTypeAliasOfRecord(
       node,
-      treeContainer,
-      elmWorkspace,
+      sourceFile,
+      program,
     )?.node;
 
     if (!typeDeclarationNode && node.parent?.parent) {
       typeDeclarationNode = TreeUtils.getTypeAliasOfRecordField(
         node.parent.parent,
-        treeContainer,
-        elmWorkspace,
+        sourceFile,
+        program,
       )?.node;
     }
 
@@ -800,7 +800,7 @@ export class CompletionProvider {
     if (!typeDeclarationNode && node.parent?.parent) {
       recordType = TreeUtils.getRecordTypeOfFunctionRecordParameter(
         node.parent.parent,
-        elmWorkspace,
+        program,
       );
     }
 
@@ -1097,7 +1097,7 @@ export class CompletionProvider {
   }
 
   private getPossibleImportsFiltered(
-    program: IElmWorkspace,
+    program: IProgram,
     uri: string,
     filterText: string,
   ): IPossibleImport[] {
@@ -1175,7 +1175,7 @@ export class CompletionProvider {
   }
 
   private getPossibleImports(
-    workspace: IElmWorkspace,
+    program: IProgram,
     range: Range,
     tree: Tree,
     uri: string,
@@ -1183,7 +1183,7 @@ export class CompletionProvider {
   ): { list: CompletionItem[]; isIncomplete: boolean } {
     const result: CompletionItem[] = [];
     const possibleImports = this.getPossibleImportsFiltered(
-      workspace,
+      program,
       uri,
       filterText,
     );
@@ -1233,8 +1233,8 @@ export class CompletionProvider {
 
   private getSubmodulesOrValues(
     node: SyntaxNode,
-    sourceFile: ITreeContainer,
-    program: IElmWorkspace,
+    sourceFile: ISourceFile,
+    program: IProgram,
     range: Range,
     targetModule: string,
   ): CompletionItem[] {
@@ -1258,7 +1258,7 @@ export class CompletionProvider {
     // Try to find the module definition that is already imported
     const definitionNode = checker.findDefinition(node, sourceFile);
 
-    let moduleTree: ITreeContainer | undefined;
+    let moduleTree: ISourceFile | undefined;
 
     if (definitionNode && definitionNode.nodeType === "Module") {
       moduleTree = program.getSourceFile(definitionNode.uri);
diff --git a/src/providers/definitionProvider.ts b/src/providers/definitionProvider.ts
index a070b6c..353ddf0 100644
--- a/src/providers/definitionProvider.ts
+++ b/src/providers/definitionProvider.ts
@@ -36,18 +36,15 @@ export class DefinitionProvider {
   ): DefinitionResult => {
     this.connection.console.info(`A definition was requested`);
     const checker = param.program.getTypeChecker();
-    const treeContainer = param.sourceFile;
+    const sourceFile = param.sourceFile;
 
-    if (treeContainer) {
+    if (sourceFile) {
       const nodeAtPosition = TreeUtils.getNamedDescendantForPosition(
-        treeContainer.tree.rootNode,
+        sourceFile.tree.rootNode,
         param.position,
       );
 
-      const definitionNode = checker.findDefinition(
-        nodeAtPosition,
-        treeContainer,
-      );
+      const definitionNode = checker.findDefinition(nodeAtPosition, sourceFile);
 
       if (definitionNode) {
         return this.createLocationFromDefinition(
diff --git a/src/providers/diagnostics/diagnosticsProvider.ts b/src/providers/diagnostics/diagnosticsProvider.ts
index 0a3a9f7..92afd54 100644
--- a/src/providers/diagnostics/diagnosticsProvider.ts
+++ b/src/providers/diagnostics/diagnosticsProvider.ts
@@ -8,21 +8,21 @@ import {
 import { TextDocument } from "vscode-languageserver-textdocument";
 import { URI } from "vscode-uri";
 import { ServerCancellationToken } from "../../cancellation";
-import { IElmWorkspace } from "../../elmWorkspace";
-import { ITreeContainer } from "../../forest";
+import { IProgram } from "../../compiler/program";
 import { GetDiagnosticsRequest } from "../../protocol";
 import { Delayer } from "../../util/delayer";
 import { ElmWorkspaceMatcher } from "../../util/elmWorkspaceMatcher";
 import { MultistepOperation } from "../../util/multistepOperation";
 import { IClientSettings } from "../../util/settings";
 import { TextDocumentEvents } from "../../util/textDocumentEvents";
-import { Diagnostic } from "../../util/types/diagnostics";
+import { Diagnostic } from "../../compiler/diagnostics";
 import { ASTProvider } from "../astProvider";
 import { DiagnosticSource } from "./diagnosticSource";
 import { DiagnosticsRequest } from "./diagnosticsRequest";
 import { ElmLsDiagnostics } from "./elmLsDiagnostics";
 import { ElmMakeDiagnostics } from "./elmMakeDiagnostics";
 import { DiagnosticKind, FileDiagnostics } from "./fileDiagnostics";
+import { ISourceFile } from "../../compiler/forest";
 
 export interface IElmIssueRegion {
   start: { line: number; column: number };
@@ -76,7 +76,7 @@ export class DiagnosticsProvider {
   private events: TextDocumentEvents;
   private connection: Connection;
   private clientSettings: IClientSettings;
-  private workspaces: IElmWorkspace[];
+  private workspaces: IProgram[];
   private elmWorkspaceMatcher: ElmWorkspaceMatcher<URI>;
   private documentEvents: TextDocumentEvents;
 
@@ -163,20 +163,17 @@ export class DiagnosticsProvider {
           this.updateDiagnostics(uri, DiagnosticKind.ElmLS, []),
         );
       } else {
-        this.workspaces.forEach((workspace) => {
-          if (!workspace.getForest(false)) {
+        this.workspaces.forEach((program) => {
+          if (!program.getForest(false)) {
             return;
           }
 
-          workspace.getForest().treeMap.forEach((treeContainer) => {
-            if (treeContainer.writeable) {
+          program.getForest().treeMap.forEach((sourceFile) => {
+            if (sourceFile.writeable) {
               this.updateDiagnostics(
-                treeContainer.uri,
+                sourceFile.uri,
                 DiagnosticKind.ElmLS,
-                this.elmLsDiagnostics.createDiagnostics(
-                  treeContainer,
-                  workspace,
-                ),
+                this.elmLsDiagnostics.createDiagnostics(sourceFile, program),
               );
             }
           });
@@ -189,9 +186,9 @@ export class DiagnosticsProvider {
     }
 
     // eslint-disable-next-line @typescript-eslint/no-unused-vars
-    astProvider.onTreeChange(({ treeContainer, declaration }) => {
+    astProvider.onTreeChange(({ sourceFile, declaration }) => {
       if (!clientInitiatedDiagnostics && !disableDiagnosticsOnChange) {
-        this.requestDiagnostics(treeContainer.uri);
+        this.requestDiagnostics(sourceFile.uri);
       }
     });
 
@@ -231,12 +228,12 @@ export class DiagnosticsProvider {
   }
 
   private requestAllDiagnostics(): void {
-    this.workspaces.forEach((workspace) => {
-      if (!workspace.getForest(false)) {
+    this.workspaces.forEach((program) => {
+      if (!program.getForest(false)) {
         return;
       }
 
-      workspace.getForest().treeMap.forEach(({ uri, writeable }) => {
+      program.getForest().treeMap.forEach(({ uri, writeable }) => {
         if (writeable) {
           this.pendingDiagnostics.set(uri, Date.now());
         }
@@ -439,9 +436,7 @@ export class DiagnosticsProvider {
     );
   }
 
-  private async getElmMakeDiagnostics(
-    sourceFile: ITreeContainer,
-  ): Promise<void> {
+  private async getElmMakeDiagnostics(sourceFile: ISourceFile): Promise<void> {
     const elmMakeDiagnostics = await this.elmMakeDiagnostics.createDiagnostics(
       sourceFile,
     );
diff --git a/src/providers/diagnostics/elmDiagnosticsHelper.ts b/src/providers/diagnostics/elmDiagnosticsHelper.ts
index 42f9219..75997cd 100644
--- a/src/providers/diagnostics/elmDiagnosticsHelper.ts
+++ b/src/providers/diagnostics/elmDiagnosticsHelper.ts
@@ -1,6 +1,6 @@
 import { DiagnosticSeverity, Range } from "vscode-languageserver";
 import { URI, Utils } from "vscode-uri";
-import { Diagnostics } from "../../util/types/diagnostics";
+import { Diagnostics } from "../../compiler/diagnostics";
 import { IDiagnostic, IElmIssue } from "./diagnosticsProvider";
 import { NAMING_ERROR } from "./elmMakeDiagnostics";
 
diff --git a/src/providers/diagnostics/elmLsDiagnostics.ts b/src/providers/diagnostics/elmLsDiagnostics.ts
index 9100f6b..5fea70b 100644
--- a/src/providers/diagnostics/elmLsDiagnostics.ts
+++ b/src/providers/diagnostics/elmLsDiagnostics.ts
@@ -1,5 +1,5 @@
 /* eslint-disable @typescript-eslint/no-unsafe-call */
-import { ITreeContainer } from "../../forest";
+import { ISourceFile } from "../../compiler/forest";
 import { container } from "tsyringe";
 import {
   CodeAction,
@@ -21,7 +21,7 @@ import {
   SyntaxNode,
   Tree,
 } from "web-tree-sitter";
-import { IElmWorkspace } from "../../elmWorkspace";
+import { IProgram } from "../../compiler/program";
 import { PositionUtil } from "../../positionUtil";
 import { ElmWorkspaceMatcher } from "../../util/elmWorkspaceMatcher";
 import { RefactorEditUtils } from "../../util/refactorEditUtils";
@@ -29,7 +29,7 @@ import { TreeUtils } from "../../util/treeUtils";
 import { Utils } from "../../util/utils";
 import { IDiagnostic } from "./diagnosticsProvider";
 import * as path from "path";
-import { SyntaxNodeMap } from "../../util/types/syntaxNodeMap";
+import { SyntaxNodeMap } from "../../compiler/utils/syntaxNodeMap";
 
 interface IElmAnalyseJson {
   checks?: {
@@ -425,14 +425,12 @@ export class ElmLsDiagnostics {
   }
 
   public createDiagnostics = (
-    treeContainer: ITreeContainer,
-    elmWorkspace: IElmWorkspace,
+    sourceFile: ISourceFile,
+    program: IProgram,
   ): IDiagnostic[] => {
-    const elmAnalyseJson = this.getElmAnalyseJson(
-      elmWorkspace.getRootPath().fsPath,
-    );
-    const tree = treeContainer.tree;
-    const uri = treeContainer.uri;
+    const elmAnalyseJson = this.getElmAnalyseJson(program.getRootPath().fsPath);
+    const tree = sourceFile.tree;
+    const uri = sourceFile.uri;
 
     if (
       elmAnalyseJson.excludedPaths?.some((path) =>
@@ -473,7 +471,7 @@ export class ElmLsDiagnostics {
           : this.getUseConsOverConcatDiagnostics(tree)),
         ...(elmAnalyseJson.checks?.SingleFieldRecord === false
           ? []
-          : this.getSingleFieldRecordDiagnostics(tree, uri, elmWorkspace)),
+          : this.getSingleFieldRecordDiagnostics(tree, uri, program)),
         ...(elmAnalyseJson.checks?.UnnecessaryListConcat === false
           ? []
           : this.getUnnecessaryListConcatDiagnostics(tree)),
@@ -534,17 +532,17 @@ export class ElmLsDiagnostics {
   ): CodeAction[] {
     const result: CodeAction[] = [];
 
-    const elmWorkspace = this.elmWorkspaceMatcher.getProgramFor(URI.parse(uri));
+    const program = this.elmWorkspaceMatcher.getProgramFor(URI.parse(uri));
 
-    const forest = elmWorkspace.getForest();
+    const forest = program.getForest();
 
-    const treeContainer = forest.getByUri(uri);
+    const sourceFile = forest.getByUri(uri);
 
-    if (treeContainer) {
+    if (sourceFile) {
       diagnostics.forEach((diagnostic) => {
         if (diagnostic.data.code === "unused_imported_value") {
           const node = TreeUtils.getNamedDescendantForPosition(
-            treeContainer.tree.rootNode,
+            sourceFile.tree.rootNode,
             diagnostic.range.start,
           );
 
@@ -567,7 +565,7 @@ export class ElmLsDiagnostics {
           }
 
           const removeValueEdit = RefactorEditUtils.removeValueFromImport(
-            treeContainer.tree,
+            sourceFile.tree,
             moduleName.text,
             node.text,
           );
@@ -590,7 +588,7 @@ export class ElmLsDiagnostics {
 
         if (diagnostic.data.code === "unused_import") {
           const node = TreeUtils.getNamedDescendantForPosition(
-            treeContainer.tree.rootNode,
+            sourceFile.tree.rootNode,
             diagnostic.range.end,
           );
 
@@ -617,7 +615,7 @@ export class ElmLsDiagnostics {
 
         if (diagnostic.data.code === "unused_alias") {
           const node = TreeUtils.getNamedDescendantForPosition(
-            treeContainer.tree.rootNode,
+            sourceFile.tree.rootNode,
             diagnostic.range.end,
           );
 
@@ -633,7 +631,7 @@ export class ElmLsDiagnostics {
 
         if (diagnostic.data.code === "unused_pattern") {
           const node = TreeUtils.getNamedDescendantForPosition(
-            treeContainer.tree.rootNode,
+            sourceFile.tree.rootNode,
             diagnostic.range.start,
           );
 
@@ -997,7 +995,7 @@ export class ElmLsDiagnostics {
   getSingleFieldRecordDiagnostics(
     tree: Tree,
     uri: string,
-    elmWorkspace: IElmWorkspace,
+    program: IProgram,
   ): IDiagnostic[] {
     const diagnostics: IDiagnostic[] = [];
 
@@ -1009,7 +1007,7 @@ export class ElmLsDiagnostics {
     recordTypes.forEach((recordType) => {
       let isSingleField = true;
       if (recordType.parent?.type === "type_ref" && recordType.parent.parent) {
-        const type = elmWorkspace.getTypeChecker().findType(recordType.parent);
+        const type = program.getTypeChecker().findType(recordType.parent);
 
         const singleField = recordType.descendantsOfType(
           "lower_case_identifier",
diff --git a/src/providers/diagnostics/elmMakeDiagnostics.ts b/src/providers/diagnostics/elmMakeDiagnostics.ts
index 49fd37a..60915c0 100644
--- a/src/providers/diagnostics/elmMakeDiagnostics.ts
+++ b/src/providers/diagnostics/elmMakeDiagnostics.ts
@@ -10,8 +10,8 @@ import {
   TextEdit,
 } from "vscode-languageserver";
 import { URI } from "vscode-uri";
-import { ITreeContainer } from "../../forest";
-import * as utils from "../../util/elmUtils";
+import { ISourceFile } from "../../compiler/forest";
+import * as utils from "../../compiler/utils/elmUtils";
 import { ElmWorkspaceMatcher } from "../../util/elmWorkspaceMatcher";
 import { Settings } from "../../util/settings";
 import { TreeUtils } from "../../util/treeUtils";
@@ -19,8 +19,8 @@ import { NonEmptyArray, Utils } from "../../util/utils";
 import { IDiagnostic, IElmIssue } from "./diagnosticsProvider";
 import { ElmDiagnosticsHelper } from "./elmDiagnosticsHelper";
 import execa = require("execa");
-import { IElmWorkspace } from "../../elmWorkspace";
 import { ElmToolingJsonManager } from "../../elmToolingJsonManager";
+import { IProgram } from "../../compiler/program";
 
 const ELM_MAKE = "Elm";
 export const NAMING_ERROR = "NAMING ERROR";
@@ -77,7 +77,7 @@ export class ElmMakeDiagnostics {
   }
 
   public createDiagnostics = async (
-    sourceFile: ITreeContainer,
+    sourceFile: ISourceFile,
   ): Promise<Map<string, IDiagnostic[]>> => {
     const filePath = URI.parse(sourceFile.uri);
     const workspaceRootPath = this.elmWorkspaceMatcher
@@ -114,8 +114,8 @@ export class ElmMakeDiagnostics {
   ): CodeAction[] {
     const result: CodeAction[] = [];
 
-    const elmWorkspace = this.elmWorkspaceMatcher.getProgramFor(URI.parse(uri));
-    const forest = elmWorkspace.getForest();
+    const program = this.elmWorkspaceMatcher.getProgramFor(URI.parse(uri));
+    const forest = program.getForest();
 
     const sourceTree = forest.getByUri(uri);
 
@@ -175,19 +175,14 @@ export class ElmMakeDiagnostics {
         const matches = regex.exec(diagnostic.message);
         if (matches !== null) {
           result.push(
-            ...this.addCaseQuickfixes(
-              sourceTree,
-              diagnostic,
-              uri,
-              elmWorkspace,
-            ),
+            ...this.addCaseQuickfixes(sourceTree, diagnostic, uri, program),
           );
         }
       } else if (
         diagnostic.message.startsWith("MISSING PATTERNS - This `case`")
       ) {
         result.push(
-          ...this.addCaseQuickfixes(sourceTree, diagnostic, uri, elmWorkspace),
+          ...this.addCaseQuickfixes(sourceTree, diagnostic, uri, program),
         );
       }
     });
@@ -195,13 +190,13 @@ export class ElmMakeDiagnostics {
   }
 
   private addCaseQuickfixes(
-    sourceTree: ITreeContainer | undefined,
+    sourceFile: ISourceFile | undefined,
     diagnostic: IDiagnostic,
     uri: string,
-    elmWorkspace: IElmWorkspace,
+    program: IProgram,
   ): CodeAction[] {
     const result = [];
-    const valueNode = sourceTree?.tree.rootNode.namedDescendantForPosition(
+    const valueNode = sourceFile?.tree.rootNode.namedDescendantForPosition(
       {
         column: diagnostic.range.start.character,
         row: diagnostic.range.start.line,
@@ -224,8 +219,8 @@ export class ElmMakeDiagnostics {
 
         const typeDeclarationNode = TreeUtils.getTypeAliasOfCase(
           valueNode.namedChildren[1].firstNamedChild!.firstNamedChild!,
-          sourceTree!,
-          elmWorkspace,
+          sourceFile!,
+          program,
         );
 
         if (typeDeclarationNode) {
@@ -339,7 +334,7 @@ export class ElmMakeDiagnostics {
 
   private async checkForErrors(
     workspaceRootPath: string,
-    sourceFile: ITreeContainer,
+    sourceFile: ISourceFile,
   ): Promise<IElmIssue[]> {
     const settings = await this.settings.getClientSettings();
 
diff --git a/src/providers/documentFormatingProvider.ts b/src/providers/documentFormatingProvider.ts
index 1aaec89..62bcd14 100644
--- a/src/providers/documentFormatingProvider.ts
+++ b/src/providers/documentFormatingProvider.ts
@@ -7,7 +7,7 @@ import {
 import { URI } from "vscode-uri";
 import { DiagnosticsProvider } from ".";
 import * as Diff from "../util/diff";
-import { execCmdSync } from "../util/elmUtils";
+import { execCmdSync } from "../compiler/utils/elmUtils";
 import { ElmWorkspaceMatcher } from "../util/elmWorkspaceMatcher";
 import { Settings } from "../util/settings";
 import { TextDocumentEvents } from "../util/textDocumentEvents";
diff --git a/src/providers/handlers/fileEventsHandler.ts b/src/providers/handlers/fileEventsHandler.ts
index ee98a03..f79276a 100644
--- a/src/providers/handlers/fileEventsHandler.ts
+++ b/src/providers/handlers/fileEventsHandler.ts
@@ -11,9 +11,9 @@ import {
 } from "vscode-languageserver";
 import { TextEdit } from "vscode-languageserver-textdocument";
 import { URI } from "vscode-uri";
-import { IElmWorkspace } from "../../elmWorkspace";
+import { IProgram } from "../../compiler/program";
 import { PositionUtil } from "../../positionUtil";
-import { getModuleName } from "../../util/elmUtils";
+import { getModuleName } from "../../compiler/utils/elmUtils";
 import { ElmWorkspaceMatcher } from "../../util/elmWorkspaceMatcher";
 import { RefactorEditUtils } from "../../util/refactorEditUtils";
 import { RenameUtils } from "../../util/renameUtils";
@@ -162,11 +162,9 @@ export class FileEventsHandler {
 
   private getModuleNameFromFile(
     uri: string,
-    elmWorkspace: IElmWorkspace,
+    program: IProgram,
   ): string | undefined {
-    const sourceDir = elmWorkspace.getSourceDirectoryOfFile(
-      URI.parse(uri).fsPath,
-    );
+    const sourceDir = program.getSourceDirectoryOfFile(URI.parse(uri).fsPath);
 
     // The file is not in a source dir (shouldn't happen)
     if (!sourceDir) {
diff --git a/src/providers/handlers/moveRefactoringHandler.ts b/src/providers/handlers/moveRefactoringHandler.ts
index 38ee8c1..ecfef95 100644
--- a/src/providers/handlers/moveRefactoringHandler.ts
+++ b/src/providers/handlers/moveRefactoringHandler.ts
@@ -10,7 +10,7 @@ import {
 } from "../../protocol";
 import { ElmWorkspaceMatcher } from "../../util/elmWorkspaceMatcher";
 import { RefactorEditUtils } from "../../util/refactorEditUtils";
-import { References } from "../../util/references";
+import { References } from "../../compiler/references";
 import { TreeUtils } from "../../util/treeUtils";
 
 export class MoveRefactoringHandler {
diff --git a/src/providers/hoverProvider.ts b/src/providers/hoverProvider.ts
index 5de50c3..16de1d9 100644
--- a/src/providers/hoverProvider.ts
+++ b/src/providers/hoverProvider.ts
@@ -8,7 +8,7 @@ import {
 import { URI } from "vscode-uri";
 import { SyntaxNode } from "web-tree-sitter";
 import { DiagnosticsProvider } from ".";
-import { getEmptyTypes } from "../util/elmUtils";
+import { getEmptyTypes } from "../compiler/utils/elmUtils";
 import { ElmWorkspaceMatcher } from "../util/elmWorkspaceMatcher";
 import { HintHelper } from "../util/hintHelper";
 import { NodeType, TreeUtils } from "../util/treeUtils";
@@ -38,18 +38,15 @@ export class HoverProvider {
     this.connection.console.info(`A hover was requested`);
 
     const checker = params.program.getTypeChecker();
-    const treeContainer = params.sourceFile;
+    const sourceFile = params.sourceFile;
 
-    if (treeContainer) {
+    if (sourceFile) {
       const nodeAtPosition = TreeUtils.getNamedDescendantForPosition(
-        treeContainer.tree.rootNode,
+        sourceFile.tree.rootNode,
         params.position,
       );
 
-      let definitionNode = checker.findDefinition(
-        nodeAtPosition,
-        treeContainer,
-      );
+      let definitionNode = checker.findDefinition(nodeAtPosition, sourceFile);
 
       if (definitionNode) {
         if (
@@ -64,7 +61,7 @@ export class HoverProvider {
 
         const typeString = checker.typeToString(
           checker.findType(definitionNode.node),
-          treeContainer,
+          sourceFile,
         );
 
         return this.createMarkdownHoverFromDefinition(
diff --git a/src/providers/referencesProvider.ts b/src/providers/referencesProvider.ts
index c1ec973..bf216cb 100644
--- a/src/providers/referencesProvider.ts
+++ b/src/providers/referencesProvider.ts
@@ -8,7 +8,7 @@ import {
 } from "vscode-languageserver";
 import { URI } from "vscode-uri";
 import { ElmWorkspaceMatcher } from "../util/elmWorkspaceMatcher";
-import { References } from "../util/references";
+import { References } from "../compiler/references";
 import { TreeUtils } from "../util/treeUtils";
 import { IReferenceParams } from "./paramsExtensions";
 
@@ -32,18 +32,15 @@ export class ReferencesProvider {
 
     const checker = params.program.getTypeChecker();
 
-    const treeContainer = params.sourceFile;
+    const sourceFile = params.sourceFile;
 
-    if (treeContainer) {
+    if (sourceFile) {
       const nodeAtPosition = TreeUtils.getNamedDescendantForPosition(
-        treeContainer.tree.rootNode,
+        sourceFile.tree.rootNode,
         params.position,
       );
 
-      const definitionNode = checker.findDefinition(
-        nodeAtPosition,
-        treeContainer,
-      );
+      const definitionNode = checker.findDefinition(nodeAtPosition, sourceFile);
 
       const references = References.find(definitionNode, params.program);
 
diff --git a/src/providers/renameProvider.ts b/src/providers/renameProvider.ts
index 7704088..bee4fe3 100644
--- a/src/providers/renameProvider.ts
+++ b/src/providers/renameProvider.ts
@@ -13,7 +13,7 @@ import {
 } from "vscode-languageserver";
 import { URI } from "vscode-uri";
 import { SyntaxNode } from "web-tree-sitter";
-import { IElmWorkspace } from "../elmWorkspace";
+import { IProgram } from "../compiler/program";
 import { ElmWorkspaceMatcher } from "../util/elmWorkspaceMatcher";
 import { RenameUtils } from "../util/renameUtils";
 import { TreeUtils } from "../util/treeUtils";
@@ -195,7 +195,7 @@ export class RenameProvider {
           }[];
         }
       | undefined,
-    elmWorkspace: IElmWorkspace,
+    program: IProgram,
     params: RenameParams,
     newName: string,
   ): RenameFile | undefined {
@@ -204,7 +204,7 @@ export class RenameProvider {
     ) {
       const newUri = this.generateUriFromModuleName(
         newName,
-        elmWorkspace,
+        program,
         params.textDocument.uri,
       );
 
@@ -241,10 +241,10 @@ export class RenameProvider {
 
   private generateUriFromModuleName(
     moduleName: string,
-    elmWorkspace: IElmWorkspace,
+    program: IProgram,
     file: string,
   ): URI | undefined {
-    const sourceDir = elmWorkspace.getSourceDirectoryOfFile(file);
+    const sourceDir = program.getSourceDirectoryOfFile(file);
 
     // The file is not in a source dir (shouldn't happen)
     if (!sourceDir) {
diff --git a/src/providers/workspaceSymbolProvider.ts b/src/providers/workspaceSymbolProvider.ts
index c51e400..4963c2b 100644
--- a/src/providers/workspaceSymbolProvider.ts
+++ b/src/providers/workspaceSymbolProvider.ts
@@ -5,15 +5,15 @@ import {
   WorkspaceSymbolParams,
 } from "vscode-languageserver";
 import { SyntaxNode } from "web-tree-sitter";
-import { IElmWorkspace } from "../elmWorkspace";
+import { IProgram } from "../compiler/program";
 import { SymbolInformationTranslator } from "../util/symbolTranslator";
 
 export class WorkspaceSymbolProvider {
   private readonly connection: Connection;
-  private readonly elmWorkspaces: IElmWorkspace[];
+  private readonly elmWorkspaces: IProgram[];
 
   constructor() {
-    this.elmWorkspaces = container.resolve<IElmWorkspace[]>("ElmWorkspaces");
+    this.elmWorkspaces = container.resolve<IProgram[]>("ElmWorkspaces");
     this.connection = container.resolve<Connection>("Connection");
     this.connection.onWorkspaceSymbol(this.workspaceSymbolRequest);
   }
@@ -27,8 +27,8 @@ export class WorkspaceSymbolProvider {
       SymbolInformation[]
     >();
 
-    this.elmWorkspaces.forEach((elmWorkspace) => {
-      elmWorkspace.getForest().treeMap.forEach((tree) => {
+    this.elmWorkspaces.forEach((program) => {
+      program.getForest().treeMap.forEach((tree) => {
         if (!tree.writeable) {
           return;
         }
diff --git a/src/server.ts b/src/server.ts
index 7d25921..164477e 100644
--- a/src/server.ts
+++ b/src/server.ts
@@ -10,7 +10,7 @@ import {
 import { URI, Utils } from "vscode-uri";
 import { CapabilityCalculator } from "./capabilityCalculator";
 import { ElmToolingJsonManager } from "./elmToolingJsonManager";
-import { ElmWorkspace, IElmWorkspace } from "./elmWorkspace";
+import { Program, IProgram } from "./compiler/program";
 import {
   CodeActionProvider,
   CodeLensProvider,
@@ -72,9 +72,9 @@ export class Server implements ILanguageServer {
           `Found ${topLevelElmJsons.size} unique elmWorkspaces for workspace ${globUri}`,
         );
 
-        const elmWorkspaces: ElmWorkspace[] = [];
+        const elmWorkspaces: Program[] = [];
         topLevelElmJsons.forEach((elmWorkspace) => {
-          elmWorkspaces.push(new ElmWorkspace(elmWorkspace));
+          elmWorkspaces.push(new Program(elmWorkspace));
         });
         container.register("ElmWorkspaces", {
           useValue: elmWorkspaces,
@@ -104,7 +104,7 @@ export class Server implements ILanguageServer {
 
   public async init(): Promise<void> {
     this.progress.begin("Indexing Elm", 0);
-    const elmWorkspaces = container.resolve<IElmWorkspace[]>("ElmWorkspaces");
+    const elmWorkspaces = container.resolve<IProgram[]>("ElmWorkspaces");
     await Promise.all(
       elmWorkspaces
         .map((ws) => ({ ws, indexedPercent: 0 }))
diff --git a/src/util/elmWorkspaceMatcher.ts b/src/util/elmWorkspaceMatcher.ts
index ce1f70a..01452c4 100644
--- a/src/util/elmWorkspaceMatcher.ts
+++ b/src/util/elmWorkspaceMatcher.ts
@@ -1,13 +1,13 @@
 import { container } from "tsyringe";
 import { CancellationToken } from "vscode-languageserver";
 import { URI } from "vscode-uri";
-import { IElmWorkspace } from "../elmWorkspace";
-import { ITreeContainer } from "../forest";
+import { IProgram } from "../compiler/program";
+import { ISourceFile } from "../compiler/forest";
 import { NoWorkspaceContainsError } from "./noWorkspaceContainsError";
 
 export interface IParams {
-  program: IElmWorkspace;
-  sourceFile: ITreeContainer;
+  program: IProgram;
+  sourceFile: ISourceFile;
 }
 
 /**
@@ -17,7 +17,7 @@ export interface IParams {
  * which has the program and sourceFile.
  */
 export class ElmWorkspaceMatcher<ParamType> {
-  private elmWorkspaces: IElmWorkspace[];
+  private elmWorkspaces: IProgram[];
 
   constructor(private getUriFor: (param: ParamType) => URI) {
     this.elmWorkspaces = container.resolve("ElmWorkspaces");
@@ -45,8 +45,8 @@ export class ElmWorkspaceMatcher<ParamType> {
   public handleResolve<ResultType>(
     handler: (
       param: ParamType,
-      program: IElmWorkspace,
-      sourceFile: ITreeContainer,
+      program: IProgram,
+      sourceFile: ISourceFile,
       token?: CancellationToken,
     ) => ResultType,
   ): (param: ParamType, token?: CancellationToken) => ResultType {
@@ -61,25 +61,22 @@ export class ElmWorkspaceMatcher<ParamType> {
     };
   }
 
-  public getProgramFor(param: ParamType): IElmWorkspace {
+  public getProgramFor(param: ParamType): IProgram {
     const uri = this.getUriFor(param);
-    const workspace =
-      // first look for a workspace where the file has been parsed to a tree
+    const program =
+      // first look for a program where the file has been parsed to a tree
       this.elmWorkspaces.find((ws) => ws.hasDocument(uri)) ||
-      // fallback: find a workspace where the file is in the source-directories
+      // fallback: find a program where the file is in the source-directories
       this.elmWorkspaces.find((ws) => ws.isInSourceDirectory(uri.fsPath));
 
-    if (!workspace) {
+    if (!program) {
       throw new NoWorkspaceContainsError(this.getUriFor(param));
     }
 
-    return workspace;
+    return program;
   }
 
-  public getSourceFileFor(
-    param: ParamType,
-    program: IElmWorkspace,
-  ): ITreeContainer {
+  public getSourceFileFor(param: ParamType, program: IProgram): ISourceFile {
     const uri = this.getUriFor(param).toString();
 
     return program.getForest().getByUri(uri)!;
diff --git a/src/util/importUtils.ts b/src/util/importUtils.ts
index a90b48f..7a2f6d1 100644
--- a/src/util/importUtils.ts
+++ b/src/util/importUtils.ts
@@ -1,8 +1,8 @@
-import { ITreeContainer } from "../forest";
 import { comparePackageRanking } from "../providers/ranking";
+import { ISourceFile } from "../compiler/forest";
 import { TreeUtils, NodeType } from "./treeUtils";
 import { SyntaxNode } from "web-tree-sitter";
-import { IElmWorkspace } from "../elmWorkspace";
+import { IProgram } from "../compiler/program";
 
 export interface IPossibleImport {
   module: string;
@@ -15,8 +15,8 @@ export interface IPossibleImport {
 
 export class ImportUtils {
   public static getPossibleImports(
-    program: IElmWorkspace,
-    sourceFile: ITreeContainer,
+    program: IProgram,
+    sourceFile: ISourceFile,
   ): IPossibleImport[] {
     const currentModule = sourceFile?.moduleName;
 
@@ -62,9 +62,7 @@ export class ImportUtils {
     return exposedValues;
   }
 
-  public static getPossibleImportsOfTree(
-    tree: ITreeContainer,
-  ): IPossibleImport[] {
+  public static getPossibleImportsOfTree(tree: ISourceFile): IPossibleImport[] {
     const exposedValues: IPossibleImport[] = [];
 
     tree.exposing?.forEach((exposed) => {
diff --git a/src/util/renameUtils.ts b/src/util/renameUtils.ts
index 7023f95..9998fec 100644
--- a/src/util/renameUtils.ts
+++ b/src/util/renameUtils.ts
@@ -1,12 +1,12 @@
-import { IElmWorkspace } from "../elmWorkspace";
+import { IProgram } from "../compiler/program";
 import { Position, ResponseError } from "vscode-languageserver";
 import { SyntaxNode } from "web-tree-sitter";
-import { References } from "./references";
+import { References } from "../compiler/references";
 import { TreeUtils } from "./treeUtils";
 
 export class RenameUtils {
   static getRenameAffectedNodes(
-    program: IElmWorkspace,
+    program: IProgram,
     uri: string,
     position: Position,
   ):
diff --git a/src/util/treeUtils.ts b/src/util/treeUtils.ts
index e811167..4da75a3 100644
--- a/src/util/treeUtils.ts
+++ b/src/util/treeUtils.ts
@@ -1,13 +1,13 @@
 import { Position } from "vscode-languageserver";
 import { SyntaxNode, Tree } from "web-tree-sitter";
-import { ITreeContainer } from "../forest";
+import { ISourceFile } from "../compiler/forest";
 import { comparePosition } from "../positionUtil";
-import { TRecord, Type } from "./types/typeInference";
-import { IElmWorkspace } from "../elmWorkspace";
+import { TRecord, Type } from "../compiler/typeInference";
+import { IProgram } from "../compiler/program";
 import {
   EFunctionCallExpr,
   mapSyntaxNodeToExpression,
-} from "./types/expressionTree";
+} from "../compiler/utils/expressionTree";
 import { Range } from "vscode-languageserver-textdocument";
 
 export type NodeType =
@@ -291,12 +291,10 @@ export class TreeUtils {
   }
 
   public static findOperator(
-    treeContainer: ITreeContainer,
+    sourceFile: ISourceFile,
     operatorName: string,
   ): SyntaxNode | undefined {
-    const rootSymbols = treeContainer.symbolLinks?.get(
-      treeContainer.tree.rootNode,
-    );
+    const rootSymbols = sourceFile.symbolLinks?.get(sourceFile.tree.rootNode);
 
     const operatorNode = rootSymbols?.get(operatorName)?.node;
 
@@ -523,9 +521,9 @@ export class TreeUtils {
 
   public static getRecordTypeOfFunctionRecordParameter(
     node: SyntaxNode | undefined,
-    elmWorkspace: IElmWorkspace,
+    program: IProgram,
   ): TRecord | undefined {
-    const checker = elmWorkspace.getTypeChecker();
+    const checker = program.getTypeChecker();
     if (
       node?.parent?.type === "function_call_expr" &&
       node.parent.firstNamedChild
@@ -552,29 +550,23 @@ export class TreeUtils {
 
   public static getTypeAliasOfRecordField(
     node: SyntaxNode | undefined,
-    treeContainer: ITreeContainer,
-    elmWorkspace: IElmWorkspace,
+    sourceFile: ISourceFile,
+    program: IProgram,
   ): { node: SyntaxNode; uri: string } | undefined {
     const fieldName = node?.parent?.firstNamedChild?.text;
 
-    let recordType = TreeUtils.getTypeAliasOfRecord(
-      node,
-      treeContainer,
-      elmWorkspace,
-    );
+    let recordType = TreeUtils.getTypeAliasOfRecord(node, sourceFile, program);
 
     while (!recordType && node?.parent?.parent) {
       node = node.parent.parent;
       recordType = TreeUtils.getTypeAliasOfRecordField(
         node,
-        treeContainer,
-        elmWorkspace,
+        sourceFile,
+        program,
       );
     }
 
-    const recordTypeTree = elmWorkspace
-      .getForest()
-      .getByUri(recordType?.uri ?? "");
+    const recordTypeTree = program.getForest().getByUri(recordType?.uri ?? "");
 
     if (recordType && recordTypeTree) {
       const fieldTypes = TreeUtils.descendantsOfType(
@@ -601,7 +593,7 @@ export class TreeUtils {
           );
 
           if (typeNode.length > 0) {
-            const typeAliasNode = elmWorkspace
+            const typeAliasNode = program
               .getTypeChecker()
               .findDefinition(typeNode[0], recordTypeTree);
 
@@ -616,18 +608,16 @@ export class TreeUtils {
 
   public static getTypeAliasOfCase(
     type: SyntaxNode | undefined,
-    treeContainer: ITreeContainer,
-    elmWorkspace: IElmWorkspace,
+    sourceFile: ISourceFile,
+    program: IProgram,
   ): { node: SyntaxNode; uri: string } | undefined {
     if (type) {
-      const definitionNode = elmWorkspace
+      const definitionNode = program
         .getTypeChecker()
-        .findDefinition(type, treeContainer);
+        .findDefinition(type, sourceFile);
 
       if (definitionNode) {
-        const definitionTree = elmWorkspace
-          .getForest()
-          .getByUri(definitionNode.uri);
+        const definitionTree = program.getForest().getByUri(definitionNode.uri);
 
         let aliasNode;
         if (definitionNode.nodeType === "FunctionParameter") {
@@ -654,7 +644,7 @@ export class TreeUtils {
           );
 
           if (childNode.length > 0) {
-            const typeNode = elmWorkspace
+            const typeNode = program
               .getTypeChecker()
               .findDefinition(childNode[0], definitionTree);
 
@@ -669,8 +659,8 @@ export class TreeUtils {
 
   public static getTypeAliasOfRecord(
     node: SyntaxNode | undefined,
-    treeContainer: ITreeContainer,
-    elmWorkspace: IElmWorkspace,
+    sourceFile: ISourceFile,
+    program: IProgram,
   ): { node: SyntaxNode; uri: string } | undefined {
     if (node?.parent?.parent) {
       let type: SyntaxNode | undefined | null =
@@ -696,15 +686,15 @@ export class TreeUtils {
       }
 
       if (type) {
-        const definitionNode = elmWorkspace
+        const definitionNode = program
           .getTypeChecker()
           .findDefinition(
             type.firstNamedChild ? type.firstNamedChild : type,
-            treeContainer,
+            sourceFile,
           );
 
         if (definitionNode) {
-          const definitionTree = elmWorkspace
+          const definitionTree = program
             .getForest()
             .getByUri(definitionNode.uri);
 
@@ -736,7 +726,7 @@ export class TreeUtils {
             );
 
             if (childNode.length > 0) {
-              const typeNode = elmWorkspace
+              const typeNode = program
                 .getTypeChecker()
                 .findDefinition(childNode[0], definitionTree);
 
@@ -812,7 +802,7 @@ export class TreeUtils {
   }
 
   public static getNamedDescendantForRange(
-    sourceFile: ITreeContainer,
+    sourceFile: ISourceFile,
     range: Range,
   ): SyntaxNode {
     return sourceFile.tree.rootNode.namedDescendantForPosition(


Test patch:
diff --git a/test/codeActionTests/codeActionTestBase.ts b/test/codeActionTests/codeActionTestBase.ts
index 69569c1..8ca116e 100644
--- a/test/codeActionTests/codeActionTestBase.ts
+++ b/test/codeActionTests/codeActionTestBase.ts
@@ -1,7 +1,7 @@
 import { container } from "tsyringe";
 import { CodeAction } from "vscode-languageserver";
 import { URI } from "vscode-uri";
-import { IElmWorkspace } from "../../src/elmWorkspace";
+import { IProgram } from "../../src/compiler/program";
 import {
   CodeActionProvider,
   convertFromAnalyzerDiagnostic,
@@ -85,7 +85,7 @@ export async function testCodeAction(
 
   if (!sourceFile) throw new Error("Getting tree failed");
 
-  const workspaces = container.resolve<IElmWorkspace[]>("ElmWorkspaces");
+  const workspaces = container.resolve<IProgram[]>("ElmWorkspaces");
   workspaces.splice(0, workspaces.length);
   workspaces.push(program);
 
diff --git a/test/constraint.test.ts b/test/constraint.test.ts
index f34d3d3..c994d09 100644
--- a/test/constraint.test.ts
+++ b/test/constraint.test.ts
@@ -4,7 +4,7 @@ import {
   IVersion,
   parseConstraint,
   versionSatifiesConstraint,
-} from "../src/util/elmUtils";
+} from "../src/compiler/utils/elmUtils";
 
 describe("constraint test", () => {
   function v(
diff --git a/test/diagnosticTests/elmDiagnostics.test.ts b/test/diagnosticTests/elmDiagnostics.test.ts
index a5df029..1e4e7c8 100644
--- a/test/diagnosticTests/elmDiagnostics.test.ts
+++ b/test/diagnosticTests/elmDiagnostics.test.ts
@@ -8,7 +8,7 @@ import {
   Diagnostics,
   error,
   IDiagnosticMessage,
-} from "../../src/util/types/diagnostics";
+} from "../../src/compiler/diagnostics";
 import { Utils } from "../../src/util/utils";
 import {
   getSourceFiles,
@@ -92,19 +92,19 @@ describe("test elm diagnostics", () => {
     const testUri = URI.file(baseUri + "Test.elm").toString();
 
     const program = await treeParser.getProgram(result.sources);
-    const treeContainer = program.getForest().getByUri(testUri);
+    const sourceFile = program.getForest().getByUri(testUri);
 
-    if (!treeContainer) throw new Error("Getting tree failed");
+    if (!sourceFile) throw new Error("Getting tree failed");
 
     const diagnostics: Diagnostic[] = [];
 
-    program.getForest().treeMap.forEach((treeContainer) => {
-      if (!treeContainer.uri.includes("Basic")) {
-        diagnostics.push(...program.getSyntacticDiagnostics(treeContainer));
-        diagnostics.push(...program.getSemanticDiagnostics(treeContainer));
+    program.getForest().treeMap.forEach((sourceFile) => {
+      if (!sourceFile.uri.includes("Basic")) {
+        diagnostics.push(...program.getSyntacticDiagnostics(sourceFile));
+        diagnostics.push(...program.getSemanticDiagnostics(sourceFile));
 
         if (!disableSuggestionDiagnostics) {
-          diagnostics.push(...program.getSuggestionDiagnostics(treeContainer));
+          diagnostics.push(...program.getSuggestionDiagnostics(sourceFile));
         }
       }
     });
diff --git a/test/diagnostics.ts b/test/diagnostics.ts
index cdb288b..f507d3a 100644
--- a/test/diagnostics.ts
+++ b/test/diagnostics.ts
@@ -1,13 +1,13 @@
 import "reflect-metadata";
 import { container } from "tsyringe";
 import { URI } from "vscode-uri";
-import { ElmWorkspace } from "../src/elmWorkspace";
+import { Program } from "../src/compiler/program";
 import * as path from "path";
 import { Settings } from "../src/util/settings";
 import Parser from "web-tree-sitter";
 import { spawnSync } from "child_process";
 import { readFileSync } from "fs";
-import { Diagnostic } from "../src/util/types/diagnostics";
+import { Diagnostic } from "../src/compiler/diagnostics";
 import { performance } from "perf_hooks";
 import { createProgramHost } from "./utils/sourceTreeParser";
 
@@ -54,14 +54,14 @@ export async function runDiagnosticTests(uri: string): Promise<void> {
   const pathUri = URI.file(uri);
 
   try {
-    let elmWorkspace = new ElmWorkspace(pathUri, createProgramHost());
-    await elmWorkspace.init(() => {
+    let program = new Program(pathUri, createProgramHost());
+    await program.init(() => {
       //
     });
 
     const start = performance.now();
     let diagnostics: Diagnostic[] = [];
-    for (const sourceFile of elmWorkspace.getForest().treeMap.values()) {
+    for (const sourceFile of program.getForest().treeMap.values()) {
       if (!sourceFile.writeable) {
         continue;
       }
@@ -74,8 +74,8 @@ export async function runDiagnosticTests(uri: string): Promise<void> {
 
       diagnostics.push(
         ...[
-          ...elmWorkspace.getSyntacticDiagnostics(sourceFile),
-          ...elmWorkspace.getSemanticDiagnostics(sourceFile),
+          ...program.getSyntacticDiagnostics(sourceFile),
+          ...program.getSemanticDiagnostics(sourceFile),
         ].filter((d) => !d.uri.includes("test")),
       );
     }
@@ -97,10 +97,10 @@ export async function runDiagnosticTests(uri: string): Promise<void> {
       // process.exitCode = 1;
     }
 
-    elmWorkspace.getForest().treeMap.forEach((sourceFile) => {
+    program.getForest().treeMap.forEach((sourceFile) => {
       sourceFile.tree.delete();
     });
-    elmWorkspace = undefined!;
+    program = undefined!;
   } catch (e) {
     console.log(e);
     failed.push(path.basename(uri));
diff --git a/test/fileEventsHandler.test.ts b/test/fileEventsHandler.test.ts
index 6a9466a..f1bec32 100644
--- a/test/fileEventsHandler.test.ts
+++ b/test/fileEventsHandler.test.ts
@@ -16,7 +16,7 @@ import {
 } from "vscode-languageserver";
 import { TextEdit } from "vscode-languageserver-textdocument";
 import { URI } from "vscode-uri";
-import { IElmWorkspace } from "../src/elmWorkspace";
+import { IProgram } from "../src/compiler/program";
 import { FileEventsHandler } from "../src/providers/handlers/fileEventsHandler";
 import { getSourceFiles } from "./utils/sourceParser";
 import { baseUri, SourceTreeParser } from "./utils/sourceTreeParser";
@@ -56,12 +56,12 @@ describe("fileEventsHandler", () => {
 
   const token = new CancellationTokenSource().token;
 
-  async function createProgram(source: string): Promise<IElmWorkspace> {
+  async function createProgram(source: string): Promise<IProgram> {
     await treeParser.init();
     new FileEventsHandler();
 
     const program = await treeParser.getProgram(getSourceFiles(source));
-    const workspaces = container.resolve<IElmWorkspace[]>("ElmWorkspaces");
+    const workspaces = container.resolve<IProgram[]>("ElmWorkspaces");
     workspaces.splice(0, workspaces.length);
     workspaces.push(program);
 
diff --git a/test/performance.ts b/test/performance.ts
index 38587e5..b90eea4 100644
--- a/test/performance.ts
+++ b/test/performance.ts
@@ -1,23 +1,20 @@
 import "reflect-metadata";
 import { container } from "tsyringe";
 import { URI } from "vscode-uri";
-import { ElmWorkspace } from "../src/elmWorkspace";
-import { importsTime, resetImportsTime } from "../src/imports";
+import { Program } from "../src/compiler/program";
+import { importsTime, resetImportsTime } from "../src/compiler/imports";
 import {
   definitionTime,
   mappingTime,
   resetDefinitionAndMappingTime,
-} from "../src/util/types/expressionTree";
-import { bindTime, resetBindTime } from "../src/util/types/typeChecker";
-import { inferTime, resetInferTime } from "../src/util/types/typeInference";
+} from "../src/compiler/utils/expressionTree";
+import { bindTime, resetBindTime } from "../src/compiler/typeChecker";
+import { inferTime, resetInferTime } from "../src/compiler/typeInference";
 import * as path from "path";
 import { argv } from "process";
 import { Settings } from "../src/util/settings";
 import Parser from "web-tree-sitter";
-import {
-  replaceTime,
-  resetReplaceTime,
-} from "../src/util/types/typeReplacement";
+import { replaceTime, resetReplaceTime } from "../src/compiler/typeReplacement";
 import {
   getCancellationFilePath,
   FileBasedCancellationTokenSource,
@@ -75,8 +72,8 @@ export async function runPerformanceTests(uri: string): Promise<void> {
   const numTimes = 10;
 
   for (let i = 0; i < numTimes; i++) {
-    const elmWorkspace = new ElmWorkspace(pathUri, createProgramHost());
-    await elmWorkspace.init(() => {
+    const program = new Program(pathUri, createProgramHost());
+    await program.init(() => {
       //
     });
 
@@ -89,10 +86,10 @@ export async function runPerformanceTests(uri: string): Promise<void> {
 
     const token = new ThrottledCancellationToken(cancellationToken.token);
 
-    elmWorkspace
+    program
       .getForest()
-      .treeMap.forEach((treeContainer) =>
-        elmWorkspace.getTypeChecker().getDiagnostics(treeContainer, token),
+      .treeMap.forEach((sourceFile) =>
+        program.getTypeChecker().getDiagnostics(sourceFile, token),
       );
 
     addTime("BINDING   :", bindTime);
diff --git a/test/solver.test.ts b/test/solver.test.ts
index b0a2dd9..27fe4ed 100644
--- a/test/solver.test.ts
+++ b/test/solver.test.ts
@@ -1,10 +1,10 @@
-import { IElmPackageCache, IPackage } from "../src/elmPackageCache";
+import { IElmPackageCache, IPackage } from "../src/compiler/elmPackageCache";
 import {
   IConstraint,
   IVersion,
   parseConstraint,
   solveDependencies,
-} from "../src/util/elmUtils";
+} from "../src/compiler/utils/elmUtils";
 
 describe("module resolution solver", () => {
   function v(
diff --git a/test/utils/sourceTreeParser.ts b/test/utils/sourceTreeParser.ts
index 0876066..8f3a043 100644
--- a/test/utils/sourceTreeParser.ts
+++ b/test/utils/sourceTreeParser.ts
@@ -5,11 +5,7 @@ import { promisify } from "util";
 import { TextEdit } from "vscode-languageserver-textdocument";
 import { URI } from "vscode-uri";
 import Parser from "web-tree-sitter";
-import {
-  ElmWorkspace,
-  IElmWorkspace,
-  IProgramHost,
-} from "../../src/elmWorkspace";
+import { Program, IProgram, IProgramHost } from "../../src/compiler/program";
 import * as path from "../../src/util/path";
 import { Utils } from "../../src/util/utils";
 
@@ -33,9 +29,7 @@ export class SourceTreeParser {
     container.resolve<Parser>("Parser").setLanguage(language);
   }
 
-  public async getProgram(sources: {
-    [K: string]: string;
-  }): Promise<IElmWorkspace> {
+  public async getProgram(sources: { [K: string]: string }): Promise<IProgram> {
     const readFile = (uri: string): string => {
       if (uri.endsWith("elm.json")) {
         return `
@@ -72,7 +66,7 @@ export class SourceTreeParser {
       }
     }
 
-    const program = new ElmWorkspace(URI.file(baseUri), {
+    const program = new Program(URI.file(baseUri), {
       readFile: (uri: string): Promise<string> =>
         Promise.resolve(readFile(uri)),
       readDirectory: (uri: string): Promise<string[]> => {