Skip to content

Commit b45dab2

Browse files
committed
Merge remote-tracking branch 'origin/master' into remove-concurrent
# Conflicts: # src/repl/transpiler.ts # src/utils/testing.ts # src/vm/svmc.ts
2 parents ea999b5 + fea2b4c commit b45dab2

File tree

11 files changed

+279
-20
lines changed

11 files changed

+279
-20
lines changed

src/createContext.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,12 @@ import { streamPrelude } from './stdlib/stream.prelude'
2323
import { createTypeEnvironment, tForAll, tVar } from './typeChecker/utils'
2424
import {
2525
Chapter,
26-
Context,
27-
CustomBuiltIns,
28-
Environment,
29-
NativeStorage,
30-
Value,
26+
type Context,
27+
type CustomBuiltIns,
28+
type Environment,
29+
type LanguageOptions,
30+
type NativeStorage,
31+
type Value,
3132
Variant
3233
} from './types'
3334
import * as operators from './utils/operators'
@@ -147,6 +148,7 @@ const createNativeStorage = (): NativeStorage => ({
147148
export const createEmptyContext = <T>(
148149
chapter: Chapter,
149150
variant: Variant = Variant.DEFAULT,
151+
languageOptions: LanguageOptions = {},
150152
externalSymbols: string[],
151153
externalContext?: T
152154
): Context<T> => {
@@ -162,6 +164,7 @@ export const createEmptyContext = <T>(
162164
nativeStorage: createNativeStorage(),
163165
executionMethod: 'auto',
164166
variant,
167+
languageOptions,
165168
moduleContexts: {},
166169
unTypecheckedCode: [],
167170
typeEnvironment: createTypeEnvironment(chapter),
@@ -839,6 +842,7 @@ const defaultBuiltIns: CustomBuiltIns = {
839842
const createContext = <T>(
840843
chapter: Chapter = Chapter.SOURCE_1,
841844
variant: Variant = Variant.DEFAULT,
845+
languageOptions: LanguageOptions = {},
842846
externalSymbols: string[] = [],
843847
externalContext?: T,
844848
externalBuiltIns: CustomBuiltIns = defaultBuiltIns
@@ -849,14 +853,21 @@ const createContext = <T>(
849853
...createContext(
850854
Chapter.SOURCE_4,
851855
variant,
856+
languageOptions,
852857
externalSymbols,
853858
externalContext,
854859
externalBuiltIns
855860
),
856861
chapter
857862
} as Context
858863
}
859-
const context = createEmptyContext(chapter, variant, externalSymbols, externalContext)
864+
const context = createEmptyContext(
865+
chapter,
866+
variant,
867+
languageOptions,
868+
externalSymbols,
869+
externalContext
870+
)
860871

861872
importBuiltins(context, externalBuiltIns)
862873
importPrelude(context)

src/parser/source/typed/index.ts

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ export class SourceTypedParser extends SourceParser {
6767
}
6868

6969
const typedProgram: TypedES.Program = ast.program as TypedES.Program
70+
if (context.prelude !== programStr) {
71+
// Check for any declaration only if the program is not the prelude
72+
checkForAnyDeclaration(typedProgram, context)
73+
}
7074
const typedCheckedProgram: Program = checkForTypeErrors(typedProgram, context)
7175
transformBabelASTToESTreeCompliantAST(typedCheckedProgram)
7276

@@ -77,3 +81,175 @@ export class SourceTypedParser extends SourceParser {
7781
return 'SourceTypedParser'
7882
}
7983
}
84+
85+
function checkForAnyDeclaration(program: TypedES.Program, context: Context) {
86+
function parseConfigOption(option: string | undefined) {
87+
return option === 'true' || option === undefined
88+
}
89+
90+
const config = {
91+
allowAnyInVariables: parseConfigOption(context.languageOptions['typedAllowAnyInVariables']),
92+
allowAnyInParameters: parseConfigOption(context.languageOptions['typedAllowAnyInParameters']),
93+
allowAnyInReturnType: parseConfigOption(context.languageOptions['typedAllowAnyInReturnType']),
94+
allowAnyInTypeAnnotationParameters: parseConfigOption(
95+
context.languageOptions['typedAllowAnyInTypeAnnotationParameters']
96+
),
97+
allowAnyInTypeAnnotationReturnType: parseConfigOption(
98+
context.languageOptions['typedAllowAnyInTypeAnnotationReturnType']
99+
)
100+
}
101+
102+
function pushAnyUsageError(message: string, node: TypedES.Node) {
103+
if (node.loc) {
104+
context.errors.push(new FatalSyntaxError(node.loc, message))
105+
}
106+
}
107+
108+
function isAnyType(node: TypedES.TSTypeAnnotation | undefined) {
109+
return node?.typeAnnotation?.type === 'TSAnyKeyword' || node?.typeAnnotation === undefined
110+
}
111+
112+
function checkNode(node: TypedES.Node) {
113+
switch (node.type) {
114+
case 'VariableDeclaration': {
115+
node.declarations.forEach(decl => {
116+
const tsType = (decl as any).id?.typeAnnotation
117+
if (!config.allowAnyInVariables && isAnyType(tsType)) {
118+
pushAnyUsageError('Usage of "any" in variable declaration is not allowed.', node)
119+
}
120+
if (decl.init) {
121+
// check for lambdas
122+
checkNode(decl.init)
123+
}
124+
})
125+
break
126+
}
127+
case 'FunctionDeclaration': {
128+
if (!config.allowAnyInParameters || !config.allowAnyInReturnType) {
129+
const func = node as any
130+
// Check parameters
131+
func.params?.forEach((param: any) => {
132+
if (!config.allowAnyInParameters && isAnyType(param.typeAnnotation)) {
133+
pushAnyUsageError('Usage of "any" in function parameter is not allowed.', param)
134+
}
135+
})
136+
// Check return type
137+
if (!config.allowAnyInReturnType && isAnyType(func.returnType)) {
138+
pushAnyUsageError('Usage of "any" in function return type is not allowed.', node)
139+
}
140+
checkNode(node.body)
141+
}
142+
break
143+
}
144+
case 'ArrowFunctionExpression': {
145+
if (!config.allowAnyInParameters || !config.allowAnyInReturnType) {
146+
const arrow = node as any
147+
// Check parameters
148+
arrow.params?.forEach((param: any) => {
149+
if (!config.allowAnyInParameters && isAnyType(param.typeAnnotation)) {
150+
pushAnyUsageError('Usage of "any" in arrow function parameter is not allowed.', param)
151+
}
152+
})
153+
// Recursively check return type if present
154+
if (!config.allowAnyInReturnType && isAnyType(arrow.returnType)) {
155+
pushAnyUsageError('Usage of "any" in arrow function return type is not allowed.', arrow)
156+
}
157+
if (
158+
!config.allowAnyInReturnType &&
159+
arrow.params?.some((param: any) => isAnyType(param.typeAnnotation))
160+
) {
161+
pushAnyUsageError('Usage of "any" in arrow function return type is not allowed.', arrow)
162+
}
163+
checkNode(node.body)
164+
}
165+
break
166+
}
167+
case 'ReturnStatement': {
168+
if (node.argument) {
169+
checkNode(node.argument)
170+
}
171+
break
172+
}
173+
case 'BlockStatement':
174+
node.body.forEach(checkNode)
175+
break
176+
default:
177+
break
178+
}
179+
}
180+
181+
function checkTSNode(node: TypedES.Node) {
182+
if (!node) {
183+
// Happens when there is no type annotation
184+
// This should have been caught by checkNode function
185+
return
186+
}
187+
switch (node.type) {
188+
case 'VariableDeclaration': {
189+
node.declarations.forEach(decl => {
190+
const tsType = (decl as any).id?.typeAnnotation
191+
checkTSNode(tsType)
192+
})
193+
break
194+
}
195+
case 'TSTypeAnnotation': {
196+
const annotation = node as TypedES.TSTypeAnnotation
197+
// If it's a function type annotation, check params and return
198+
if (annotation.typeAnnotation?.type === 'TSFunctionType') {
199+
annotation.typeAnnotation.parameters?.forEach(param => {
200+
// Recursively check nested TSTypeAnnotations in parameters
201+
if (!config.allowAnyInTypeAnnotationParameters && isAnyType(param.typeAnnotation)) {
202+
pushAnyUsageError(
203+
'Usage of "any" in type annotation\'s function parameter is not allowed.',
204+
param
205+
)
206+
}
207+
if (param.typeAnnotation) {
208+
checkTSNode(param.typeAnnotation)
209+
}
210+
})
211+
const returnAnno = (annotation.typeAnnotation as TypedES.TSFunctionType).typeAnnotation
212+
if (!config.allowAnyInTypeAnnotationReturnType && isAnyType(returnAnno)) {
213+
pushAnyUsageError(
214+
'Usage of "any" in type annotation\'s function return type is not allowed.',
215+
annotation
216+
)
217+
}
218+
// Recursively check nested TSTypeAnnotations in return type
219+
checkTSNode(returnAnno)
220+
}
221+
break
222+
}
223+
case 'FunctionDeclaration': {
224+
// Here we also check param type annotations + return type via config
225+
if (
226+
!config.allowAnyInTypeAnnotationParameters ||
227+
!config.allowAnyInTypeAnnotationReturnType
228+
) {
229+
const func = node as any
230+
// Check parameters
231+
if (!config.allowAnyInTypeAnnotationParameters) {
232+
func.params?.forEach((param: any) => {
233+
checkTSNode(param.typeAnnotation)
234+
})
235+
}
236+
// Recursively check the function return type annotation
237+
checkTSNode(func.returnType)
238+
}
239+
break
240+
}
241+
case 'BlockStatement':
242+
node.body.forEach(checkTSNode)
243+
break
244+
default:
245+
break
246+
}
247+
}
248+
249+
if (!config.allowAnyInVariables || !config.allowAnyInParameters || !config.allowAnyInReturnType) {
250+
program.body.forEach(checkNode)
251+
}
252+
if (!config.allowAnyInTypeAnnotationParameters || !config.allowAnyInTypeAnnotationReturnType) {
253+
program.body.forEach(checkTSNode)
254+
}
255+
}

src/repl/repl.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import type { FileGetter } from '../modules/moduleTypes'
1212
import {
1313
chapterParser,
1414
getChapterOption,
15+
getLanguageOption,
1516
getVariantOption,
1617
handleResult,
1718
validChapterVariant
@@ -21,6 +22,7 @@ export const getReplCommand = () =>
2122
new Command('run')
2223
.addOption(getChapterOption(Chapter.SOURCE_4, chapterParser))
2324
.addOption(getVariantOption(Variant.DEFAULT, objectValues(Variant)))
25+
.addOption(getLanguageOption())
2426
.option('-v, --verbose', 'Enable verbose errors')
2527
.option('--modulesBackend <backend>')
2628
.option('-r, --repl', 'Start a REPL after evaluating files')
@@ -34,7 +36,7 @@ export const getReplCommand = () =>
3436

3537
const fs: typeof fslib = require('fs/promises')
3638

37-
const context = createContext(lang.chapter, lang.variant)
39+
const context = createContext(lang.chapter, lang.variant, lang.languageOptions)
3840

3941
if (modulesBackend !== undefined) {
4042
setModulesStaticURL(modulesBackend)

src/repl/svmc.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ strings containing the names of the VM-internal functions.`
6363
const vmInternalFunctions = opts.internals || []
6464

6565
const source = await fs.readFile(inputFile, 'utf-8')
66-
const context = createEmptyContext(opts.chapter, Variant.DEFAULT, [], null)
66+
const context = createEmptyContext(opts.chapter, Variant.DEFAULT, undefined, [], null)
6767
const program = parse(source, context)
6868
if (program === null) {
6969
process.stderr.write(parseError(context.errors))

src/repl/transpiler.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { Chapter, Variant } from '../types'
1212
import {
1313
chapterParser,
1414
getChapterOption,
15+
getLanguageOption,
1516
getVariantOption,
1617
validateChapterAndVariantCombo
1718
} from './utils'
@@ -20,7 +21,11 @@ export const getTranspilerCommand = () =>
2021
new Command('transpiler')
2122
.addOption(getVariantOption(Variant.DEFAULT, [Variant.DEFAULT, Variant.NATIVE]))
2223
.addOption(getChapterOption(Chapter.SOURCE_4, chapterParser))
23-
.option('-p, --pretranspile', "only pretranspile and don't perform Source -> JS transpilation")
24+
.addOption(getLanguageOption())
25+
.option(
26+
'-p, --pretranspile',
27+
"only pretranspile (e.g. GPU -> Source) and don't perform Source -> JS transpilation"
28+
)
2429
.option('-o, --out <outFile>', 'Specify a file to write to')
2530
.argument('<filename>')
2631
.action(async (fileName, opts) => {
@@ -30,7 +35,7 @@ export const getTranspilerCommand = () =>
3035
}
3136

3237
const fs: typeof fslib = require('fs/promises')
33-
const context = createContext(opts.chapter, opts.variant)
38+
const context = createContext(opts.chapter, opts.variant, opts.languageOptions)
3439
const entrypointFilePath = resolve(fileName)
3540

3641
const linkerResult = await parseProgramsAndConstructImportGraph(

src/repl/utils.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Option } from '@commander-js/extra-typings'
22

33
import { pyLanguages, scmLanguages, sourceLanguages } from '../constants'
4-
import { Chapter, type Language, Variant, type Result } from '../types'
4+
import { Chapter, type Language, Variant, type Result, type LanguageOptions } from '../types'
55
import { stringify } from '../utils/stringify'
66
import Closure from '../cse-machine/closure'
77
import { parseError, type Context } from '..'
@@ -39,6 +39,18 @@ export const getVariantOption = <T extends Variant>(defaultValue: T, choices: T[
3939
return new Option('--variant <variant>').default(defaultValue).choices(choices)
4040
}
4141

42+
export const getLanguageOption = <T extends LanguageOptions>() => {
43+
return new Option('--languageOptions <options>')
44+
.default({})
45+
.argParser((value: string): LanguageOptions => {
46+
const languageOptions = value.split(',').map(lang => {
47+
const [key, value] = lang.split('=')
48+
return { [key]: value }
49+
})
50+
return Object.assign({}, ...languageOptions)
51+
})
52+
}
53+
4254
export function validateChapterAndVariantCombo(language: Language) {
4355
for (const { chapter, variant } of sourceLanguages) {
4456
if (language.chapter === chapter && language.variant === variant) return true

src/typeChecker/typeErrorChecker.ts

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -393,9 +393,45 @@ function typeCheckAndReturnType(node: tsEs.Node): Type {
393393
}
394394

395395
// Due to the use of generics, pair, list and stream functions are handled separately
396-
const pairFunctions = ['pair']
397-
const listFunctions = ['list', 'map', 'filter', 'accumulate', 'reverse']
398-
const streamFunctions = ['stream_map', 'stream_reverse']
396+
const pairFunctions = ['pair', 'is_pair', 'head', 'tail', 'is_null', 'set_head', 'set_tail']
397+
const listFunctions = [
398+
'list',
399+
'equal',
400+
'length',
401+
'map',
402+
'build_list',
403+
'for_each',
404+
'list_to_string',
405+
'append',
406+
'member',
407+
'remove',
408+
'remove_all',
409+
'filter',
410+
'enum_list',
411+
'list_ref',
412+
'accumulate',
413+
'reverse'
414+
]
415+
const streamFunctions = [
416+
'stream_tail',
417+
'is_stream',
418+
'list_to_stream',
419+
'stream_to_list',
420+
'stream_length',
421+
'stream_map',
422+
'build_stream',
423+
'stream_for_each',
424+
'stream_reverse',
425+
'stream_append',
426+
'stream_member',
427+
'stream_remove',
428+
'stream_remove_all',
429+
'stream_filter',
430+
'enum_stream',
431+
'integers_from',
432+
'eval_stream',
433+
'stream_ref'
434+
]
399435
if (
400436
pairFunctions.includes(fnName) ||
401437
listFunctions.includes(fnName) ||

0 commit comments

Comments
 (0)