Skip to content

Make Python operations stricter about types #1708

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jun 3, 2024
Merged
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 143 additions & 18 deletions src/stdlib/pylib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ export function is_int(v: Value) {
return typeof v === 'bigint'
}

function __is_numeric(v: Value) {
return is_int(v) || is_float(v)
}

function __is_string(v: Value) {
// Retrieved from https://stackoverflow.com/questions/4059147/check-if-a-variable-is-a-string-in-javascript
return typeof v === 'string' || v instanceof String
}

export function __py_adder(x: Value, y: Value) {
if (typeof x === typeof y) {
return x + y
Expand All @@ -18,14 +27,17 @@ export function __py_adder(x: Value, y: Value) {
if (typeof y === 'bigint') {
return x + Number(y)
}
return x + y
if (__is_numeric(x) && __is_numeric(y)) {
return x + y
}
throw new Error(`Invalid types for addition operation: ${typeof x}, ${typeof y}`)
}

export function __py_minuser(x: Value, y: Value) {
if (!(typeof x === 'bigint') && !(typeof x === 'number')) {
if (!__is_numeric(x)) {
throw new Error('Expected number on left hand side of operation, got ' + typeof x + '.')
}
if (!(typeof y === 'bigint') && !(typeof y === 'number')) {
if (!__is_numeric(y)) {
throw new Error('Expected number on right hand side of operation, got ' + typeof y + '.')
}
if (typeof x === 'bigint' && typeof y === 'bigint') {
Expand All @@ -40,14 +52,14 @@ export function __py_minuser(x: Value, y: Value) {
if (typeof x === 'number' && typeof y === 'bigint') {
return x - Number(y)
}
return NaN
throw new Error(`Invalid types for subtraction operation: ${typeof x}, ${typeof y}`)
}

export function __py_multiplier(x: Value, y: Value) {
if (!(typeof x === 'bigint') && !(typeof x === 'number')) {
if (!__is_numeric(x)) {
throw new Error('Expected number on left hand side of operation, got ' + typeof x + '.')
}
if (!(typeof y === 'bigint') && !(typeof y === 'number')) {
if (!__is_numeric(y)) {
throw new Error('Expected number on right hand side of operation, got ' + typeof y + '.')
}
if (typeof x === 'bigint' && typeof y === 'bigint') {
Expand All @@ -62,14 +74,20 @@ export function __py_multiplier(x: Value, y: Value) {
if (typeof x === 'number' && typeof y === 'bigint') {
return x * Number(y)
}
return NaN
if (typeof x == 'number' && __is_string(y)) {
return y.repeat(x)
}
if (typeof y == 'number' && __is_string(x)) {
return x.repeat(y)
}
throw new Error(`Invalid types for multiply operation: ${typeof x}, ${typeof y}`)
}

export function __py_divider(x: Value, y: Value) {
if (!(typeof x === 'bigint') && !(typeof x === 'number')) {
if (!__is_numeric(x)) {
throw new Error('Expected number on left hand side of operation, got ' + typeof x + '.')
}
if (!(typeof y === 'bigint') && !(typeof y === 'number')) {
if (!__is_numeric(y)) {
throw new Error('Expected number on right hand side of operation, got ' + typeof y + '.')
}
if (typeof x === 'bigint' && typeof y === 'bigint') {
Expand All @@ -84,14 +102,14 @@ export function __py_divider(x: Value, y: Value) {
if (typeof x === 'number' && typeof y === 'bigint') {
return x / Number(y)
}
return NaN
throw new Error(`Invalid types for divide operation: ${typeof x}, ${typeof y}`)
}

export function __py_modder(x: Value, y: Value) {
if (!(typeof x === 'bigint') && !(typeof x === 'number')) {
if (!__is_numeric(x)) {
throw new Error('Expected number on left hand side of operation, got ' + typeof x + '.')
}
if (!(typeof y === 'bigint') && !(typeof y === 'number')) {
if (!__is_numeric(y)) {
throw new Error('Expected number on right hand side of operation, got ' + typeof y + '.')
}
if (typeof x === 'bigint' && typeof y === 'bigint') {
Expand All @@ -106,14 +124,14 @@ export function __py_modder(x: Value, y: Value) {
if (typeof x === 'number' && typeof y === 'bigint') {
return x % Number(y)
}
return NaN
throw new Error(`Invalid types for modulo operation: ${typeof x}, ${typeof y}`)
}

export function __py_powerer(x: Value, y: Value) {
if (!(typeof x === 'bigint') && !(typeof x === 'number')) {
if (!__is_numeric(x)) {
throw new Error('Expected number on left hand side of operation, got ' + typeof x + '.')
}
if (!(typeof y === 'bigint') && !(typeof y === 'number')) {
if (!__is_numeric(y)) {
throw new Error('Expected number on right hand side of operation, got ' + typeof y + '.')
}
if (typeof x === 'bigint' && typeof y === 'bigint') {
Expand All @@ -123,136 +141,219 @@ export function __py_powerer(x: Value, y: Value) {
}
return res
}
if (typeof x === 'number' && typeof y === 'number') {
return Math.pow(x, y)
}
if (typeof x === 'bigint' && typeof y === 'number') {
return Math.pow(Number(x), y)
}
if (typeof x === 'number' && typeof y === 'bigint') {
return Math.pow(x, Number(y))
}
return Math.pow(Number(x), Number(y))
throw new Error(`Invalid types for power operation: ${typeof x}, ${typeof y}`)
}

export function __py_floorer(x: Value, y: Value) {
return BigInt(Math.floor(__py_divider(x, y)))
}

export function __py_unary_plus(x: Value) {
if (typeof x === 'bigint') {
if (__is_numeric(x)) {
return +Number(x)
}
return +x
throw new Error(`Invalid type for unary plus operation: ${typeof x}`)
}

export function math_abs(x: Value) {
if (!__is_numeric(x)) {
throw new Error(`Invalid type for operation: ${typeof x}`)
}
return Math.abs(Number(x))
}

export function math_acos(x: Value) {
if (!__is_numeric(x)) {
throw new Error(`Invalid type for operation: ${typeof x}`)
}
return Math.acos(Number(x))
}

export function math_acosh(x: Value) {
if (!__is_numeric(x)) {
throw new Error(`Invalid type for operation: ${typeof x}`)
}
return Math.acosh(Number(x))
}

export function math_asin(x: Value) {
if (!__is_numeric(x)) {
throw new Error(`Invalid type for operation: ${typeof x}`)
}
return Math.asin(Number(x))
}

export function math_asinh(x: Value) {
if (!__is_numeric(x)) {
throw new Error(`Invalid type for operation: ${typeof x}`)
}
return Math.asinh(Number(x))
}

export function math_atan(x: Value) {
if (!__is_numeric(x)) {
throw new Error(`Invalid type for operation: ${typeof x}`)
}
return Math.atan(Number(x))
}

export function math_atan2(y: Value, x: Value) {
if (!__is_numeric(x)) {
throw new Error(`Invalid type for operation: ${typeof x}`)
}
return Math.atan2(Number(y), Number(x))
}

export function math_atanh(x: Value) {
if (!__is_numeric(x)) {
throw new Error(`Invalid type for operation: ${typeof x}`)
}
return Math.atanh(Number(x))
}

export function math_cbrt(x: Value) {
if (!__is_numeric(x)) {
throw new Error(`Invalid type for operation: ${typeof x}`)
}
return Math.cbrt(Number(x))
}

export function math_ceil(x: Value) {
if (!__is_numeric(x)) {
throw new Error(`Invalid type for operation: ${typeof x}`)
}
return Math.ceil(Number(x))
}

export function math_clz32(x: Value) {
if (!__is_numeric(x)) {
throw new Error(`Invalid type for operation: ${typeof x}`)
}
return Math.clz32(Number(x))
}

export function math_cos(x: Value) {
if (!__is_numeric(x)) {
throw new Error(`Invalid type for operation: ${typeof x}`)
}
return Math.cos(Number(x))
}

export function math_cosh(x: Value) {
if (!__is_numeric(x)) {
throw new Error(`Invalid type for operation: ${typeof x}`)
}
return Math.cosh(Number(x))
}

export function math_exp(x: Value) {
if (!__is_numeric(x)) {
throw new Error(`Invalid type for operation: ${typeof x}`)
}
return Math.exp(Number(x))
}

export function math_expm1(x: Value) {
if (!__is_numeric(x)) {
throw new Error(`Invalid type for operation: ${typeof x}`)
}
return Math.expm1(Number(x))
}

export function math_floor(x: Value) {
if (!__is_numeric(x)) {
throw new Error(`Invalid type for operation: ${typeof x}`)
}
return Math.floor(Number(x))
}

export function math_fround(x: Value) {
if (!__is_numeric(x)) {
throw new Error(`Invalid type for operation: ${typeof x}`)
}
return Math.fround(Number(x))
}

export function math_hypot(...elements: Value[]) {
const coercedElements: number[] = elements.map(el => {
if (!__is_numeric(el)) {
throw new Error(`Invalid type for operation: ${typeof el}`)
}
return Number(el)
})
return Math.hypot(...coercedElements)
}

export function math_imul(x: Value, y: Value) {
if (!__is_numeric(x) || !__is_numeric(y)) {
throw new Error(`Invalid types for power operation: ${typeof x}, ${typeof y}`)
}
return Math.imul(Number(x), Number(y))
}

export function math_log(x: Value) {
if (!__is_numeric(x)) {
throw new Error(`Invalid type for operation: ${typeof x}`)
}
return Math.log(Number(x))
}

export function math_log1p(x: Value) {
if (!__is_numeric(x)) {
throw new Error(`Invalid type for operation: ${typeof x}`)
}
return Math.log1p(Number(x))
}

export function math_log2(x: Value) {
if (!__is_numeric(x)) {
throw new Error(`Invalid type for operation: ${typeof x}`)
}
return Math.log2(Number(x))
}

export function math_log10(x: Value) {
if (!__is_numeric(x)) {
throw new Error(`Invalid type for operation: ${typeof x}`)
}
return Math.log10(Number(x))
}

export function math_max(...elements: Value[]) {
// TODO: Python max also supports strings!
const coercedElements: number[] = elements.map(el => {
if (!__is_numeric(el)) {
throw new Error(`Invalid type for operation: ${typeof el}`)
}
return Number(el)
})
return Math.max(...coercedElements)
}

export function math_min(...elements: Value[]) {
// TODO: Python min also supports strings!
const coercedElements: number[] = elements.map(el => {
if (!__is_numeric(el)) {
throw new Error(`Invalid type for operation: ${typeof el}`)
}
return Number(el)
})
return Math.min(...coercedElements)
}

export function math_pow(x: Value, y: Value) {
if (!__is_numeric(x) || !__is_numeric(y)) {
throw new Error(`Invalid types for power operation: ${typeof x}, ${typeof y}`)
}
return Math.pow(Number(x), Number(y))
}

Expand All @@ -261,33 +362,57 @@ export function math_random() {
}

export function math_round(x: Value) {
if (!__is_numeric(x)) {
throw new Error(`Invalid type for operation: ${typeof x}`)
}
return Math.round(Number(x))
}

export function math_sign(x: Value) {
if (!__is_numeric(x)) {
throw new Error(`Invalid type for operation: ${typeof x}`)
}
return Math.sign(Number(x))
}

export function math_sin(x: Value) {
if (!__is_numeric(x)) {
throw new Error(`Invalid type for operation: ${typeof x}`)
}
return Math.sin(Number(x))
}

export function math_sinh(x: Value) {
if (!__is_numeric(x)) {
throw new Error(`Invalid type for operation: ${typeof x}`)
}
return Math.sinh(Number(x))
}

export function math_sqrt(x: Value) {
if (!__is_numeric(x)) {
throw new Error(`Invalid type for operation: ${typeof x}`)
}
return Math.sqrt(Number(x))
}

export function math_tan(x: Value) {
if (!__is_numeric(x)) {
throw new Error(`Invalid type for operation: ${typeof x}`)
}
return Math.tan(Number(x))
}

export function math_tanh(x: Value) {
if (!__is_numeric(x)) {
throw new Error(`Invalid type for operation: ${typeof x}`)
}
return Math.tanh(Number(x))
}

export function math_trunc(x: Value) {
if (!__is_numeric(x)) {
throw new Error(`Invalid type for operation: ${typeof x}`)
}
return Math.trunc(Number(x))
}
Loading