diff --git a/CHANGELOG.md b/CHANGELOG.md
index 323c746b2d..1bf49f3af8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
### Added
- Added a message after `npm i hyperformula` that points AI assistants to the SKILLS file. The message needs `--foreground-scripts` to be visible in npm, and pnpm 10 needs the `postinstall` script to be approved first. [#1829](https://github.com/handsontable/hyperformula/pull/1829)
+- Added the `TYPE` and `ERROR.TYPE` information functions: `TYPE` returns the code of a value's type (number, text, boolean, error, array), and `ERROR.TYPE` returns the code of an error, or `#N/A` for any other value. [#1813](https://github.com/handsontable/hyperformula/pull/1813)
### Fixed
diff --git a/docs/guide/list-of-differences.md b/docs/guide/list-of-differences.md
index 660fda0f98..37e5b45e26 100644
--- a/docs/guide/list-of-differences.md
+++ b/docs/guide/list-of-differences.md
@@ -53,6 +53,8 @@ See a full list of differences between HyperFormula, Microsoft Excel, and Google
| EOMONTH function | =EOMONTH(DATE(2019, 7, 31), 1) | Type of the returned value: `CellValueDetailedType.NUMBER_DATE`. This is non-compliant with the [OpenDocument](https://docs.oasis-open.org/office/OpenDocument/v1.3/os/part4-formula/OpenDocument-v1.3-os-part4-formula.html) standard, which defines the return type as a Number, while describing it as a Date serial number through the function summary. | Cell auto-formatted as **date** | Cell auto-formatted as **regular number** |
| EDATE and EOMONTH results in February 1900 | =EDATE(DATE(1900, 1, 31), 1)
=EOMONTH(DATE(1900, 1, 31), 1) | With the [`leapYear1900`](/api/interfaces/configparams.md#leapyear1900) option set to `true`, February 1900 has 29 days, so both examples return 29 February 1900 (serial `60` with `nullDate` set to 1899-12-31). With the default `false`, both return 28 February 1900. | 28 February 1900 for both examples. Google Sheets does not treat 1900 as a leap year. | 28 February 1900 (serial `59`) for both examples. Excel keeps the 1900 leap day only in the serial-to-date mapping (`DAY(60)` is `29`); its month arithmetic treats February 1900 as having 28 days, so no `EDATE` or `EOMONTH` result is ever serial `60`. |
| Empty cells in lookup search | =XLOOKUP(3, A1:A4, A1:A4, "NF", 0, 2)
where A1:A4 = 1, 2, (empty), 3 | Empty cells are skipped during the search. A value that is present is found even when it sits past an interspersed empty cell (exact match is gap-independent), and approximate `MATCH`/`VLOOKUP`/`HLOOKUP`/`XLOOKUP` skip empty cells — but not empty strings — when finding the lower/upper bound. Returns `3`. On an all-empty range in a binary search mode, HyperFormula returns the `if_not_found` result (never row 1). | Skips empty cells in approximate search (parity with HyperFormula). | With binary search modes (`search_mode` ±2), a range with interspersed empty cells is not strictly sorted; per Excel's documentation the result may be invalid, so a value past an empty cell is not reliably found. On an all-empty range in a binary mode, Excel returns the first row's value. |
+| TYPE of a computed array | =TYPE(ROW(A1:A2)+0)
=TYPE(A1:A2+1)
=TYPE(IF(TRUE(), A1:A2)) | `ROW` and `COLUMN` over a multi-cell range count as arrays only when passed to `TYPE` directly, so `TYPE(ROW(A1:A2)+0)` gives `1`. With [`useArrayArithmetic`](/api/interfaces/configparams.md#usearrayarithmetic) set to `false` (the default), `A1:A2+1` and `IF(TRUE(), A1:A2)` are reduced to the cell in the formula's row, so `TYPE` classifies that cell's value (`1` for a number) in rows 1 and 2, and gives `16` (`#VALUE!`) in any other row. With `useArrayArithmetic` set to `true`, both give `64`. | Not checked. | Returns `64` for all three examples. |
+| ERROR.TYPE function | =ERROR.TYPE(#NULL!) | HyperFormula has no `#NULL!` error, so the code 1 is never returned and the literal does not parse. `#CYCLE!`, which Excel does not have, has no code and gives `#N/A`. | Not checked. | Returns `1` for `#NULL!`. |
## Built-in functions
diff --git a/hyperformula/src/i18n/languages/csCZ.ts b/hyperformula/src/i18n/languages/csCZ.ts
index fea286083c..294edcfbb1 100644
--- a/hyperformula/src/i18n/languages/csCZ.ts
+++ b/hyperformula/src/i18n/languages/csCZ.ts
@@ -103,6 +103,7 @@ const dictionary: RawTranslationPackage = {
EOMONTH: 'EOMONTH',
ERF: 'ERF',
ERFC: 'ERFC',
+ 'ERROR.TYPE': 'CHYBA.TYP',
EVEN: 'ZAOKROUHLIT.NA.SUDÉ',
EXACT: 'STEJNÉ',
EXP: 'EXP',
@@ -239,6 +240,7 @@ const dictionary: RawTranslationPackage = {
TRIM: 'PROČISTIT',
TRUE: 'PRAVDA',
TRUNC: 'USEKNOUT',
+ TYPE: 'TYP',
UNICHAR: 'UNICHAR',
UNICODE: 'UNICODE',
UNIQUE: 'UNIQUE',
diff --git a/hyperformula/src/i18n/languages/daDK.ts b/hyperformula/src/i18n/languages/daDK.ts
index 9264551249..682768c817 100644
--- a/hyperformula/src/i18n/languages/daDK.ts
+++ b/hyperformula/src/i18n/languages/daDK.ts
@@ -103,6 +103,7 @@ const dictionary: RawTranslationPackage = {
EOMONTH: 'SLUT.PÅ.MÅNED',
ERF: 'FEJLFUNK',
ERFC: 'FEJLFUNK.KOMP',
+ 'ERROR.TYPE': 'FEJLTYPE',
EVEN: 'LIGE',
EXACT: 'EKSAKT',
EXP: 'EKSP',
@@ -239,6 +240,7 @@ const dictionary: RawTranslationPackage = {
TRIM: 'FJERN.OVERFLØDIGE.BLANKE',
TRUE: 'TRUE',
TRUNC: 'AFKORT',
+ TYPE: 'VÆRDITYPE',
UNICHAR: 'UNICHAR',
UNICODE: 'UNICODE',
UNIQUE: 'ENTYDIGE',
diff --git a/hyperformula/src/i18n/languages/deDE.ts b/hyperformula/src/i18n/languages/deDE.ts
index 4d3d39319b..3135a1a157 100644
--- a/hyperformula/src/i18n/languages/deDE.ts
+++ b/hyperformula/src/i18n/languages/deDE.ts
@@ -103,6 +103,7 @@ const dictionary: RawTranslationPackage = {
EOMONTH: 'MONATSENDE',
ERF: 'GAUSSFEHLER',
ERFC: 'GAUSSFKOMPL',
+ 'ERROR.TYPE': 'FEHLER.TYP',
EVEN: 'GERADE',
EXACT: 'IDENTISCH',
EXP: 'EXP',
@@ -239,6 +240,7 @@ const dictionary: RawTranslationPackage = {
TRIM: 'GLÄTTEN',
TRUE: 'WAHR',
TRUNC: 'KÜRZEN',
+ TYPE: 'TYP',
UNICHAR: 'UNIZEICHEN',
UNICODE: 'UNICODE',
UNIQUE: 'EINDEUTIG',
diff --git a/hyperformula/src/i18n/languages/enGB.ts b/hyperformula/src/i18n/languages/enGB.ts
index d271b732cd..0b1ff8d0ac 100644
--- a/hyperformula/src/i18n/languages/enGB.ts
+++ b/hyperformula/src/i18n/languages/enGB.ts
@@ -104,6 +104,7 @@ const dictionary: RawTranslationPackage = {
EOMONTH: 'EOMONTH',
ERF: 'ERF',
ERFC: 'ERFC',
+ 'ERROR.TYPE': 'ERROR.TYPE',
EVEN: 'EVEN',
EXACT: 'EXACT',
EXP: 'EXP',
@@ -241,6 +242,7 @@ const dictionary: RawTranslationPackage = {
TRIM: 'TRIM',
TRUE: 'TRUE',
TRUNC: 'TRUNC',
+ TYPE: 'TYPE',
UNICHAR: 'UNICHAR',
UNICODE: 'UNICODE',
UNIQUE: 'UNIQUE',
diff --git a/hyperformula/src/i18n/languages/esES.ts b/hyperformula/src/i18n/languages/esES.ts
index 9c6b817b98..3206eadb5e 100644
--- a/hyperformula/src/i18n/languages/esES.ts
+++ b/hyperformula/src/i18n/languages/esES.ts
@@ -103,6 +103,7 @@ export const dictionary: RawTranslationPackage = {
EOMONTH: 'FIN.MES',
ERF: 'FUN.ERROR',
ERFC: 'FUN.ERROR.COMPL',
+ 'ERROR.TYPE': 'TIPO.DE.ERROR',
EVEN: 'REDONDEA.PAR',
EXACT: 'IGUAL',
EXP: 'EXP',
@@ -239,6 +240,7 @@ export const dictionary: RawTranslationPackage = {
TRIM: 'ESPACIOS',
TRUE: 'VERDADERO',
TRUNC: 'TRUNCAR',
+ TYPE: 'TIPO',
UNICHAR: 'UNICHAR',
UNICODE: 'UNICODE',
UNIQUE: 'UNICOS',
diff --git a/hyperformula/src/i18n/languages/fiFI.ts b/hyperformula/src/i18n/languages/fiFI.ts
index 99ff05708f..dffc59fab1 100644
--- a/hyperformula/src/i18n/languages/fiFI.ts
+++ b/hyperformula/src/i18n/languages/fiFI.ts
@@ -103,6 +103,7 @@ const dictionary: RawTranslationPackage = {
EOMONTH: 'KUUKAUSI.LOPPU',
ERF: 'VIRHEFUNKTIO',
ERFC: 'VIRHEFUNKTIO.KOMPLEMENTTI',
+ 'ERROR.TYPE': 'VIRHEEN.LAJI',
EVEN: 'PARILLINEN',
EXACT: 'VERTAA',
EXP: 'EKSPONENTTI',
@@ -239,6 +240,7 @@ const dictionary: RawTranslationPackage = {
TRIM: 'POISTA.VÄLIT',
TRUE: 'TOSI',
TRUNC: 'KATKAISE',
+ TYPE: 'TYYPPI',
UNICHAR: 'UNICODEMERKKI',
UNICODE: 'UNICODE',
UNIQUE: 'AINUTKERTAISET.ARVOT',
diff --git a/hyperformula/src/i18n/languages/frFR.ts b/hyperformula/src/i18n/languages/frFR.ts
index f7b9be45f7..1d02c27f50 100644
--- a/hyperformula/src/i18n/languages/frFR.ts
+++ b/hyperformula/src/i18n/languages/frFR.ts
@@ -103,6 +103,7 @@ const dictionary: RawTranslationPackage = {
EOMONTH: 'FIN.MOIS',
ERF: 'ERF',
ERFC: 'ERFC',
+ 'ERROR.TYPE': 'TYPE.ERREUR',
EVEN: 'PAIR',
EXACT: 'EXACT',
EXP: 'EXP',
@@ -239,6 +240,7 @@ const dictionary: RawTranslationPackage = {
TRIM: 'SUPPRESPACE',
TRUE: 'VRAI',
TRUNC: 'TRONQUE',
+ TYPE: 'TYPE',
UNICHAR: 'UNICAR',
UNICODE: 'UNICODE',
UNIQUE: 'UNIQUE',
diff --git a/hyperformula/src/i18n/languages/huHU.ts b/hyperformula/src/i18n/languages/huHU.ts
index e40219553a..0faede3bea 100644
--- a/hyperformula/src/i18n/languages/huHU.ts
+++ b/hyperformula/src/i18n/languages/huHU.ts
@@ -103,6 +103,7 @@ const dictionary: RawTranslationPackage = {
EOMONTH: 'HÓNAP.UTOLSÓ.NAP',
ERF: 'HIBAF',
ERFC: 'HIBAF.KOMPLEMENTER',
+ 'ERROR.TYPE': 'HIBA.TÍPUS',
EVEN: 'PÁROS',
EXACT: 'AZONOS',
EXP: 'KITEVŐ',
@@ -239,6 +240,7 @@ const dictionary: RawTranslationPackage = {
TRIM: 'KIMETSZ',
TRUE: 'IGAZ',
TRUNC: 'CSONK',
+ TYPE: 'TÍPUS',
UNICHAR: 'UNIKARAKTER',
UNICODE: 'UNICODE',
UNIQUE: 'EGYEDI',
diff --git a/hyperformula/src/i18n/languages/idID.ts b/hyperformula/src/i18n/languages/idID.ts
index 719cb11db6..33e2375302 100644
--- a/hyperformula/src/i18n/languages/idID.ts
+++ b/hyperformula/src/i18n/languages/idID.ts
@@ -104,6 +104,7 @@ const dictionary: RawTranslationPackage = {
EOMONTH: 'AKHIR.BULAN',
ERF: 'ERF',
ERFC: 'ERFC',
+ 'ERROR.TYPE': 'ERROR.TYPE',
EVEN: 'GENAP',
EXACT: 'IDENTIK',
EXP: 'EXP',
@@ -241,6 +242,7 @@ const dictionary: RawTranslationPackage = {
TRIM: 'HAPUS.SPASI',
TRUE: 'BENAR',
TRUNC: 'POTONG',
+ TYPE: 'TYPE',
UNICHAR: 'UNIKARAKTER',
UNICODE: 'UNICODE',
UNIQUE: 'UNIQUE',
diff --git a/hyperformula/src/i18n/languages/itIT.ts b/hyperformula/src/i18n/languages/itIT.ts
index 1a494628f8..0efe41f2e3 100644
--- a/hyperformula/src/i18n/languages/itIT.ts
+++ b/hyperformula/src/i18n/languages/itIT.ts
@@ -103,6 +103,7 @@ const dictionary: RawTranslationPackage = {
EOMONTH: 'FINE.MESE',
ERF: 'FUNZ.ERRORE',
ERFC: 'FUNZ.ERRORE.COMP',
+ 'ERROR.TYPE': 'ERRORE.TIPO',
EVEN: 'PARI',
EXACT: 'IDENTICO',
EXP: 'EXP',
@@ -239,6 +240,7 @@ const dictionary: RawTranslationPackage = {
TRIM: 'ANNULLA.SPAZI',
TRUE: 'VERO',
TRUNC: 'TRONCA',
+ TYPE: 'TIPO',
UNICHAR: 'CARATT.UNI',
UNICODE: 'UNICODE',
UNIQUE: 'UNICI',
diff --git a/hyperformula/src/i18n/languages/nbNO.ts b/hyperformula/src/i18n/languages/nbNO.ts
index ff15415a98..43a1821e67 100644
--- a/hyperformula/src/i18n/languages/nbNO.ts
+++ b/hyperformula/src/i18n/languages/nbNO.ts
@@ -103,6 +103,7 @@ const dictionary: RawTranslationPackage = {
EOMONTH: 'MÅNEDSSLUTT',
ERF: 'FEILF',
ERFC: 'FEILFK',
+ 'ERROR.TYPE': 'FEIL.TYPE',
EVEN: 'AVRUND.TIL.PARTALL',
EXACT: 'EKSAKT',
EXP: 'EKSP',
@@ -239,6 +240,7 @@ const dictionary: RawTranslationPackage = {
TRIM: 'TRIMME',
TRUE: 'SANN',
TRUNC: 'AVKORT',
+ TYPE: 'VERDITYPE',
UNICHAR: 'UNICODETEGN',
UNICODE: 'UNICODE',
UNIQUE: 'UNIK',
diff --git a/hyperformula/src/i18n/languages/nlNL.ts b/hyperformula/src/i18n/languages/nlNL.ts
index c491e2f44b..2974a3ce9c 100644
--- a/hyperformula/src/i18n/languages/nlNL.ts
+++ b/hyperformula/src/i18n/languages/nlNL.ts
@@ -103,6 +103,7 @@ const dictionary: RawTranslationPackage = {
EOMONTH: 'LAATSTE.DAG',
ERF: 'FOUTFUNCTIE',
ERFC: 'FOUT.COMPLEMENT',
+ 'ERROR.TYPE': 'TYPE.FOUT',
EVEN: 'EVEN',
EXACT: 'GELIJK',
EXP: 'EXP',
@@ -239,6 +240,7 @@ const dictionary: RawTranslationPackage = {
TRIM: 'SPATIES.WISSEN',
TRUE: 'WAAR',
TRUNC: 'GEHEEL',
+ TYPE: 'TYPE',
UNICHAR: 'UNITEKEN',
UNICODE: 'UNICODE',
UNIQUE: 'UNIEK',
diff --git a/hyperformula/src/i18n/languages/plPL.ts b/hyperformula/src/i18n/languages/plPL.ts
index 6fa4f016b4..42829a8852 100644
--- a/hyperformula/src/i18n/languages/plPL.ts
+++ b/hyperformula/src/i18n/languages/plPL.ts
@@ -103,6 +103,7 @@ const dictionary: RawTranslationPackage = {
EOMONTH: 'NR.SER.OST.DN.MIEŚ',
ERF: 'FUNKCJA.BŁ',
ERFC: 'KOMP.FUNKCJA.BŁ',
+ 'ERROR.TYPE': 'NR.BŁĘDU',
EVEN: 'ZAOKR.DO.PARZ',
EXACT: 'PORÓWNAJ',
EXP: 'EXP',
@@ -239,6 +240,7 @@ const dictionary: RawTranslationPackage = {
TRIM: 'USUŃ.ZBĘDNE.ODSTĘPY',
TRUE: 'PRAWDA',
TRUNC: 'LICZBA.CAŁK',
+ TYPE: 'TYP',
UNICHAR: 'ZNAK.UNICODE',
UNICODE: 'UNICODE',
UNIQUE: 'UNIKATOWE',
diff --git a/hyperformula/src/i18n/languages/ptPT.ts b/hyperformula/src/i18n/languages/ptPT.ts
index 6c1e68c328..a235c025d4 100644
--- a/hyperformula/src/i18n/languages/ptPT.ts
+++ b/hyperformula/src/i18n/languages/ptPT.ts
@@ -103,6 +103,7 @@ const dictionary: RawTranslationPackage = {
EOMONTH: 'FIMMÊS',
ERF: 'FUNERRO',
ERFC: 'FUNERROCOMPL',
+ 'ERROR.TYPE': 'TIPO.ERRO',
EVEN: 'PAR',
EXACT: 'EXATO',
EXP: 'EXP',
@@ -239,6 +240,7 @@ const dictionary: RawTranslationPackage = {
TRIM: 'ARRUMAR',
TRUE: 'VERDADEIRO',
TRUNC: 'TRUNCAR',
+ TYPE: 'TIPO',
UNICHAR: 'CARACTUNI',
UNICODE: 'UNICODE',
UNIQUE: 'EXCLUSIVOS',
diff --git a/hyperformula/src/i18n/languages/ruRU.ts b/hyperformula/src/i18n/languages/ruRU.ts
index 6b35c1ce2f..0813ca0e1a 100644
--- a/hyperformula/src/i18n/languages/ruRU.ts
+++ b/hyperformula/src/i18n/languages/ruRU.ts
@@ -103,6 +103,7 @@ const dictionary: RawTranslationPackage = {
EOMONTH: 'КОНМЕСЯЦА',
ERF: 'ФОШ',
ERFC: 'ДФОШ',
+ 'ERROR.TYPE': 'ТИП.ОШИБКИ',
EVEN: 'ЧЁТН',
EXACT: 'СОВПАД',
EXP: 'EXP',
@@ -239,6 +240,7 @@ const dictionary: RawTranslationPackage = {
TRIM: 'СЖПРОБЕЛЫ',
TRUE: 'ИСТИНА',
TRUNC: 'ОТБР',
+ TYPE: 'ТИП',
UNICHAR: 'ЮНИСИМВ',
UNICODE: 'UNICODE',
UNIQUE: 'УНИК',
diff --git a/hyperformula/src/i18n/languages/svSE.ts b/hyperformula/src/i18n/languages/svSE.ts
index 5f87580087..7c0b0ee350 100644
--- a/hyperformula/src/i18n/languages/svSE.ts
+++ b/hyperformula/src/i18n/languages/svSE.ts
@@ -103,6 +103,7 @@ const dictionary: RawTranslationPackage = {
EOMONTH: 'SLUTMÅNAD',
ERF: 'FELF',
ERFC: 'FELFK',
+ 'ERROR.TYPE': 'FEL.TYP',
EVEN: 'JÄMN',
EXACT: 'EXAKT',
EXP: 'EXP',
@@ -239,6 +240,7 @@ const dictionary: RawTranslationPackage = {
TRIM: 'RENSA',
TRUE: 'SANT',
TRUNC: 'AVKORTA',
+ TYPE: 'VÄRDETYP',
UNICHAR: 'UNICHAR',
UNICODE: 'UNICODE',
UNIQUE: 'UNIK',
diff --git a/hyperformula/src/i18n/languages/trTR.ts b/hyperformula/src/i18n/languages/trTR.ts
index 93c2176d37..de0e6b04d6 100644
--- a/hyperformula/src/i18n/languages/trTR.ts
+++ b/hyperformula/src/i18n/languages/trTR.ts
@@ -103,6 +103,7 @@ const dictionary: RawTranslationPackage = {
EOMONTH: 'SERİAY',
ERF: 'HATAİŞLEV',
ERFC: 'TÜMHATAİŞLEV',
+ 'ERROR.TYPE': 'HATA.TİPİ',
EVEN: 'ÇİFT',
EXACT: 'ÖZDEŞ',
EXP: 'ÜS',
@@ -239,6 +240,7 @@ const dictionary: RawTranslationPackage = {
TRIM: 'KIRP',
TRUE: 'DOĞRU',
TRUNC: 'NSAT',
+ TYPE: 'TÜR',
UNICHAR: 'UNICODEKARAKTERİ',
UNICODE: 'UNICODE',
UNIQUE: 'BENZERSİZ',
diff --git a/hyperformula/src/interpreter/functionMetadata/categories/information.ts b/hyperformula/src/interpreter/functionMetadata/categories/information.ts
index fd18cd79b6..62cfe3cf00 100644
--- a/hyperformula/src/interpreter/functionMetadata/categories/information.ts
+++ b/hyperformula/src/interpreter/functionMetadata/categories/information.ts
@@ -11,6 +11,13 @@ import {FunctionDoc} from '../FunctionDescription'
* `VERSION` function, whose structural metadata lives in `protectedFunctionMetadata.ts` rather than in a plugin.
*/
export const INFORMATION_DOCS: Record = {
+ 'ERROR.TYPE': {
+ category: 'Information',
+ shortDescription: 'Returns the number of an error type: 2 for #DIV/0!, 3 for #VALUE!, 4 for #REF!, 5 for #NAME?, 6 for #NUM!, 7 for #N/A and 9 for #SPILL!, or #N/A for a value that is not an error or an error without a number.',
+ parameters: [{name: 'value', description: 'The value or cell reference to inspect; any value that is not an error, a number or text included, results in #N/A.'}],
+ documentationUrl: 'https://hyperformula.handsontable.com/docs/guide/built-in-functions.html',
+ examples: ['=ERROR.TYPE(1/0)', '=ERROR.TYPE(A1)', '=ERROR.TYPE(NA())'],
+ },
ISBINARY: {
category: 'Information',
shortDescription: 'Returns TRUE if provided value is a valid binary number.',
@@ -130,6 +137,13 @@ export const INFORMATION_DOCS: Record = {
documentationUrl: 'https://hyperformula.handsontable.com/docs/guide/built-in-functions.html',
examples: ['=SHEETS()', '=SHEETS(A1:B3)'],
},
+ TYPE: {
+ category: 'Information',
+ shortDescription: 'Returns the type of a value: 1 for a number or an empty cell, 2 for text, 4 for a logical value, 16 for an error and 64 for an array or a multi-cell range.',
+ parameters: [{name: 'value', description: 'The value or reference to classify; an error is reported as 16 instead of being propagated, and a single-cell range is classified by the value it holds.'}],
+ documentationUrl: 'https://hyperformula.handsontable.com/docs/guide/built-in-functions.html',
+ examples: ['=TYPE(A1)', '=TYPE("text")', '=TYPE(A1:B2)'],
+ },
// VERSION is a protected function (its plugin, VersionPlugin, is kept out of the general registry so it can
// never be unregistered; see `FunctionRegistry._protectedPlugins`). Its structural metadata (parameters,
// repeatLastArgs) is authored separately in `PROTECTED_FUNCTION_METADATA`.
diff --git a/hyperformula/src/interpreter/plugin/InformationPlugin.ts b/hyperformula/src/interpreter/plugin/InformationPlugin.ts
index 8c10ac05ed..ad6d0d5faf 100644
--- a/hyperformula/src/interpreter/plugin/InformationPlugin.ts
+++ b/hyperformula/src/interpreter/plugin/InformationPlugin.ts
@@ -3,15 +3,53 @@
* Copyright (c) 2025 Handsoncode. All rights reserved.
*/
+import {AbsoluteCellRange} from '../../AbsoluteCellRange'
import {CellError, ErrorType, SimpleCellAddress} from '../../Cell'
import {FormulaVertex} from '../../DependencyGraph/FormulaVertex'
import {ErrorMessage} from '../../error-message'
-import {AstNodeType, ProcedureAst} from '../../parser'
+import {Ast, AstNodeType, ProcedureAst} from '../../parser'
import {InterpreterState} from '../InterpreterState'
import {EmptyValue, InternalScalarValue, InterpreterValue, isExtendedNumber} from '../InterpreterValue'
import {SimpleRangeValue} from '../../SimpleRangeValue'
import {FunctionArgumentType, FunctionPlugin, FunctionPluginTypecheck, ImplementedFunctions} from './FunctionPlugin'
+const TYPE_CODE_NUMBER = 1
+const TYPE_CODE_TEXT = 2
+const TYPE_CODE_BOOLEAN = 4
+const TYPE_CODE_ERROR = 16
+const TYPE_CODE_ARRAY = 64
+
+/**
+ * Codes returned by ERROR.TYPE. #NULL! (code 1) has no counterpart in HyperFormula, and neither has a code for #CYCLE!.
+ */
+const ERROR_TYPE_CODES: Partial> = {
+ [ErrorType.DIV_BY_ZERO]: 2,
+ [ErrorType.VALUE]: 3,
+ [ErrorType.REF]: 4,
+ [ErrorType.NAME]: 5,
+ [ErrorType.NUM]: 6,
+ [ErrorType.NA]: 7,
+ [ErrorType.SPILL]: 9,
+}
+
+/**
+ * Returns the TYPE code of a single (non-array) value.
+ *
+ * @param value
+ */
+function valueTypeCode(value: InterpreterValue): number {
+ if (value instanceof CellError) {
+ return TYPE_CODE_ERROR
+ }
+ if (typeof value === 'string') {
+ return TYPE_CODE_TEXT
+ }
+ if (typeof value === 'boolean') {
+ return TYPE_CODE_BOOLEAN
+ }
+ return TYPE_CODE_NUMBER
+}
+
/**
* Interpreter plugin containing information functions
*/
@@ -35,6 +73,12 @@ export class InformationPlugin extends FunctionPlugin implements FunctionPluginT
doesNotNeedArgumentsToBeComputed: true,
vectorizationForbidden: true,
},
+ 'ERROR.TYPE': {
+ method: 'errortype',
+ parameters: [
+ {argumentType: FunctionArgumentType.SCALAR}
+ ]
+ },
'ISBINARY': {
method: 'isbinary',
parameters: [
@@ -149,7 +193,88 @@ export class InformationPlugin extends FunctionPlugin implements FunctionPluginT
],
doesNotNeedArgumentsToBeComputed: true,
vectorizationForbidden: true,
+ },
+ 'TYPE': {
+ method: 'type',
+ parameters: [
+ {argumentType: FunctionArgumentType.ANY}
+ ],
+ vectorizationForbidden: true,
+ }
+ }
+
+ /**
+ * Corresponds to ERROR.TYPE(value)
+ *
+ * Returns the number of an error type (2-7 and 9), or #N/A when the value is not an error or the error has no code
+ *
+ * @param ast
+ * @param state
+ */
+ public errortype(ast: ProcedureAst, state: InterpreterState): InterpreterValue {
+ return this.runFunction(ast.args, state, this.metadata('ERROR.TYPE'), (arg: InternalScalarValue) => {
+ if (!(arg instanceof CellError)) {
+ return new CellError(ErrorType.NA)
+ }
+ return ERROR_TYPE_CODES[arg.type] ?? new CellError(ErrorType.NA)
+ })
+ }
+
+ /**
+ * Corresponds to TYPE(value)
+ *
+ * Returns the type of a value: 1 for numbers and empty cells, 2 for text, 4 for booleans, 16 for errors
+ * and 64 for arrays and multi-cell ranges. Errors are classified, not propagated.
+ *
+ * @param ast
+ * @param state
+ */
+ public type(ast: ProcedureAst, state: InterpreterState): InterpreterValue {
+ if (ast.args.length !== 1) {
+ return new CellError(ErrorType.NA, ErrorMessage.WrongArgNumber)
+ }
+ if (this.isRowOrColumnOfMultiCellRange(ast.args[0], state)) {
+ return TYPE_CODE_ARRAY
+ }
+ const value = this.evaluateAst(ast.args[0], state)
+ if (value instanceof SimpleRangeValue) {
+ return TYPE_CODE_ARRAY
+ }
+ return valueTypeCode(value)
+ }
+
+ /**
+ * ROW and COLUMN evaluate to a scalar here, while Excel returns an array when they are given a multi-cell range.
+ * TYPE reports the Excel classification.
+ *
+ * @param ast
+ * @param state
+ */
+ private isRowOrColumnOfMultiCellRange(ast: Ast, state: InterpreterState): boolean {
+ while (ast.type === AstNodeType.PARENTHESIS) {
+ ast = ast.expression
+ }
+ if (ast.type !== AstNodeType.FUNCTION_CALL || (ast.procedureName !== 'ROW' && ast.procedureName !== 'COLUMN')) {
+ return false
+ }
+ let arg = ast.args[0]
+ while (arg !== undefined && arg.type === AstNodeType.PARENTHESIS) {
+ arg = arg.expression
+ }
+ if (arg === undefined) {
+ return false
+ }
+ if (arg.type !== AstNodeType.CELL_RANGE && arg.type !== AstNodeType.COLUMN_RANGE && arg.type !== AstNodeType.ROW_RANGE) {
+ return false
+ }
+ if (arg.start.sheet !== arg.end.sheet) {
+ // A range across sheets makes ROW and COLUMN return #REF!, which TYPE classifies as an error.
+ return false
+ }
+ if (arg.type === AstNodeType.CELL_RANGE) {
+ return AbsoluteCellRange.fromAst(arg, state.formulaAddress).size() > 1
}
+ return true
}
/**
diff --git a/hyperformula/src/license/functionCapabilities.ts b/hyperformula/src/license/functionCapabilities.ts
index a26733ec5f..148827e21b 100644
--- a/hyperformula/src/license/functionCapabilities.ts
+++ b/hyperformula/src/license/functionCapabilities.ts
@@ -83,6 +83,7 @@ const UNGROUPED_FUNCTIONS = [
'COVARIANCE.S', 'CSC', 'CSCH', 'CUMIPMT', 'CUMPRINC', 'DAVERAGE', 'DB', 'DCOUNT', 'DCOUNTA', 'DDB',
'DEC2BIN', 'DEC2OCT', 'DECIMAL', 'DEGREES', 'DELTA', 'DEVSQ', 'DGET', 'DMAX', 'DMIN', 'DOLLARDE',
'DOLLARFR', 'DPRODUCT', 'DSTDEV', 'DSTDEVP', 'DSUM', 'DVAR', 'DVARP', 'EFFECT', 'ERF', 'ERFC',
+ 'ERROR.TYPE',
'EXPON.DIST', 'F.DIST', 'F.DIST.RT', 'F.INV', 'F.INV.RT', 'F.TEST', 'FACT', 'FACTDOUBLE', 'FISHER',
'FISHERINV', 'FLOOR.MATH', 'FLOOR.PRECISE', 'FORMULATEXT', 'FVSCHEDULE', 'GAMMA', 'GAMMA.DIST',
'GAMMA.INV', 'GAMMALN', 'GAUSS', 'GCD', 'GEOMEAN', 'HARMEAN', 'HEX2BIN', 'HEX2OCT', 'HYPGEOM.DIST',
@@ -95,7 +96,7 @@ const UNGROUPED_FUNCTIONS = [
'PHI', 'POISSON.DIST', 'QUARTILE.EXC', 'QUARTILE.INC', 'RADIANS', 'ROMAN', 'RRI', 'RSQ', 'SEC', 'SECH',
'SERIESSUM', 'SHEET', 'SHEETS', 'SINH', 'SKEW', 'SKEW.P', 'SLOPE', 'SPLIT', 'SQRTPI', 'STANDARDIZE',
'STEYX', 'SUMX2MY2', 'SUMX2PY2', 'SYD', 'T.DIST', 'T.DIST.2T', 'T.DIST.RT', 'T.INV', 'T.INV.2T', 'T.TEST',
- 'TANH', 'TBILLEQ', 'TBILLPRICE', 'TBILLYIELD', 'TDIST', 'TIMEVALUE', 'UNICODE', 'VARA', 'VARPA',
+ 'TANH', 'TBILLEQ', 'TBILLPRICE', 'TBILLYIELD', 'TDIST', 'TIMEVALUE', 'TYPE', 'UNICODE', 'VARA', 'VARPA',
'WEIBULL.DIST', 'WORKDAY.INTL', 'Z.TEST',
]