Move project option detection into OptionStore.
diff --git a/mesonbuild/ast/introspection.py b/mesonbuild/ast/introspection.py
index 1197510..07f82df 100644
--- a/mesonbuild/ast/introspection.py
+++ b/mesonbuild/ast/introspection.py
@@ -105,7 +105,7 @@
         if not os.path.exists(optfile):
             optfile = os.path.join(self.source_root, self.subdir, 'meson_options.txt')
         if os.path.exists(optfile):
-            oi = optinterpreter.OptionInterpreter(self.subproject)
+            oi = optinterpreter.OptionInterpreter(self.coredata.optstore, self.subproject)
             oi.process(optfile)
             assert isinstance(proj_name, str), 'for mypy'
             self.coredata.update_project_options(oi.options, T.cast('SubProject', proj_name))
diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py
index eabe758..456615e 100644
--- a/mesonbuild/backend/ninjabackend.py
+++ b/mesonbuild/backend/ninjabackend.py
@@ -3603,7 +3603,7 @@
     def get_user_option_args(self):
         cmds = []
         for k, v in self.environment.coredata.optstore.items():
-            if k.is_project():
+            if self.environment.coredata.optstore.is_project_option(k):
                 cmds.append('-D' + str(k) + '=' + (v.value if isinstance(v.value, str) else str(v.value).lower()))
         # The order of these arguments must be the same between runs of Meson
         # to ensure reproducible output. The order we pass them shouldn't
diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py
index 7fb3bca..1bd2269 100644
--- a/mesonbuild/coredata.py
+++ b/mesonbuild/coredata.py
@@ -584,7 +584,7 @@
 
     def update_project_options(self, project_options: 'MutableKeyedOptionDictType', subproject: SubProject) -> None:
         for key, value in project_options.items():
-            if not key.is_project():
+            if not self.optstore.is_project_option(key):
                 continue
             if key not in self.optstore:
                 self.optstore.add_project_option(key, value)
@@ -608,7 +608,7 @@
 
         # Find any extranious keys for this project and remove them
         for key in self.optstore.keys() - project_options.keys():
-            if key.is_project() and key.subproject == subproject:
+            if self.optstore.is_project_option(key) and key.subproject == subproject:
                 self.optstore.remove(key)
 
     def is_cross_build(self, when_building_for: MachineChoice = MachineChoice.HOST) -> bool:
@@ -906,7 +906,7 @@
         # FIXME: This is fundamentally the same algorithm than interpreter.get_option_internal().
         # We should try to share the code somehow.
         key = key.evolve(subproject=self.subproject)
-        if not key.is_project():
+        if not key.is_project_hack_for_optionsview():
             opt = self.original_options.get(key)
             if opt is None or opt.yielding:
                 key2 = key.as_root()
diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py
index 13f7f22..742d24c 100644
--- a/mesonbuild/interpreter/interpreter.py
+++ b/mesonbuild/interpreter/interpreter.py
@@ -1051,7 +1051,7 @@
     def get_option_internal(self, optname: str) -> options.UserOption:
         key = OptionKey.from_string(optname).evolve(subproject=self.subproject)
 
-        if not key.is_project():
+        if not self.environment.coredata.optstore.is_project_option(key):
             for opts in [self.coredata.optstore, compilers.base_options]:
                 v = opts.get(key)
                 if v is None or v.yielding:
@@ -1198,7 +1198,7 @@
                 # We want fast  not cryptographically secure, this is just to
                 # see if the option file has changed
                 self.coredata.options_files[self.subproject] = (option_file, hashlib.sha1(f.read()).hexdigest())
-            oi = optinterpreter.OptionInterpreter(self.subproject)
+            oi = optinterpreter.OptionInterpreter(self.environment.coredata.optstore, self.subproject)
             oi.process(option_file)
             self.coredata.update_project_options(oi.options, self.subproject)
             self.add_build_def_file(option_file)
diff --git a/mesonbuild/mconf.py b/mesonbuild/mconf.py
index da96ac4..1294479 100644
--- a/mesonbuild/mconf.py
+++ b/mesonbuild/mconf.py
@@ -92,7 +92,7 @@
                     with open(opfile, 'rb') as f:
                         ophash = hashlib.sha1(f.read()).hexdigest()
                         if ophash != conf_options[1]:
-                            oi = OptionInterpreter(sub)
+                            oi = OptionInterpreter(self.coredata.optstore, sub)
                             oi.process(opfile)
                             self.coredata.update_project_options(oi.options, sub)
                             self.coredata.options_files[sub] = (opfile, ophash)
@@ -101,7 +101,7 @@
                     if not os.path.exists(opfile):
                         opfile = os.path.join(self.source_dir, 'meson_options.txt')
                     if os.path.exists(opfile):
-                        oi = OptionInterpreter(sub)
+                        oi = OptionInterpreter(self.coredata.optstore, sub)
                         oi.process(opfile)
                         self.coredata.update_project_options(oi.options, sub)
                         with open(opfile, 'rb') as f:
@@ -284,7 +284,7 @@
         build_core_options = self.split_options_per_subproject({k: v for k, v in core_options.items() if k.machine is MachineChoice.BUILD})
         host_compiler_options = self.split_options_per_subproject({k: v for k, v in self.coredata.optstore.items() if k.is_compiler() and k.machine is MachineChoice.HOST})
         build_compiler_options = self.split_options_per_subproject({k: v for k, v in self.coredata.optstore.items() if k.is_compiler() and k.machine is MachineChoice.BUILD})
