nerdamer-prime
Version:
javascript light-weight symbolic math library
400 lines (347 loc) • 18.3 kB
text/typescript
import type nerdamer from '../index';
import tsMorph from 'ts-morph';
// Type alias for dynamic property access in investigation tests
type Indexable = Record<string, any>;
describe('Arity Mismatch Investigation', () => {
let nerdamerRuntime: typeof nerdamer & Indexable;
let project: tsMorph.Project;
let sourceFile: tsMorph.SourceFile;
let nerdamerPrimeNamespace: tsMorph.ModuleDeclaration | undefined;
beforeAll(async () => {
// Load complete nerdamer with all modules
const module = await import('../all.js');
nerdamerRuntime = module.default || module;
project = new tsMorph.Project({
tsConfigFilePath: 'tsconfig.json',
});
sourceFile = project.getSourceFileOrThrow('index.d.ts');
nerdamerPrimeNamespace = sourceFile
.getDescendantsOfKind(tsMorph.SyntaxKind.ModuleDeclaration)
.find((ns: tsMorph.ModuleDeclaration) => ns.getName() === 'nerdamerPrime');
});
it('should analyze specific arity mismatch functions in detail', () => {
// Select a few representative functions with arity mismatches
const testFunctions = ['atan2', 'solve', 'matget', 'cross', 'diff', 'integrate', 'defint'];
console.log('\n=== Detailed Arity Mismatch Analysis ===');
for (const funcName of testFunctions) {
const runtimeFunction = nerdamerRuntime[funcName];
if (typeof runtimeFunction !== 'function') {
console.log(`❌ ${funcName}: Not found in runtime`);
continue;
}
// Get TypeScript function declaration
if (!nerdamerPrimeNamespace) {
console.log(`❌ ${funcName}: nerdamerPrime namespace not found`);
continue;
}
const functionDeclarations = nerdamerPrimeNamespace
.getDescendantsOfKind(tsMorph.SyntaxKind.FunctionDeclaration)
.filter(f => f.getName() === funcName);
if (functionDeclarations.length === 0) {
console.log(`❌ ${funcName}: No TS declaration found`);
continue;
}
const funcDecl = functionDeclarations[0];
if (!funcDecl) {
console.log(`❌ ${funcName}: Function declaration is undefined`);
continue;
}
const tsParams = funcDecl.getParameters();
const requiredParams = tsParams.filter(p => !p.hasQuestionToken() && !p.isRestParameter()).length;
const runtimeArity = runtimeFunction.length;
console.log(`\n📋 ${funcName}:`);
console.log(` TS required params: ${requiredParams}`);
console.log(` Runtime arity: ${runtimeArity}`);
const signatureText = funcDecl.getText();
const signature = signatureText.split('{')[0]?.trim() || signatureText;
console.log(` TS signature: ${signature}`);
// Analyze function source code
const functionSource = runtimeFunction.toString();
const usesArguments = functionSource.includes('arguments');
const argMatches = functionSource.match(/arguments\[(?:\d+)\]/gu) || [];
const maxArgIndex =
argMatches.length > 0
? Math.max(...argMatches.map((m: string) => parseInt(/\d+/u.exec(m)![0], 10))) + 1
: 0;
console.log(` Uses arguments object: ${usesArguments}`);
if (usesArguments) {
console.log(` Max argument index used: ${maxArgIndex}`);
console.log(` Arguments pattern matches: ${argMatches.length}`);
}
// Test if function actually works with expected parameters
let functionalTest = 'Not tested';
try {
switch (funcName) {
case 'atan2': {
const result1 = runtimeFunction(1, 2);
functionalTest = `✅ Works: atan2(1,2) = ${result1?.toString?.() || result1}`;
break;
}
case 'solve': {
const result2 = runtimeFunction('x^2-4', 'x');
functionalTest = `✅ Works: solve('x^2-4','x') returns ${typeof result2}`;
break;
}
case 'matget': {
const testMatrix = nerdamerRuntime.matrix([
[1, 2],
[3, 4],
]);
const result3 = runtimeFunction(testMatrix, 0, 1);
functionalTest = `✅ Works: matget(matrix,0,1) = ${result3?.toString?.() || result3}`;
break;
}
case 'cross': {
const v1 = nerdamerRuntime.vector([1, 0, 0]);
const v2 = nerdamerRuntime.vector([0, 1, 0]);
const result4 = runtimeFunction(v1, v2);
functionalTest = `✅ Works: cross(v1,v2) returns ${typeof result4}`;
break;
}
case 'diff': {
const result5 = runtimeFunction('x^2', 'x');
functionalTest = `✅ Works: diff('x^2','x') = ${result5?.toString?.() || result5}`;
break;
}
case 'integrate': {
const result6 = runtimeFunction('x^2', 'x');
functionalTest = `✅ Works: integrate('x^2','x') = ${result6?.toString?.() || result6}`;
break;
}
case 'defint': {
const result7 = runtimeFunction('x', 0, 1, 'x');
functionalTest = `✅ Works: defint('x',0,1,'x') = ${result7?.toString?.() || result7}`;
break;
}
}
} catch (error) {
functionalTest = `❌ Error: ${error instanceof Error ? error.message : String(error)}`;
}
console.log(` Functional test: ${functionalTest}`);
// Check if this is a wrapper function pattern
const isWrapper = functionSource.includes('return') && functionSource.length < 200;
if (isWrapper) {
console.log(` 🔄 Appears to be a wrapper function`);
}
}
});
it('should analyze the overall pattern of arity mismatches', () => {
console.log('\n=== Overall Arity Pattern Analysis ===');
const allFunctions = Object.keys(nerdamerRuntime).filter(key => typeof nerdamerRuntime[key] === 'function');
const arityStats = {
zeroArity: 0,
nonZeroArity: 0,
usesArguments: 0,
doesNotUseArguments: 0,
wrapperFunctions: 0,
};
const sampleArityZeroFunctions: string[] = [];
for (const funcName of allFunctions) {
const func = nerdamerRuntime[funcName];
const arity = func.length;
const source = func.toString();
if (arity === 0) {
arityStats.zeroArity++;
if (sampleArityZeroFunctions.length < 10) {
sampleArityZeroFunctions.push(funcName);
}
} else {
arityStats.nonZeroArity++;
}
if (source.includes('arguments')) {
arityStats.usesArguments++;
} else {
arityStats.doesNotUseArguments++;
}
// Simple heuristic for wrapper functions
if (source.includes('return') && source.length < 200 && !source.includes('function')) {
arityStats.wrapperFunctions++;
}
}
console.log('Function arity distribution:');
console.log(
` Zero arity: ${arityStats.zeroArity} (${Math.round((arityStats.zeroArity / allFunctions.length) * 100)}%)`
);
console.log(
` Non-zero arity: ${arityStats.nonZeroArity} (${Math.round((arityStats.nonZeroArity / allFunctions.length) * 100)}%)`
);
console.log(` Uses arguments object: ${arityStats.usesArguments}`);
console.log(` Does not use arguments: ${arityStats.doesNotUseArguments}`);
console.log(` Apparent wrapper functions: ${arityStats.wrapperFunctions}`);
console.log('\nSample zero-arity functions:', sampleArityZeroFunctions);
// Test if zero-arity functions are typically module exports
console.log('\n=== Zero-Arity Function Analysis ===');
for (const funcName of sampleArityZeroFunctions.slice(0, 5)) {
const func = nerdamerRuntime[funcName];
const source = func.toString();
console.log(`\n${funcName}:`);
console.log(` Source length: ${source.length} chars`);
console.log(` Uses arguments: ${source.includes('arguments')}`);
console.log(` First 100 chars: ${source.substring(0, 100)}...`);
}
});
it('should test common JavaScript argument patterns', () => {
console.log('\n=== JavaScript Argument Pattern Testing ===');
// Test some specific functions that should work despite arity 0
const testCases = [
{
name: 'Basic arithmetic with solve',
test: () => nerdamerRuntime.solve('x^2 - 4', 'x'),
description: 'solve() with 2 arguments',
},
{
name: 'Trigonometric with multiple args',
test: () => nerdamerRuntime.atan2(1, 1),
description: 'atan2() with 2 arguments',
},
{
name: 'Matrix operations',
test: () => {
const mat = nerdamerRuntime.matrix([
[1, 2],
[3, 4],
]);
return nerdamerRuntime.matget(mat, 0, 1);
},
description: 'matget() with 3 arguments',
},
{
name: 'Variable argument functions',
test: () => nerdamerRuntime.max(1, 2, 3, 4, 5),
description: 'max() with variable arguments',
},
{
name: 'Integration with bounds',
test: () => nerdamerRuntime.defint('x^2', 0, 2, 'x'),
description: 'defint() with 4 arguments',
},
];
for (const testCase of testCases) {
try {
const result = testCase.test();
console.log(`✅ ${testCase.name}: ${testCase.description} -> ${result?.toString?.() || typeof result}`);
} catch (error) {
console.log(
`❌ ${testCase.name}: ${testCase.description} -> ERROR: ${error instanceof Error ? error.message : String(error)}`
);
}
}
console.log('\n🔍 Pattern Analysis Conclusion:');
console.log('Most zero-arity functions likely use the arguments object or are');
console.log('wrapper functions that delegate to internal implementations.');
console.log('This is a common JavaScript pattern for flexible function interfaces.');
});
it('should investigate the 6 remaining genuine issues from robust arity checking', () => {
console.log('\n=== Investigation of 6 Remaining Genuine Issues ===');
const remainingIssues = [
'getVars', // Return type mismatch
'supported', // Return type mismatch
'matset', // Complex function that may not handle 4+ parameters correctly
'sum', // Complex function that may not handle 4+ parameters correctly
'product', // Complex function that may not handle 4+ parameters correctly
'solveEquations', // Genuine arity mismatch (declares 5 but needs 1)
];
for (const funcName of remainingIssues) {
const runtimeFunction = nerdamerRuntime[funcName];
if (typeof runtimeFunction !== 'function') {
console.log(`❌ ${funcName}: Not found in runtime`);
continue;
}
console.log(`\n🔍 Investigating ${funcName}:`);
console.log(` Runtime arity: ${runtimeFunction.length}`);
const functionSource = runtimeFunction.toString();
const usesArguments = functionSource.includes('arguments');
console.log(` Uses arguments object: ${usesArguments}`);
console.log(` Function length: ${functionSource.length} chars`);
// Show function signature preview
const firstLine = functionSource.split('\n')[0].trim();
console.log(` Signature: ${firstLine}...`);
// Test specific issue types
switch (funcName) {
case 'getVars':
console.log(' 🧪 Testing return type:');
try {
const result = runtimeFunction();
console.log(` - Returns: ${typeof result} (${JSON.stringify(result)})`);
console.log(` - Expected: Record<string, NerdamerExpression | string>`);
console.log(` - Issue: Returns empty object when no variables set`);
} catch (error) {
console.log(` - Error: ${error instanceof Error ? error.message : String(error)}`);
}
break;
case 'supported':
console.log(' 🧪 Testing return type:');
try {
// Test with no arguments
const result1 = runtimeFunction();
console.log(` - No args: ${typeof result1} (length: ${result1?.length || 'N/A'})`);
// Test with string argument
const result2 = runtimeFunction('cos');
console.log(` - With 'cos': ${typeof result2} (${result2})`);
console.log(` - Expected: boolean`);
console.log(
` - Issue: Returns array when called without arguments, boolean with arguments`
);
} catch (error) {
console.log(` - Error: ${error instanceof Error ? error.message : String(error)}`);
}
break;
case 'matset':
case 'sum':
case 'product':
console.log(' 🧪 Testing complex parameter handling:');
try {
if (funcName === 'matset') {
const testMatrix = nerdamerRuntime.matrix([
[1, 2],
[3, 4],
]);
const result = runtimeFunction(testMatrix, 0, 1, 'newValue');
console.log(` - 4 params test: Success (${typeof result})`);
} else if (funcName === 'sum') {
const result = runtimeFunction('x^2', 'x', 1, 5);
console.log(` - 4 params test: Success (${result?.toString?.() || typeof result})`);
} else if (funcName === 'product') {
const result = runtimeFunction('x', 'x', 1, 3);
console.log(` - 4 params test: Success (${result?.toString?.() || typeof result})`);
}
} catch (error) {
console.log(
` - 4 params test: Failed - ${error instanceof Error ? error.message : String(error)}`
);
}
break;
case 'solveEquations':
console.log(' 🧪 Testing arity mismatch:');
console.log(` - Declared arity: ${runtimeFunction.length}`);
console.log(` - TypeScript expects: 1 required param (equations array)`);
try {
// Test with array of equations
const result1 = runtimeFunction(['x + y = 5', 'x - y = 1']);
console.log(` - Array test: Success (${typeof result1})`);
// Test with additional variables parameter
const result2 = runtimeFunction(['x + y = 5', 'x - y = 1'], ['x', 'y']);
console.log(` - Array + vars test: Success (${typeof result2})`);
} catch (error) {
console.log(` - Test failed: ${error instanceof Error ? error.message : String(error)}`);
}
break;
}
// Additional argument pattern analysis
if (usesArguments) {
const argMatches = functionSource.match(/arguments\[(?:\d+)\]/gu) || [];
const maxArgIndex =
argMatches.length > 0
? Math.max(...argMatches.map((m: string) => parseInt(/\d+/u.exec(m)![0], 10))) + 1
: 0;
console.log(` Arguments pattern: max index ${maxArgIndex}, ${argMatches.length} references`);
}
}
console.log('\n📋 Summary of Remaining Issues:');
console.log('1. getVars() - Returns correct type but empty object when no vars');
console.log('2. supported() - Overloaded: array with no args, boolean with string arg');
console.log('3. matset/sum/product - Functions work but complex argument handling');
console.log('4. solveEquations - High arity (5) suggests internal implementation detail');
console.log('\n💡 These represent edge cases in API design rather than bugs.');
});
});