Improve error messages for invalid option values

By adding the option name to UserOption object, it is now possible to
display the name of the affected option when the given option value is
not valid.

Fixes #12635
diff --git a/mesonbuild/cargo/interpreter.py b/mesonbuild/cargo/interpreter.py
index f1ed239..e1b0928 100644
--- a/mesonbuild/cargo/interpreter.py
+++ b/mesonbuild/cargo/interpreter.py
@@ -716,7 +716,7 @@
     for feature in cargo.features:
         key = OptionKey(_option_name(feature), subproject=subp_name)
         enabled = feature == 'default'
-        options[key] = coredata.UserBooleanOption(f'Cargo {feature} feature', enabled)
+        options[key] = coredata.UserBooleanOption(key.name, f'Cargo {feature} feature', enabled)
 
     ast = _create_project(cargo, build)
     ast += [build.assign(build.function('import', [build.string('rust')]), 'rust')]
diff --git a/mesonbuild/compilers/c.py b/mesonbuild/compilers/c.py
index 048649a..7e21461 100644
--- a/mesonbuild/compilers/c.py
+++ b/mesonbuild/compilers/c.py
@@ -152,12 +152,13 @@
     def get_options(self) -> 'MutableKeyedOptionDictType':
         opts = super().get_options()
         if self.info.is_windows() or self.info.is_cygwin():
-            opts.update({
-                OptionKey('winlibs', machine=self.for_machine, lang=self.language): coredata.UserArrayOption(
-                    'Standard Win libraries to link against',
-                    gnu_winlibs,
-                ),
-            })
+            self.update_options(
+                opts,
+                self.create_option(coredata.UserArrayOption,
+                                   OptionKey('winlibs', machine=self.for_machine, lang=self.language),
+                                   'Standard Win libraries to link against',
+                                   gnu_winlibs),
+            )
         return opts
 
     def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
@@ -290,12 +291,13 @@
         assert isinstance(std_opt, coredata.UserStdOption), 'for mypy'
         std_opt.set_versions(stds, gnu=True)
         if self.info.is_windows() or self.info.is_cygwin():
-            opts.update({
-                key.evolve('winlibs'): coredata.UserArrayOption(
-                    'Standard Win libraries to link against',
-                    gnu_winlibs,
-                ),
-            })
+            self.update_options(
+                opts,
+                self.create_option(coredata.UserArrayOption,
+                                   key.evolve('winlibs'),
+                                   'Standard Win libraries to link against',
+                                   gnu_winlibs),
+            )
         return opts
 
     def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
@@ -425,15 +427,16 @@
 
     """Shared methods that apply to MSVC-like C compilers."""
 