-        project_options = self.split_options_per_subproject({k: v for k, v in self.coredata.optstore.items() if k.is_project()})
+        project_options = self.split_options_per_subproject({k: v for k, v in self.coredata.optstore.items() if self.coredata.optstore.is_project_option(k)})
         show_build_options = self.default_values_only or self.build.environment.is_cross_build()
 
         self.add_section('Main project options')
diff --git a/mesonbuild/mintro.py b/mesonbuild/mintro.py
index dea67d8..17d1b08 100644
--- a/mesonbuild/mintro.py
+++ b/mesonbuild/mintro.py
@@ -335,7 +335,7 @@
         'compiler',
     )
     add_keys(dir_options, 'directory')
-    add_keys({k: v for k, v in coredata.optstore.items() if k.is_project()}, 'user')
+    add_keys({k: v for k, v in coredata.optstore.items() if coredata.optstore.is_project_option(k)}, 'user')
     add_keys(test_options, 'test')
     return optlist
 
diff --git a/mesonbuild/optinterpreter.py b/mesonbuild/optinterpreter.py
index ffa46cd..7c94c15 100644
--- a/mesonbuild/optinterpreter.py
+++ b/mesonbuild/optinterpreter.py
@@ -64,7 +64,7 @@
 
 
 class OptionInterpreter:
-    def __init__(self, subproject: 'SubProject') -> None:
+    def __init__(self, optionstore, subproject: 'SubProject') -> None:
         self.options: 'coredata.MutableKeyedOptionDictType' = {}
         self.subproject = subproject
         self.option_types: T.Dict[str, T.Callable[..., options.UserOption]] = {
@@ -75,6 +75,7 @@
             'array': self.string_array_parser,
             'feature': self.feature_parser,
         }
+        self.optionstore = optionstore
 
     def process(self, option_file: str) -> None:
         try:
@@ -189,7 +190,7 @@
         if optname_regex.search(opt_name) is not None:
             raise OptionException('Option names can only contain letters, numbers or dashes.')
         key = mesonlib.OptionKey.from_string(opt_name).evolve(subproject=self.subproject)
-        if not key.is_project():
+        if self.optionstore.is_reserved_name(key):
             raise OptionException('Option name %s is reserved.' % opt_name)
 
         opt_type = kwargs['type']
diff --git a/mesonbuild/options.py b/mesonbuild/options.py
index d83a312..e88710d 100644
--- a/mesonbuild/options.py
+++ b/mesonbuild/options.py
@@ -8,6 +8,7 @@
 from .mesonlib import (
     HoldableObject,
     OptionKey,
+    OptionType,
     default_prefix,
     default_datadir,
     default_includedir,
@@ -536,3 +537,10 @@
 
     def get(self, *args, **kwargs) -> UserOption:
         return self.d.get(*args, **kwargs)
+
+    def is_project_option(self, key: OptionKey) -> bool:
+        """Convenience method to check if this is a project option."""
+        return key.type is OptionType.PROJECT
+
+    def is_reserved_name(self, key: OptionKey) -> bool:
+        return not self.is_project_option(key)
diff --git a/mesonbuild/utils/universal.py b/mesonbuild/utils/universal.py
index 6aee268..4582336 100644
--- a/mesonbuild/utils/universal.py
+++ b/mesonbuild/utils/universal.py
@@ -2406,8 +2406,8 @@
         """Convenience method to check if this is a builtin option."""
         return self.type is OptionType.COMPILER
 
-    def is_project(self) -> bool:
-        """Convenience method to check if this is a project option."""
+    def is_project_hack_for_optionsview(self) -> bool:
+        """This method will be removed once we can delete OptionsView."""
         return self.type is OptionType.PROJECT
 
     def is_base(self) -> bool:
diff --git a/unittests/platformagnostictests.py b/unittests/platformagnostictests.py
index fe598a7..4ac4b7a 100644
--- a/unittests/platformagnostictests.py
+++ b/unittests/platformagnostictests.py
@@ -18,6 +18,7 @@
 from mesonbuild.mesonlib import EnvironmentVariables, ExecutableSerialisation, MesonException, is_linux, python_command
 from mesonbuild.mformat import match_path
 from mesonbuild.optinterpreter import OptionInterpreter, OptionException
+from mesonbuild.options import OptionStore
 from run_tests import Backend
 
 @skipIf(is_ci() and not is_linux(), "Run only on fast platforms")
@@ -35,7 +36,8 @@
         self.init(testdir, workdir=testdir)
 
     def test_invalid_option_names(self):
-        interp = OptionInterpreter('')
+        store = OptionStore()
+        interp = OptionInterpreter(store, '')
 
         def write_file(code: str):
             with tempfile.NamedTemporaryFile('w', dir=self.builddir, encoding='utf-8', delete=False) as f:
@@ -68,7 +70,8 @@
 
     def test_option_validation(self):
         """Test cases that are not catch by the optinterpreter itself."""
-        interp = OptionInterpreter('')
+        store = OptionStore()
+        interp = OptionInterpreter(store, '')
 
         def write_file(code: str):
             with tempfile.NamedTemporaryFile('w', dir=self.builddir, encoding='utf-8', delete=False) as f: