Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

### Fixed

- Fixed the `IFS` function evaluating later condition/value pairs after the first true condition.
- Fixed a memory leak in `LazilyTransformingAstService` where the transformations array grew unboundedly, causing increasing memory usage over time. [#1629](https://github.com/handsontable/hyperformula/issues/1629)
- Fixed a memory leak in `UndoRedo` where `oldData` entries for evicted undo stack entries were never cleaned up, causing increasing memory usage over time. [#1629](https://github.com/handsontable/hyperformula/issues/1629)
- Fixed the IRR function returning `#NUM!` error when the initial investment significantly exceeds the sum of returns. [#1628](https://github.com/handsontable/hyperformula/issues/1628)
Expand Down
57 changes: 51 additions & 6 deletions src/interpreter/plugin/BooleanPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import {CellError, ErrorType} from '../../Cell'
import {ErrorMessage} from '../../error-message'
import {ProcedureAst} from '../../parser'
import {SimpleRangeValue} from '../../SimpleRangeValue'
import {InterpreterState} from '../InterpreterState'
import {InternalNoErrorScalarValue, InternalScalarValue, InterpreterValue} from '../InterpreterValue'
import {FunctionArgumentType, FunctionPlugin, FunctionPluginTypecheck, ImplementedFunctions} from './FunctionPlugin'
Expand Down Expand Up @@ -147,14 +148,58 @@ export class BooleanPlugin extends FunctionPlugin implements FunctionPluginTypec
* @param state
*/
public ifs(ast: ProcedureAst, state: InterpreterState): InterpreterValue {
return this.runFunction(ast.args, state, this.metadata('IFS'), (...args) => {
for (let idx = 0; idx < args.length; idx += 2) {
if (args[idx]) {
return args[idx+1]
const metadata = this.metadata('IFS')
const argumentsMetadata = this.buildMetadataForEachArgumentValue(ast.args.length, metadata)

if (!this.isNumberOfArgumentValuesValid(argumentsMetadata, ast.args.length)) {
return new CellError(ErrorType.NA, ErrorMessage.WrongArgNumber)
}

for (let idx = 0; idx < ast.args.length; idx += 2) {
const condition = this.evaluateAst(ast.args[idx], state)

if (condition instanceof SimpleRangeValue && state.arraysFlag) {
return this.runFunction(ast.args, state, metadata, (...args: InterpreterValue[]) => this.evaluateIfsArguments(args))
}

const coercedCondition = this.coerceToType(condition, argumentsMetadata[idx], state)
if (coercedCondition === undefined) {
return new CellError(ErrorType.VALUE, ErrorMessage.WrongType)
}
if (coercedCondition instanceof CellError) {
return coercedCondition
}
if (coercedCondition) {
const value = this.evaluateAst(ast.args[idx + 1], state)
if (value instanceof SimpleRangeValue && state.arraysFlag) {
return this.runFunction(ast.args, state, metadata, (...args: InterpreterValue[]) => this.evaluateIfsArguments(args))
}

const coercedValue = this.coerceToType(value, argumentsMetadata[idx + 1], state)
if (coercedValue === undefined) {
return new CellError(ErrorType.VALUE, ErrorMessage.WrongType)
}

return coercedValue as InterpreterValue
}
return new CellError(ErrorType.NA, ErrorMessage.NoConditionMet)
})
}

return new CellError(ErrorType.NA, ErrorMessage.NoConditionMet)
}

/**
* Preserves the previous vectorized IFS implementation for array arithmetic.
*
* @param {InterpreterValue[]} args evaluated IFS arguments
*/
private evaluateIfsArguments(args: InterpreterValue[]): InterpreterValue {
for (let idx = 0; idx < args.length; idx += 2) {
if (args[idx]) {
return args[idx + 1]
}
}

return new CellError(ErrorType.NA, ErrorMessage.NoConditionMet)
}

/**
Expand Down
10 changes: 10 additions & 0 deletions test/smoke.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,16 @@ describe('HyperFormula', () => {
hf.destroy()
})

it('should not evaluate IFS branches after the first true condition', () => {
const hf = HyperFormula.buildFromArray([
['=IFS(TRUE(), 1, 1/0, 2)'],
], {licenseKey: 'gpl-v3'})

expect(hf.getCellValue(adr('A1'))).toBe(1)

hf.destroy()
})

it('should handle common spreadsheet functions', () => {
const data = [
[1, 2, 3, 4, 5],
Expand Down