coredata: remove extraneous keys when updating project_options

This can happen when a project's meson.options file is updated, and an
old option is removed.
diff --git a/mesonbuild/ast/introspection.py b/mesonbuild/ast/introspection.py
index 046c0a2..17eb1d5 100644
--- a/mesonbuild/ast/introspection.py
+++ b/mesonbuild/ast/introspection.py
@@ -1,5 +1,6 @@
 # SPDX-License-Identifier: Apache-2.0
 # Copyright 2018 The Meson development team
+# Copyright © 2024 Intel Corporation
 
 # This class contains the basic functionality needed to run any interpreter
 # or an interpreter-based tool
@@ -106,7 +107,8 @@
         if os.path.exists(optfile):
             oi = optinterpreter.OptionInterpreter(self.subproject)
             oi.process(optfile)
-            self.coredata.update_project_options(oi.options)
+            assert isinstance(proj_name, str), 'for mypy'
+            self.coredata.update_project_options(oi.options, T.cast('SubProject', proj_name))
 
         def_opts = self.flatten_args(kwargs.get('default_options', []))
         _project_default_options = mesonlib.stringlistify(def_opts)
diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py
index df57598..0c2f501 100644
--- a/mesonbuild/coredata.py
+++ b/mesonbuild/coredata.py
@@ -1,6 +1,6 @@
 # SPDX-License-Identifier: Apache-2.0
 # Copyright 2013-2024 The Meson development team
-# Copyright © 2023 Intel Corporation
+# Copyright © 2023-2024 Intel Corporation
 
 from __future__ import annotations
 
@@ -15,7 +15,7 @@
 from dataclasses import dataclass
 
 from .mesonlib import (
-    HoldableObject,
+    HoldableObject, MesonBugException,
     MesonException, EnvironmentException, MachineChoice, PerMachine,
     PerMachineDefaultable, default_libdir, default_libexecdir,
     default_prefix, default_datadir, default_includedir, default_infodir,
@@ -40,6 +40,7 @@
     from .environment import Environment
     from .mesonlib import FileOrString
     from .cmake.traceparser import CMakeCacheEntry
+    from .interpreterbase import SubProject
 
     class SharedCMDOptions(Protocol):
 
@@ -892,13 +893,15 @@
         # mypy cannot analyze type of OptionKey
         return T.cast('T.List[str]', self.options[OptionKey('link_args', machine=for_machine, lang=lang)].value)
 
-    def update_project_options(self, options: 'MutableKeyedOptionDictType') -> None:
+    def update_project_options(self, options: 'MutableKeyedOptionDictType', subproject: SubProject) -> None:
         for key, value in options.items():
             if not key.is_project():
                 continue
             if key not in self.options:
                 self.options[key] = value
                 continue
+            if key.subproject != subproject:
+                raise MesonBugException(f'Tried to set an option for subproject {key.subproject} from {subproject}!')
 
             oldval = self.options[key]
             if type(oldval) is not type(value):
@@ -914,6 +917,11 @@
                     mlog.warning(f'Old value(s) of {key} are no longer valid, resetting to default ({value.value}).',
                                  fatal=False)
 
+        # Find any extranious keys for this project and remove them
+        for key in list(self.options.keys() - options.keys()):
+            if key.is_project() and key.subproject == subproject:
+                del self.options[key]
+
     def is_cross_build(self, when_building_for: MachineChoice = MachineChoice.HOST) -> bool:
         if when_building_for == MachineChoice.BUILD:
             return False
diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py
index 47d0d2d..d870de1 100644
--- a/mesonbuild/interpreter/interpreter.py
+++ b/mesonbuild/interpreter/interpreter.py
@@ -1035,7 +1035,7 @@
         FeatureNew.single_use('Cargo subproject', '1.3.0', self.subproject, location=self.current_node)
         with mlog.nested(subp_name):
             ast, options = cargo.interpret(subp_name, subdir, self.environment)
-            self.coredata.update_project_options(options)
+            self.coredata.update_project_options(options, subp_name)
             return self._do_subproject_meson(
                 subp_name, subdir, default_options, kwargs, ast,
                 # FIXME: Are there other files used by cargo interpreter?
@@ -1189,7 +1189,7 @@
         if os.path.exists(option_file):
             oi = optinterpreter.OptionInterpreter(self.subproject)
             oi.process(option_file)
-            self.coredata.update_project_options(oi.options)
+            self.coredata.update_project_options(oi.options, self.subproject)
             self.add_build_def_file(option_file)
 
         if self.subproject: