Skip to content

Commit 3c5afad

Browse files
authored
Typed modules (#1739)
* Initial prototype of typed modules * Using "placeholder class" for representing module types * Enhance module imports * Fix support for untyped modules * Fix support for multiple typed module imports * Remove redundant log statements * Update type of loadedModulesTypes * Handle built-in functions as special cases * Add typed modules tests * Load types only when variant is Typed * Revert temporary fixes * Fix bug such that untyped module should not be type-checked * Changed code according to code review
1 parent a50f7dc commit 3c5afad

File tree

7 files changed

+472
-20
lines changed

7 files changed

+472
-20
lines changed

src/createContext.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,8 @@ const createNativeStorage = (): NativeStorage => ({
152152
gpu: new Map(Object.entries(gpu_lib)),
153153
maxExecTime: JSSLANG_PROPERTIES.maxExecTime,
154154
evaller: null,
155-
loadedModules: {}
155+
loadedModules: {},
156+
loadedModuleTypes: {}
156157
})
157158

158159
export const createEmptyContext = <T>(

src/errors/typeErrors.ts

+23
Original file line numberDiff line numberDiff line change
@@ -735,3 +735,26 @@ export class DuplicateTypeAliasError implements SourceError {
735735
return this.explain()
736736
}
737737
}
738+
739+
export class NameNotFoundInModuleError implements SourceError {
740+
public type = ErrorType.TYPE
741+
public severity = ErrorSeverity.ERROR
742+
743+
constructor(
744+
public node: tsEs.ImportDeclaration,
745+
public moduleName: string,
746+
public name: string
747+
) {}
748+
749+
get location() {
750+
return this.node.loc ?? UNKNOWN_LOCATION
751+
}
752+
753+
public explain() {
754+
return `Module '${this.moduleName}' has no exported member '${this.name}'.`
755+
}
756+
757+
public elaborate() {
758+
return this.explain()
759+
}
760+
}

src/modules/loader/index.ts

+14-2
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,25 @@ export default async function loadSourceModules(
3434
return [moduleName, bundle] as [string, ModuleFunctions]
3535
})
3636
)
37-
3837
const loadedObj = Object.fromEntries(loadedModules)
3938
context.nativeStorage.loadedModules = loadedObj
40-
4139
return loadedObj
4240
}
4341

42+
export async function loadSourceModuleTypes(sourceModulesToImport: Set<string>, context: Context) {
43+
const loadedModules = await Promise.all(
44+
[...sourceModulesToImport].map(async moduleName => {
45+
await initModuleContextAsync(moduleName, context, false)
46+
const bundle = await loadModuleBundleAsync(moduleName, context)
47+
return [moduleName, bundle] as [string, ModuleFunctions]
48+
})
49+
)
50+
const loadedObj = Object.fromEntries(loadedModules)
51+
sourceModulesToImport.forEach(module => {
52+
context.nativeStorage.loadedModuleTypes[module] = loadedObj[module].type_map
53+
})
54+
}
55+
4456
export {
4557
setModulesStaticURL,
4658
MODULES_STATIC_URL,

src/modules/preprocessor/index.ts

+28-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import type es from 'estree'
22
// import * as TypedES from '../../typeChecker/tsESTree'
33

44
import type { Context, IOptions } from '../..'
5-
import type { RecursivePartial } from '../../types'
6-
import loadSourceModules from '../loader'
5+
import { RecursivePartial, Variant } from '../../types'
6+
import loadSourceModules, { loadSourceModuleTypes } from '../loader'
77
import type { FileGetter } from '../moduleTypes'
88
import analyzeImportsAndExports from './analyzer'
99
import parseProgramsAndConstructImportGraph from './linker'
@@ -45,6 +45,20 @@ const preprocessFileImports = async (
4545
options: RecursivePartial<IOptions> = {},
4646
bundler: Bundler = defaultBundler
4747
): Promise<PreprocessResult> => {
48+
if (context.variant === Variant.TYPED) {
49+
// Load typed source modules into context first to ensure that the type checker has access to all types.
50+
// TODO: This is a temporary solution, and we should consider a better way to handle this.
51+
try {
52+
await loadSourceModuleTypes(new Set<string>(['rune', 'curve']), context)
53+
} catch (error) {
54+
context.errors.push(error)
55+
return {
56+
ok: false,
57+
verboseErrors: false
58+
}
59+
}
60+
}
61+
4862
// Parse all files into ASTs and build the import graph.
4963
const linkerResult = await parseProgramsAndConstructImportGraph(
5064
files,
@@ -62,6 +76,18 @@ const preprocessFileImports = async (
6276

6377
try {
6478
await loadSourceModules(sourceModulesToImport, context, options.importOptions?.loadTabs ?? true)
79+
// Run type checking on the programs after loading the source modules and their types.
80+
const linkerResult = await parseProgramsAndConstructImportGraph(
81+
files,
82+
entrypointFilePath,
83+
context,
84+
options?.importOptions,
85+
!!options?.shouldAddFileName
86+
)
87+
// Return 'undefined' if there are errors while parsing.
88+
if (!linkerResult.ok) {
89+
return linkerResult
90+
}
6591

6692
analyzeImportsAndExports(
6793
programs,

0 commit comments

Comments
 (0)