-    def get_options(self) -> 'MutableKeyedOptionDictType':
-        opts = super().get_options()
-        opts.update({
-            OptionKey('winlibs', machine=self.for_machine, lang=self.language): coredata.UserArrayOption(
+    def get_options(self) -> MutableKeyedOptionDictType:
+        return self.update_options(
+            super().get_options(),
+            self.create_option(
+                coredata.UserArrayOption,
+                OptionKey('winlibs', machine=self.for_machine, lang=self.language),
                 'Windows libs to link against.',
                 msvc_winlibs,
             ),
-        })
-        return opts
+        )
 
     def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
         # need a TypeDict to make this work
diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py
index 9e99706..5854c9e 100644
--- a/mesonbuild/compilers/compilers.py
+++ b/mesonbuild/compilers/compilers.py
@@ -9,6 +9,7 @@
 import enum
 import itertools
 import typing as T
+from dataclasses import dataclass
 from functools import lru_cache
 
 from .. import coredata
@@ -34,6 +35,7 @@
 
     CompilerType = T.TypeVar('CompilerType', bound='Compiler')
     _T = T.TypeVar('_T')
+    UserOptionType = T.TypeVar('UserOptionType', bound=coredata.UserOption)
 
 """This file contains the data files of all compilers Meson knows
 about. To support a new compiler, add its information below.
@@ -206,36 +208,46 @@
 
 MSCRT_VALS = ['none', 'md', 'mdd', 'mt', 'mtd']
 
-base_options: 'KeyedOptionDictType' = {
-    OptionKey('b_pch'): coredata.UserBooleanOption('Use precompiled headers', True),
-    OptionKey('b_lto'): coredata.UserBooleanOption('Use link time optimization', False),
-    OptionKey('b_lto_threads'): coredata.UserIntegerOption('Use multiple threads for Link Time Optimization', (None, None, 0)),
-    OptionKey('b_lto_mode'): coredata.UserComboOption('Select between different LTO modes.',
-                                                      ['default', 'thin'],
-                                                      'default'),
-    OptionKey('b_thinlto_cache'): coredata.UserBooleanOption('Use LLVM ThinLTO caching for faster incremental builds', False),
-    OptionKey('b_thinlto_cache_dir'): coredata.UserStringOption('Directory to store ThinLTO cache objects', ''),
-    OptionKey('b_sanitize'): coredata.UserComboOption('Code sanitizer to use',
-                                                      ['none', 'address', 'thread', 'undefined', 'memory', 'leak', 'address,undefined'],
-                                                      'none'),
-    OptionKey('b_lundef'): coredata.UserBooleanOption('Use -Wl,--no-undefined when linking', True),
-    OptionKey('b_asneeded'): coredata.UserBooleanOption('Use -Wl,--as-needed when linking', True),
-    OptionKey('b_pgo'): coredata.UserComboOption('Use profile guided optimization',
-                                                 ['off', 'generate', 'use'],
-                                                 'off'),
-    OptionKey('b_coverage'): coredata.UserBooleanOption('Enable coverage tracking.', False),
-    OptionKey('b_colorout'): coredata.UserComboOption('Use colored output',
-                                                      ['auto', 'always', 'never'],
-                                                      'always'),
-    OptionKey('b_ndebug'): coredata.UserComboOption('Disable asserts', ['true', 'false', 'if-release'], 'false'),
-    OptionKey('b_staticpic'): coredata.UserBooleanOption('Build static libraries as position independent', True),
-    OptionKey('b_pie'): coredata.UserBooleanOption('Build executables as position independent', False),
-    OptionKey('b_bitcode'): coredata.UserBooleanOption('Generate and embed bitcode (only macOS/iOS/tvOS)', False),
-    OptionKey('b_vscrt'): coredata.UserComboOption('VS run-time library type to use.',
-                                                   MSCRT_VALS + ['from_buildtype', 'static_from_buildtype'],
-                                                   'from_buildtype'),
+@dataclass
+class BaseOption(T.Generic[coredata._T, coredata._U]):
+    opt_type: T.Type[coredata._U]
+    description: str
+    default: T.Any = None
+    choices: T.Any = None
+
+    def init_option(self, name: OptionKey) -> coredata._U:
+        keywords = {'value': self.default}
+        if self.choices:
+            keywords['choices'] = self.choices
+        return self.opt_type(name.name, self.description, **keywords)
+
+BASE_OPTIONS: T.Mapping[OptionKey, BaseOption] = {
+    OptionKey('b_pch'): BaseOption(coredata.UserBooleanOption, 'Use precompiled headers', True),
+    OptionKey('b_lto'): BaseOption(coredata.UserBooleanOption, 'Use link time optimization', False),
+    OptionKey('b_lto_threads'): BaseOption(coredata.UserIntegerOption, 'Use multiple threads for Link Time Optimization', (None, None, 0)),
+    OptionKey('b_lto_mode'): BaseOption(coredata.UserComboOption, 'Select between different LTO modes.', 'default',
+                                        choices=['default', 'thin']),
+    OptionKey('b_thinlto_cache'): BaseOption(coredata.UserBooleanOption, 'Use LLVM ThinLTO caching for faster incremental builds', False),
+    OptionKey('b_thinlto_cache_dir'): BaseOption(coredata.UserStringOption, 'Directory to store ThinLTO cache objects', ''),
+    OptionKey('b_sanitize'): BaseOption(coredata.UserComboOption, 'Code sanitizer to use', 'none',
+                                        choices=['none', 'address', 'thread', 'undefined', 'memory', 'leak', 'address,undefined']),
+    OptionKey('b_lundef'): BaseOption(coredata.UserBooleanOption, 'Use -Wl,--no-undefined when linking', True),
+    OptionKey('b_asneeded'): BaseOption(coredata.UserBooleanOption, 'Use -Wl,--as-needed when linking', True),
+    OptionKey('b_pgo'): BaseOption(coredata.UserComboOption, 'Use profile guided optimization', 'off',
+                                   choices=['off', 'generate', 'use']),
+    OptionKey('b_coverage'): BaseOption(coredata.UserBooleanOption, 'Enable coverage tracking.', False),
+    OptionKey('b_colorout'): BaseOption(coredata.UserComboOption, 'Use colored output', 'always',
+                                        choices=['auto', 'always', 'never']),
+    OptionKey('b_ndebug'): BaseOption(coredata.UserComboOption, 'Disable asserts', 'false', choices=['true', 'false', 'if-release']),
+    OptionKey('b_staticpic'): BaseOption(coredata.UserBooleanOption, 'Build static libraries as position independent', True),
+    OptionKey('b_pie'): BaseOption(coredata.UserBooleanOption, 'Build executables as position independent', False),
+    OptionKey('b_bitcode'): BaseOption(coredata.UserBooleanOption, 'Generate and embed bitcode (only macOS/iOS/tvOS)', False),
+    OptionKey('b_vscrt'): BaseOption(coredata.UserComboOption, 'VS run-time library type to use.', 'from_buildtype',
+                                     choices=MSCRT_VALS + ['from_buildtype', 'static_from_buildtype']),
 }
 
+base_options: KeyedOptionDictType = {key: base_opt.init_option(key) for key, base_opt in BASE_OPTIONS.items()}
+
 def option_enabled(boptions: T.Set[OptionKey], options: 'KeyedOptionDictType',
                    option: OptionKey) -> bool:
     try:
@@ -577,6 +589,14 @@
         """
         return []
 
+    def create_option(self, option_type: T.Type[UserOptionType], option_key: OptionKey, *args: T.Any, **kwargs: T.Any) -> T.Tuple[OptionKey, UserOptionType]:
+        return option_key, option_type(f'{self.language}_{option_key.name}', *args, **kwargs)
+
+    @staticmethod
+    def update_options(options: MutableKeyedOptionDictType, *args: T.Tuple[OptionKey, UserOptionType]) -> MutableKeyedOptionDictType:
+        options.update(args)
+        return options
+
     def get_options(self) -> 'MutableKeyedOptionDictType':
         return {}
 
@@ -1346,10 +1366,12 @@
     link_options = env.options.get(largkey, [])
 
     cargs = coredata.UserArrayOption(
+        f'{lang}_{argkey.name}',
         description + ' compiler',
         comp_options, split_args=True, allow_dups=True)
 
     largs = coredata.UserArrayOption(
+        f'{lang}_{largkey.name}',
         description + ' linker',
         link_options, split_args=True, allow_dups=True)
 
diff --git a/mesonbuild/compilers/cpp.py b/mesonbuild/compilers/cpp.py
index 5e412e7..540dedb 100644
--- a/mesonbuild/compilers/cpp.py
+++ b/mesonbuild/compilers/cpp.py
@@ -237,18 +237,22 @@
     def get_options(self) -> 'MutableKeyedOptionDictType':
         opts = CPPCompiler.get_options(self)
         key = OptionKey('key', machine=self.for_machine, lang=self.language)
-        opts.update({
-            key.evolve('debugstl'): coredata.UserBooleanOption(
-                'STL debug mode',
-                False,
-            ),
-            key.evolve('eh'): coredata.UserComboOption(
-                'C++ exception handling type.',
-                ['none', 'default', 'a', 's', 'sc'],
-                'default',
-            ),
-            key.evolve('rtti'): coredata.UserBooleanOption('Enable RTTI', True),
-        })
+        self.update_options(
+            opts,
+            self.create_option(coredata.UserComboOption,
+                               key.evolve('eh'),
+                               'C++ exception handling type.',
+                               ['none', 'default', 'a', 's', 'sc'],
+                               'default'),
+            self.create_option(coredata.UserBooleanOption,
+                               key.evolve('rtti'),
+                               'Enable RTTI',
+                               True),
+            self.create_option(coredata.UserBooleanOption,
+                               key.evolve('debugstl'),
+                               'STL debug mode',
+                               False),
+        )
         cppstd_choices = [
             'c++98', 'c++03', 'c++11', 'c++14', 'c++17', 'c++1z', 'c++2a', 'c++20',
         ]
@@ -260,12 +264,13 @@
         assert isinstance(std_opt, coredata.UserStdOption), 'for mypy'
         std_opt.set_versions(cppstd_choices, gnu=True)
         if self.info.is_windows() or self.info.is_cygwin():
-            opts.update({
-                key.evolve('winlibs'): coredata.UserArrayOption(
-                    'Standard Win libraries to link against',
-                    gnu_winlibs,
-                ),
-            })
+            self.update_options(
+                opts,
+                self.create_option(coredata.UserArrayOption,
+                                   key.evolve('winlibs'),
+                                   'Standard Win libraries to link against',
+                                   gnu_winlibs),
+            )
         return opts
 
     def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
@@ -378,13 +383,14 @@
     def get_options(self) -> 'MutableKeyedOptionDictType':
         opts = CPPCompiler.get_options(self)
         key = OptionKey('std', machine=self.for_machine, lang=self.language)
-        opts.update({
-            key.evolve('eh'): coredata.UserComboOption(
-                'C++ exception handling type.',
-                ['none', 'default', 'a', 's', 'sc'],
-                'default',
-            ),
-        })
+        self.update_options(
+            opts,
+            self.create_option(coredata.UserComboOption,
+                               key.evolve('eh'),
+                               'C++ exception handling type.',
+                               ['none', 'default', 'a', 's', 'sc'],
+                               'default'),
+        )
         std_opt = opts[key]
         assert isinstance(std_opt, coredata.UserStdOption), 'for mypy'
         std_opt.set_versions(['c++98', 'c++03', 'c++11', 'c++14', 'c++17'], gnu=True)
@@ -426,18 +432,22 @@
     def get_options(self) -> 'MutableKeyedOptionDictType':
         key = OptionKey('std', machine=self.for_machine, lang=self.language)
         opts = CPPCompiler.get_options(self)
-        opts.update({
-            key.evolve('eh'): coredata.UserComboOption(
-                'C++ exception handling type.',
-                ['none', 'default', 'a', 's', 'sc'],
-                'default',
-            ),
-            key.evolve('rtti'): coredata.UserBooleanOption('Enable RTTI', True),
-            key.evolve('debugstl'): coredata.UserBooleanOption(
-                'STL debug mode',
-                False,
-            )
-        })
+        self.update_options(
+            opts,
+            self.create_option(coredata.UserComboOption,
+                               key.evolve('eh'),
+                               'C++ exception handling type.',
+                               ['none', 'default', 'a', 's', 'sc'],
+                               'default'),
+            self.create_option(coredata.UserBooleanOption,
+                               key.evolve('rtti'),
+                               'Enable RTTI',
+                               True),
+            self.create_option(coredata.UserBooleanOption,
+                               key.evolve('debugstl'),
+                               'STL debug mode',
+                               False),
+        )
         cppstd_choices = [
             'c++98', 'c++03', 'c++11', 'c++14', 'c++17', 'c++1z',
             'c++2a', 'c++20',
@@ -450,12 +460,13 @@
         assert isinstance(std_opt, coredata.UserStdOption), 'for mypy'
         std_opt.set_versions(cppstd_choices, gnu=True)
         if self.info.is_windows() or self.info.is_cygwin():
-            opts.update({
-                key.evolve('winlibs'): coredata.UserArrayOption(
-                    'Standard Win libraries to link against',
-                    gnu_winlibs,
-                ),
-            })
+            self.update_options(
+                opts,
+                self.create_option(coredata.UserArrayOption,
+                                   key.evolve('winlibs'),
+                                   'Standard Win libraries to link against',
+                                   gnu_winlibs),
+            )
         return opts
 
     def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
@@ -550,17 +561,18 @@
             cpp_stds += ['c++20']
 
         key = OptionKey('std', machine=self.for_machine, lang=self.language)
-        opts.update({
-            key.evolve('eh'): coredata.UserComboOption(
-                'C++ exception handling type.',
-                ['none', 'default', 'a', 's', 'sc'],
-                'default',
-            ),
-            key.evolve('debugstl'): coredata.UserBooleanOption(
-                'STL debug mode',
-                False,
-            ),
-        })
+        self.update_options(
+            opts,
+            self.create_option(coredata.UserComboOption,
+                               key.evolve('eh'),
+                               'C++ exception handling type.',
+                               ['none', 'default', 'a', 's', 'sc'],
+                               'default'),
+            self.create_option(coredata.UserBooleanOption,
+                               key.evolve('debugstl'),
+                               'STL debug mode',
+                               False),
+        )
         std_opt = opts[key]
         assert isinstance(std_opt, coredata.UserStdOption), 'for mypy'
         std_opt.set_versions(cpp_stds, gnu=True)
@@ -628,15 +640,22 @@
             g_stds += ['gnu++2a']
 
         key = OptionKey('std', machine=self.for_machine, lang=self.language)
-        opts.update({
-            key.evolve('eh'): coredata.UserComboOption(
-                'C++ exception handling type.',
-                ['none', 'default', 'a', 's', 'sc'],
-                'default',
-            ),
-            key.evolve('rtti'): coredata.UserBooleanOption('Enable RTTI', True),
-            key.evolve('debugstl'): coredata.UserBooleanOption('STL debug mode', False),
-        })
+        self.update_options(
+            opts,
+            self.create_option(coredata.UserComboOption,
+                               key.evolve('eh'),
+                               'C++ exception handling type.',
+                               ['none', 'default', 'a', 's', 'sc'],
+                               'default'),
+            self.create_option(coredata.UserBooleanOption,
+                               key.evolve('rtti'),
+                               'Enable RTTI',
+                               True),
+            self.create_option(coredata.UserBooleanOption,
+                               key.evolve('debugstl'),
+                               'STL debug mode',
+                               False),
+        )
         std_opt = opts[key]
         assert isinstance(std_opt, coredata.UserStdOption), 'for mypy'
         std_opt.set_versions(c_stds + g_stds)
@@ -694,18 +713,22 @@
 
     def _get_options_impl(self, opts: 'MutableKeyedOptionDictType', cpp_stds: T.List[str]) -> 'MutableKeyedOptionDictType':
         key = OptionKey('std', machine=self.for_machine, lang=self.language)
-        opts.update({
-            key.evolve('eh'): coredata.UserComboOption(
-                'C++ exception handling type.',
-                ['none', 'default', 'a', 's', 'sc'],
-                'default',
-            ),
-            key.evolve('rtti'): coredata.UserBooleanOption('Enable RTTI', True),
-            key.evolve('winlibs'): coredata.UserArrayOption(
-                'Windows libs to link against.',
-                msvc_winlibs,
-            ),
-        })
+        self.update_options(
+            opts,
+            self.create_option(coredata.UserComboOption,
+                               key.evolve('eh'),
+                               'C++ exception handling type.',
+                               ['none', 'default', 'a', 's', 'sc'],
+                               'default'),
+            self.create_option(coredata.UserBooleanOption,
+                               key.evolve('rtti'),
+                               'Enable RTTI',
+                               True),
+            self.create_option(coredata.UserArrayOption,
+                               key.evolve('winlibs'),
+                               'Windows libs to link against.',
+                               msvc_winlibs),
+        )
         std_opt = opts[key]
         assert isinstance(std_opt, coredata.UserStdOption), 'for mypy'
         std_opt.set_versions(cpp_stds)
diff --git a/mesonbuild/compilers/cuda.py b/mesonbuild/compilers/cuda.py
index 391107f..ab48107 100644
--- a/mesonbuild/compilers/cuda.py
+++ b/mesonbuild/compilers/cuda.py
@@ -630,10 +630,6 @@
     _CPP20_VERSION = '>=12.0'
 
     def get_options(self) -> 'MutableKeyedOptionDictType':
-        opts = super().get_options()
-        std_key = OptionKey('std', machine=self.for_machine, lang=self.language)
-        ccbindir_key = OptionKey('ccbindir', machine=self.for_machine, lang=self.language)
-
         cpp_stds = ['none', 'c++03', 'c++11']
         if version_compare(self.version, self._CPP14_VERSION):
             cpp_stds += ['c++14']
@@ -642,13 +638,18 @@
         if version_compare(self.version, self._CPP20_VERSION):
             cpp_stds += ['c++20']
 
-        opts.update({
-            std_key:      coredata.UserComboOption('C++ language standard to use with CUDA',
-                                                   cpp_stds, 'none'),
-            ccbindir_key: coredata.UserStringOption('CUDA non-default toolchain directory to use (-ccbin)',
-                                                    ''),
-        })
-        return opts
+        return self.update_options(
+            super().get_options(),
+            self.create_option(coredata.UserComboOption,
+                               OptionKey('std', machine=self.for_machine, lang=self.language),
+                               'C++ language standard to use with CUDA',
+                               cpp_stds,
+                               'none'),
+            self.create_option(coredata.UserStringOption,
+                               OptionKey('ccbindir', machine=self.for_machine, lang=self.language),
+                               'CUDA non-default toolchain directory to use (-ccbin)',
+                               ''),
+        )
 
     def _to_host_compiler_options(self, options: 'KeyedOptionDictType') -> 'KeyedOptionDictType':
         """
diff --git a/mesonbuild/compilers/cython.py b/mesonbuild/compilers/cython.py
index a58b3a8..30cec81 100644
--- a/mesonbuild/compilers/cython.py
+++ b/mesonbuild/compilers/cython.py
@@ -67,20 +67,19 @@
         return new
 
     def get_options(self) -> 'MutableKeyedOptionDictType':
-        opts = super().get_options()
-        opts.update({
-            OptionKey('version', machine=self.for_machine, lang=self.language): coredata.UserComboOption(
-                'Python version to target',
-                ['2', '3'],
-                '3',
-            ),
-            OptionKey('language', machine=self.for_machine, lang=self.language): coredata.UserComboOption(
-                'Output C or C++ files',
-                ['c', 'cpp'],
-                'c',
-            )
-        })
-        return opts
+        return self.update_options(
+            super().get_options(),
+            self.create_option(coredata.UserComboOption,
+                               OptionKey('version', machine=self.for_machine, lang=self.language),
+                               'Python version to target',
+                               ['2', '3'],
+                               '3'),
+            self.create_option(coredata.UserComboOption,
+                               OptionKey('language', machine=self.for_machine, lang=self.language),
+                               'Output C or C++ files',
+                               ['c', 'cpp'],
+                               'c'),
+        )
 
     def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
         args: T.List[str] = []
diff --git a/mesonbuild/compilers/fortran.py b/mesonbuild/compilers/fortran.py
index 3a73650..2cdff36 100644
--- a/mesonbuild/compilers/fortran.py
+++ b/mesonbuild/compilers/fortran.py
@@ -112,16 +112,14 @@
         return self._has_multi_link_arguments(args, env, 'stop; end program')
 
     def get_options(self) -> 'MutableKeyedOptionDictType':
-        opts = super().get_options()
-        key = OptionKey('std', machine=self.for_machine, lang=self.language)
-        opts.update({
-            key: coredata.UserComboOption(
-                'Fortran language standard to use',
-                ['none'],
-                'none',
-            ),
-        })
-        return opts
+        return self.update_options(
+            super().get_options(),
+            self.create_option(coredata.UserComboOption,
+                               OptionKey('std', machine=self.for_machine, lang=self.language),
+                               'Fortran language standard to use',
+                               ['none'],
+                               'none'),
+        )
 
 
 class GnuFortranCompiler(GnuCompiler, FortranCompiler):
diff --git a/mesonbuild/compilers/mixins/emscripten.py b/mesonbuild/compilers/mixins/emscripten.py
index d10c499..bb8a520 100644
--- a/mesonbuild/compilers/mixins/emscripten.py
+++ b/mesonbuild/compilers/mixins/emscripten.py
@@ -55,17 +55,16 @@
             args.append(f'-sPTHREAD_POOL_SIZE={count}')
         return args
 
-    def get_options(self) -> 'coredata.MutableKeyedOptionDictType':
-        opts = super().get_options()
-        key = OptionKey('thread_count', machine=self.for_machine, lang=self.language)
-        opts.update({
-            key: coredata.UserIntegerOption(
+    def get_options(self) -> coredata.MutableKeyedOptionDictType:
+        return self.update_options(
+            super().get_options(),
+            self.create_option(
+                coredata.UserIntegerOption,
+                OptionKey('thread_count', machine=self.for_machine, lang=self.language),
                 'Number of threads to use in web assembly, set to 0 to disable',
                 (0, None, 4),  # Default was picked at random
             ),
-        })
-
-        return opts
+        )
 
     @classmethod
     def native_args_to_unix(cls, args: T.List[str]) -> T.List[str]:
diff --git a/mesonbuild/compilers/objc.py b/mesonbuild/compilers/objc.py
index f805bd6..7c19c1b 100644
--- a/mesonbuild/compilers/objc.py
+++ b/mesonbuild/compilers/objc.py
@@ -78,15 +78,14 @@
                           'everything': ['-Weverything']}
 
     def get_options(self) -> 'coredata.MutableKeyedOptionDictType':
-        opts = super().get_options()
-        opts.update({
-            OptionKey('std', machine=self.for_machine, lang='c'): coredata.UserComboOption(
-                'C language standard to use',
-                ['none', 'c89', 'c99', 'c11', 'c17', 'gnu89', 'gnu99', 'gnu11', 'gnu17'],
-                'none',
-            )
-        })
-        return opts
+        return self.update_options(
+            super().get_options(),
+            self.create_option(coredata.UserComboOption,
+                               OptionKey('std', machine=self.for_machine, lang='c'),
+                               'C language standard to use',
+                               ['none', 'c89', 'c99', 'c11', 'c17', 'gnu89', 'gnu99', 'gnu11', 'gnu17'],
+                               'none'),
+        )
 
     def get_option_compile_args(self, options: 'coredata.KeyedOptionDictType') -> T.List[str]:
         args = []
diff --git a/mesonbuild/compilers/objcpp.py b/mesonbuild/compilers/objcpp.py
index baf4fb5..46eaa50 100644
--- a/mesonbuild/compilers/objcpp.py
+++ b/mesonbuild/compilers/objcpp.py
@@ -77,18 +77,17 @@
                           '3': default_warn_args + ['-Wextra', '-Wpedantic'],
                           'everything': ['-Weverything']}
 
-    def get_options(self) -> 'coredata.MutableKeyedOptionDictType':
-        opts = super().get_options()
-        opts.update({
-            OptionKey('std', machine=self.for_machine, lang='cpp'): coredata.UserComboOption(
-                'C++ language standard to use',
-                ['none', 'c++98', 'c++11', 'c++14', 'c++17', 'c++20', 'c++2b',
-                 'gnu++98', 'gnu++11', 'gnu++14', 'gnu++17', 'gnu++20',
-                 'gnu++2b'],
-                'none',
-            )
-        })
-        return opts
+    def get_options(self) -> coredata.MutableKeyedOptionDictType:
+        return self.update_options(
+            super().get_options(),
+            self.create_option(coredata.UserComboOption,
+                               OptionKey('std', machine=self.for_machine, lang='cpp'),
+                               'C++ language standard to use',
+                               ['none', 'c++98', 'c++11', 'c++14', 'c++17', 'c++20', 'c++2b',
+                                'gnu++98', 'gnu++11', 'gnu++14', 'gnu++17', 'gnu++20',
+                                'gnu++2b'],
+                               'none'),
+        )
 
     def get_option_compile_args(self, options: 'coredata.KeyedOptionDictType') -> T.List[str]:
         args = []
diff --git a/mesonbuild/compilers/rust.py b/mesonbuild/compilers/rust.py
index 65f4ac1..05e8b2b 100644
--- a/mesonbuild/compilers/rust.py
+++ b/mesonbuild/compilers/rust.py
@@ -156,15 +156,12 @@
     # C compiler for dynamic linking, as such we invoke the C compiler's
     # use_linker_args method instead.
 
-    def get_options(self) -> 'MutableKeyedOptionDictType':
-        key = OptionKey('std', machine=self.for_machine, lang=self.language)
-        return {
-            key: coredata.UserComboOption(
-                'Rust edition to use',
-                ['none', '2015', '2018', '2021'],
-                'none',
-            ),
-        }
+    def get_options(self) -> MutableKeyedOptionDictType:
+        return dict((self.create_option(coredata.UserComboOption,
+                                        OptionKey('std', machine=self.for_machine, lang=self.language),
+                                        'Rust edition to use',
+                                        ['none', '2015', '2018', '2021'],
+                                        'none'),))
 
     def get_dependency_compile_args(self, dep: 'Dependency') -> T.List[str]:
         # Rust doesn't have dependency compile arguments so simply return
diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py
index 1123107..df57598 100644
--- a/mesonbuild/coredata.py
+++ b/mesonbuild/coredata.py
@@ -109,10 +109,11 @@
 
 
 class UserOption(T.Generic[_T], HoldableObject):
-    def __init__(self, description: str, choices: T.Optional[T.Union[str, T.List[_T]]],
+    def __init__(self, name: str, description: str, choices: T.Optional[T.Union[str, T.List[_T]]],
                  yielding: bool,
                  deprecated: T.Union[bool, str, T.Dict[str, str], T.List[str]] = False):
         super().__init__()
+        self.name = name
         self.choices = choices
         self.description = description
         if not isinstance(yielding, bool):
@@ -140,20 +141,20 @@
         return self.value != oldvalue
 
 class UserStringOption(UserOption[str]):
-    def __init__(self, description: str, value: T.Any, yielding: bool = DEFAULT_YIELDING,
+    def __init__(self, name: str, description: str, value: T.Any, yielding: bool = DEFAULT_YIELDING,
                  deprecated: T.Union[bool, str, T.Dict[str, str], T.List[str]] = False):
-        super().__init__(description, None, yielding, deprecated)
+        super().__init__(name, description, None, yielding, deprecated)
         self.set_value(value)
 
     def validate_value(self, value: T.Any) -> str:
         if not isinstance(value, str):
-            raise MesonException('Value "%s" for string option is not a string.' % str(value))
+            raise MesonException(f'The value of option "{self.name}" is "{value}", which is not a string.')
         return value
 
 class UserBooleanOption(UserOption[bool]):
-    def __init__(self, description: str, value: bool, yielding: bool = DEFAULT_YIELDING,
+    def __init__(self, name: str, description: str, value: bool, yielding: bool = DEFAULT_YIELDING,
                  deprecated: T.Union[bool, str, T.Dict[str, str], T.List[str]] = False):
-        super().__init__(description, [True, False], yielding, deprecated)
+        super().__init__(name, description, [True, False], yielding, deprecated)
         self.set_value(value)
 
     def __bool__(self) -> bool:
@@ -163,15 +164,15 @@
         if isinstance(value, bool):
             return value
         if not isinstance(value, str):
-            raise MesonException(f'Value {value} cannot be converted to a boolean')
+            raise MesonException(f'Option "{self.name}" value {value} cannot be converted to a boolean')
         if value.lower() == 'true':
             return True
         if value.lower() == 'false':
             return False
-        raise MesonException('Value %s is not boolean (true or false).' % value)
+        raise MesonException(f'Option "{self.name}" value {value} is not boolean (true or false).')
 
 class UserIntegerOption(UserOption[int]):
-    def __init__(self, description: str, value: T.Any, yielding: bool = DEFAULT_YIELDING,
+    def __init__(self, name: str, description: str, value: T.Any, yielding: bool = DEFAULT_YIELDING,
                  deprecated: T.Union[bool, str, T.Dict[str, str], T.List[str]] = False):
         min_value, max_value, default_value = value
         self.min_value = min_value
@@ -182,25 +183,25 @@
         if max_value is not None:
             c.append('<=' + str(max_value))
         choices = ', '.join(c)
-        super().__init__(description, choices, yielding, deprecated)
+        super().__init__(name, description, choices, yielding, deprecated)
         self.set_value(default_value)
 
     def validate_value(self, value: T.Any) -> int:
         if isinstance(value, str):
             value = self.toint(value)
         if not isinstance(value, int):
-            raise MesonException('New value for integer option is not an integer.')
+            raise MesonException(f'Value {value!r} for option "{self.name}" is not an integer.')
         if self.min_value is not None and value < self.min_value:
-            raise MesonException('New value %d is less than minimum value %d.' % (value, self.min_value))
+            raise MesonException(f'Value {value} for option "{self.name}" is less than minimum value {self.min_value}.')
         if self.max_value is not None and value > self.max_value:
-            raise MesonException('New value %d is more than maximum value %d.' % (value, self.max_value))
+            raise MesonException(f'Value {value} for option "{self.name}" is more than maximum value {self.max_value}.')
         return value
 
     def toint(self, valuestring: str) -> int:
         try:
             return int(valuestring)
         except ValueError:
-            raise MesonException('Value string "%s" is not convertible to an integer.' % valuestring)
+            raise MesonException(f'Value string "{valuestring}" for option "{self.name}" is not convertible to an integer.')
 
 class OctalInt(int):
     # NinjaBackend.get_user_option_args uses str() to converts it to a command line option
@@ -210,9 +211,9 @@
         return oct(int(self))
 
 class UserUmaskOption(UserIntegerOption, UserOption[T.Union[str, OctalInt]]):
-    def __init__(self, description: str, value: T.Any, yielding: bool = DEFAULT_YIELDING,
+    def __init__(self, name: str, description: str, value: T.Any, yielding: bool = DEFAULT_YIELDING,
                  deprecated: T.Union[bool, str, T.Dict[str, str], T.List[str]] = False):
-        super().__init__(description, (0, 0o777, value), yielding, deprecated)
+        super().__init__(name, description, (0, 0o777, value), yielding, deprecated)
         self.choices = ['preserve', '0000-0777']
 
     def printable_value(self) -> str:
@@ -229,18 +230,18 @@
         try:
             return int(valuestring, 8)
         except ValueError as e:
-            raise MesonException(f'Invalid mode: {e}')
+            raise MesonException(f'Invalid mode for option "{self.name}" {e}')
 
 class UserComboOption(UserOption[str]):
-    def __init__(self, description: str, choices: T.List[str], value: T.Any,
+    def __init__(self, name: str, description: str, choices: T.List[str], value: T.Any,
                  yielding: bool = DEFAULT_YIELDING,
                  deprecated: T.Union[bool, str, T.Dict[str, str], T.List[str]] = False):
-        super().__init__(description, choices, yielding, deprecated)
+        super().__init__(name, description, choices, yielding, deprecated)
         if not isinstance(self.choices, list):
-            raise MesonException('Combo choices must be an array.')
+            raise MesonException(f'Combo choices for option "{self.name}" must be an array.')
         for i in self.choices:
             if not isinstance(i, str):
-                raise MesonException('Combo choice elements must be strings.')
+                raise MesonException(f'Combo choice elements for option "{self.name}" must be strings.')
         self.set_value(value)
 
     def validate_value(self, value: T.Any) -> str:
@@ -252,24 +253,27 @@
             else:
                 _type = 'string'
             optionsstring = ', '.join([f'"{item}"' for item in self.choices])
-            raise MesonException('Value "{}" (of type "{}") for combo option "{}" is not one of the choices.'
+            raise MesonException('Value "{}" (of type "{}") for option "{}" is not one of the choices.'
                                  ' Possible choices are (as string): {}.'.format(
-                                     value, _type, self.description, optionsstring))
+                                     value, _type, self.name, optionsstring))
         return value
 
 class UserArrayOption(UserOption[T.List[str]]):
-    def __init__(self, description: str, value: T.Union[str, T.List[str]],
+    def __init__(self, name: str, description: str, value: T.Union[str, T.List[str]],
                  split_args: bool = False,
                  allow_dups: bool = False, yielding: bool = DEFAULT_YIELDING,
                  choices: T.Optional[T.List[str]] = None,
                  deprecated: T.Union[bool, str, T.Dict[str, str], T.List[str]] = False):
-        super().__init__(description, choices if choices is not None else [], yielding, deprecated)
+        super().__init__(name, description, choices if choices is not None else [], yielding, deprecated)
         self.split_args = split_args
         self.allow_dups = allow_dups
         self.set_value(value)
 
     def listify(self, value: T.Any) -> T.List[T.Any]:
-        return listify_array_value(value, self.split_args)
+        try:
+            return listify_array_value(value, self.split_args)
+        except MesonException as e:
+            raise MesonException(f'error in option "{self.name}": {e!s}')
 
     def validate_value(self, value: T.Union[str, T.List[str]]) -> T.List[str]:
         newvalue = self.listify(value)
@@ -280,12 +284,17 @@
             mlog.deprecation(msg)
         for i in newvalue:
             if not isinstance(i, str):
-                raise MesonException(f'String array element "{newvalue!s}" is not a string.')
+                raise MesonException(f'String array element "{newvalue!s}" for option "{self.name}" is not a string.')
         if self.choices:
             bad = [x for x in newvalue if x not in self.choices]
             if bad:
-                raise MesonException('Options "{}" are not in allowed choices: "{}"'.format(
-                    ', '.join(bad), ', '.join(self.choices)))
+                raise MesonException('Value{} "{}" for option "{}" {} not in allowed choices: "{}"'.format(
+                    '' if len(bad) == 1 else 's',
+                    ', '.join(bad),
+                    self.name,
+                    'is' if len(bad) == 1 else 'are',
+                    ', '.join(self.choices))
+                )
         return newvalue
 
     def extend_value(self, value: T.Union[str, T.List[str]]) -> None:
@@ -297,9 +306,9 @@
 class UserFeatureOption(UserComboOption):
     static_choices = ['enabled', 'disabled', 'auto']
 
-    def __init__(self, description: str, value: T.Any, yielding: bool = DEFAULT_YIELDING,
+    def __init__(self, name: str, description: str, value: T.Any, yielding: bool = DEFAULT_YIELDING,
                  deprecated: T.Union[bool, str, T.Dict[str, str], T.List[str]] = False):
-        super().__init__(description, self.static_choices, value, yielding, deprecated)
+        super().__init__(name, description, self.static_choices, value, yielding, deprecated)
         self.name: T.Optional[str] = None  # TODO: Refactor options to all store their name
 
     def is_enabled(self) -> bool:
@@ -330,7 +339,8 @@
         self.all_stds = ['none'] + all_stds
         # Map a deprecated std to its replacement. e.g. gnu11 -> c11.
         self.deprecated_stds: T.Dict[str, str] = {}
-        super().__init__(f'{lang} language standard to use', ['none'], 'none')
+        opt_name = 'cpp_std' if lang == 'c++' else f'{lang}_std'
+        super().__init__(opt_name, f'{lang} language standard to use', ['none'], 'none')
 
     def set_versions(self, versions: T.List[str], gnu: bool = False, gnu_deprecated: bool = False) -> None:
         assert all(std in self.all_stds for std in versions)
@@ -343,10 +353,13 @@
                 self.choices += gnu_stds_map.keys()
 
     def validate_value(self, value: T.Union[str, T.List[str]]) -> str:
-        candidates = listify_array_value(value)
-        unknown = [std for std in candidates if std not in self.all_stds]
+        try:
+            candidates = listify_array_value(value)
+        except MesonException as e:
+            raise MesonException(f'error in option "{self.name}": {e!s}')
+        unknown = ','.join(std for std in candidates if std not in self.all_stds)
         if unknown:
-            raise MesonException(f'Unknown {self.lang.upper()} std {unknown}. Possible values are {self.all_stds}.')
+            raise MesonException(f'Unknown option "{self.name}" value {unknown}. Possible values are {self.all_stds}.')
         # Check first if any of the candidates are not deprecated
         for std in candidates:
             if std in self.choices:
@@ -360,10 +373,10 @@
                     f'However, the deprecated {std} std currently falls back to {newstd}.\n' +
                     'This will be an error in the future.\n' +
                     'If the project supports both GNU and MSVC compilers, a value such as\n' +
-                    '"c_std=gnu11,c11" specifies that GNU is prefered but it can safely fallback to plain c11.')
+                    '"c_std=gnu11,c11" specifies that GNU is preferred but it can safely fallback to plain c11.')
                 return newstd
         raise MesonException(f'None of values {candidates} are supported by the {self.lang.upper()} compiler. ' +
-                             f'Possible values are {self.choices}')
+                             f'Possible values for option "{self.name}" are {self.choices}')
 
 @dataclass
 class OptionsView(abc.Mapping):
@@ -720,11 +733,13 @@
     def init_backend_options(self, backend_name: str) -> None:
         if backend_name == 'ninja':
             self.options[OptionKey('backend_max_links')] = UserIntegerOption(
+                'backend_max_links',
                 'Maximum number of linker processes to run or 0 for no '
                 'limit',
                 (0, None, 0))
         elif backend_name.startswith('vs'):
             self.options[OptionKey('backend_startup_project')] = UserStringOption(
+                'backend_startup_project',
                 'Default project to execute in Visual Studio',
                 '')
 
@@ -1294,7 +1309,7 @@
         keywords = {'yielding': self.yielding, 'value': value}
         if self.choices:
             keywords['choices'] = self.choices
-        o = self.opt_type(self.description, **keywords)
+        o = self.opt_type(name.name, self.description, **keywords)
         o.readonly = self.readonly
         return o
 
diff --git a/mesonbuild/optinterpreter.py b/mesonbuild/optinterpreter.py
index e9c201e..9da355a 100644
--- a/mesonbuild/optinterpreter.py
+++ b/mesonbuild/optinterpreter.py
@@ -196,7 +196,7 @@
         n_kwargs = {k: v for k, v in kwargs.items()
                     if k not in {'type', 'description', 'deprecated', 'yield'}}
 
-        opt = parser(description, (kwargs['yield'], kwargs['deprecated']), n_kwargs)
+        opt = parser(opt_name, description, (kwargs['yield'], kwargs['deprecated']), n_kwargs)
         if key in self.options:
             mlog.deprecation(f'Option {opt_name} already exists.')
         self.options[key] = opt
@@ -205,8 +205,8 @@
         'string option',
         KwargInfo('value', str, default=''),
     )
-    def string_parser(self, description: str, args: T.Tuple[bool, _DEPRECATED_ARGS], kwargs: StringArgs) -> coredata.UserOption:
-        return coredata.UserStringOption(description, kwargs['value'], *args)
+    def string_parser(self, name: str, description: str, args: T.Tuple[bool, _DEPRECATED_ARGS], kwargs: StringArgs) -> coredata.UserOption:
+        return coredata.UserStringOption(name, description, kwargs['value'], *args)
 
     @typed_kwargs(
         'boolean option',
@@ -218,20 +218,20 @@
             deprecated_values={str: ('1.1.0', 'use a boolean, not a string')},
         ),
     )
-    def boolean_parser(self, description: str, args: T.Tuple[bool, _DEPRECATED_ARGS], kwargs: BooleanArgs) -> coredata.UserOption:
-        return coredata.UserBooleanOption(description, kwargs['value'], *args)
+    def boolean_parser(self, name: str, description: str, args: T.Tuple[bool, _DEPRECATED_ARGS], kwargs: BooleanArgs) -> coredata.UserOption:
+        return coredata.UserBooleanOption(name, description, kwargs['value'], *args)
 
     @typed_kwargs(
         'combo option',
         KwargInfo('value', (str, NoneType)),
         KwargInfo('choices', ContainerTypeInfo(list, str, allow_empty=False), required=True),
     )
-    def combo_parser(self, description: str, args: T.Tuple[bool, _DEPRECATED_ARGS], kwargs: ComboArgs) -> coredata.UserOption:
+    def combo_parser(self, name: str, description: str, args: T.Tuple[bool, _DEPRECATED_ARGS], kwargs: ComboArgs) -> coredata.UserOption:
         choices = kwargs['choices']
         value = kwargs['value']
         if value is None:
             value = kwargs['choices'][0]
-        return coredata.UserComboOption(description, choices, value, *args)
+        return coredata.UserComboOption(name, description, choices, value, *args)
 
     @typed_kwargs(
         'integer option',
@@ -245,17 +245,17 @@
         KwargInfo('min', (int, NoneType)),
         KwargInfo('max', (int, NoneType)),
     )
-    def integer_parser(self, description: str, args: T.Tuple[bool, _DEPRECATED_ARGS], kwargs: IntegerArgs) -> coredata.UserOption:
+    def integer_parser(self, name: str, description: str, args: T.Tuple[bool, _DEPRECATED_ARGS], kwargs: IntegerArgs) -> coredata.UserOption:
         value = kwargs['value']
         inttuple = (kwargs['min'], kwargs['max'], value)
-        return coredata.UserIntegerOption(description, inttuple, *args)
+        return coredata.UserIntegerOption(name, description, inttuple, *args)
 
     @typed_kwargs(
         'string array option',
         KwargInfo('value', (ContainerTypeInfo(list, str), str, NoneType)),
         KwargInfo('choices', ContainerTypeInfo(list, str), default=[]),
     )
-    def string_array_parser(self, description: str, args: T.Tuple[bool, _DEPRECATED_ARGS], kwargs: StringArrayArgs) -> coredata.UserOption:
+    def string_array_parser(self, name: str, description: str, args: T.Tuple[bool, _DEPRECATED_ARGS], kwargs: StringArrayArgs) -> coredata.UserOption:
         choices = kwargs['choices']
         value = kwargs['value'] if kwargs['value'] is not None else choices
         if isinstance(value, str):
@@ -263,7 +263,7 @@
                 FeatureDeprecated('String value for array option', '1.3.0').use(self.subproject)
             else:
                 raise mesonlib.MesonException('Value does not define an array: ' + value)
-        return coredata.UserArrayOption(description, value,
+        return coredata.UserArrayOption(name, description, value,
                                         choices=choices,
                                         yielding=args[0],
                                         deprecated=args[1])
@@ -272,5 +272,5 @@
         'feature option',
         KwargInfo('value', str, default='auto', validator=in_set_validator({'auto', 'enabled', 'disabled'})),
     )
-    def feature_parser(self, description: str, args: T.Tuple[bool, _DEPRECATED_ARGS], kwargs: FeatureArgs) -> coredata.UserOption:
-        return coredata.UserFeatureOption(description, kwargs['value'], *args)
+    def feature_parser(self, name: str, description: str, args: T.Tuple[bool, _DEPRECATED_ARGS], kwargs: FeatureArgs) -> coredata.UserOption:
+        return coredata.UserFeatureOption(name, description, kwargs['value'], *args)
diff --git a/test cases/failing/98 number in combo/test.json b/test cases/failing/98 number in combo/test.json
index ecac667..c1c9484 100644
--- a/test cases/failing/98 number in combo/test.json
+++ b/test cases/failing/98 number in combo/test.json
@@ -1,5 +1,5 @@
 {
   "stdout": [
-    { "line": "test cases/failing/98 number in combo/meson.build:1:0: ERROR: Value \"1\" (of type \"number\") for combo option \"Optimization level\" is not one of the choices. Possible choices are (as string): \"plain\", \"0\", \"g\", \"1\", \"2\", \"3\", \"s\"." }
+    { "line": "test cases/failing/98 number in combo/meson.build:1:0: ERROR: Value \"1\" (of type \"number\") for option \"optimization\" is not one of the choices. Possible choices are (as string): \"plain\", \"0\", \"g\", \"1\", \"2\", \"3\", \"s\"." }
   ]
 }
diff --git a/test cases/failing/99 bool in combo/test.json b/test cases/failing/99 bool in combo/test.json
index 364dde8..b3effc7 100644
--- a/test cases/failing/99 bool in combo/test.json
+++ b/test cases/failing/99 bool in combo/test.json
@@ -1,5 +1,5 @@
 {
   "stdout": [
-    { "line": "test cases/failing/99 bool in combo/meson.build:1:0: ERROR: Value \"True\" (of type \"boolean\") for combo option \"opt\" is not one of the choices. Possible choices are (as string): \"true\", \"false\"." }
+    { "line": "test cases/failing/99 bool in combo/meson.build:1:0: ERROR: Value \"True\" (of type \"boolean\") for option \"opt\" is not one of the choices. Possible choices are (as string): \"true\", \"false\"." }
   ]
 }
diff --git a/unittests/platformagnostictests.py b/unittests/platformagnostictests.py
index b75a780..e3c25c7 100644
--- a/unittests/platformagnostictests.py
+++ b/unittests/platformagnostictests.py
@@ -13,7 +13,7 @@
 
 from .baseplatformtests import BasePlatformTests
 from .helpers import is_ci
-from mesonbuild.mesonlib import EnvironmentVariables, ExecutableSerialisation, is_linux, python_command
+from mesonbuild.mesonlib import EnvironmentVariables, ExecutableSerialisation, MesonException, is_linux, python_command
 from mesonbuild.optinterpreter import OptionInterpreter, OptionException
 from run_tests import Backend
 
@@ -63,6 +63,27 @@
         fname = write_file("option('platlib', type: 'string')")
         interp.process(fname)
 
+    def test_option_validation(self):
+        """Test cases that are not catch by the optinterpreter itself."""
+        interp = OptionInterpreter('')
+
+        def write_file(code: str):
+            with tempfile.NamedTemporaryFile('w', dir=self.builddir, encoding='utf-8', delete=False) as f:
+                f.write(code)
+                return f.name
+        
+        fname = write_file("option('intminmax', type: 'integer', value: 10, min: 0, max: 5)")
+        self.assertRaisesRegex(MesonException, 'Value 10 for option "intminmax" is more than maximum value 5.',
+                               interp.process, fname)
+
+        fname = write_file("option('array', type: 'array', choices : ['one', 'two', 'three'], value : ['one', 'four'])")
+        self.assertRaisesRegex(MesonException, 'Value "four" for option "array" is not in allowed choices: "one, two, three"',
+                               interp.process, fname)
+        
+        fname = write_file("option('array', type: 'array', choices : ['one', 'two', 'three'], value : ['four', 'five', 'six'])")
+        self.assertRaisesRegex(MesonException, 'Values "four, five, six" for option "array" are not in allowed choices: "one, two, three"',
+                               interp.process, fname)
+
     def test_python_dependency_without_pkgconfig(self):
         testdir = os.path.join(self.unit_test_dir, '103 python without pkgconfig')
         self.init(testdir, override_envvars={'PKG_CONFIG': 'notfound'})