diff --git a/src/Quasar/Wayland/Protocol/Core.hs b/src/Quasar/Wayland/Protocol/Core.hs
index 4d75ebe415a5c59b2351e794353f295a1684bcb9..5ac5f818045e3a4eaa572f452dc01919ab5ae00c 100644
--- a/src/Quasar/Wayland/Protocol/Core.hs
+++ b/src/Quasar/Wayland/Protocol/Core.hs
@@ -28,6 +28,7 @@ module Quasar.Wayland.Protocol.Core (
   setException,
 
   showObjectMessage,
+  isNewId,
 
   -- * Message decoder operations
   WireFormat(..),
@@ -94,6 +95,11 @@ data ArgumentType
   | FdArgument
   deriving stock (Eq, Show, Lift)
 
+isNewId :: ArgumentType -> Bool
+isNewId (NewIdArgument _) = True
+isNewId GenericNewIdArgument = True
+isNewId _ = False
+
 class (Eq (Argument a), Show (Argument a)) => WireFormat a where
   type Argument a
   putArgument :: Argument a -> PutM ()
diff --git a/src/Quasar/Wayland/Protocol/TH.hs b/src/Quasar/Wayland/Protocol/TH.hs
index 9dc6711f2040e1d97096872a2318a66495555432..08d96465c9224b90e8dfe3330121df37a5b3c57e 100644
--- a/src/Quasar/Wayland/Protocol/TH.hs
+++ b/src/Quasar/Wayland/Protocol/TH.hs
@@ -37,14 +37,16 @@ data MessageSpec = MessageSpec {
   name :: String,
   since :: Maybe Integer,
   opcode :: Opcode,
-  arguments :: [ArgumentSpec]
+  arguments :: [ArgumentSpec],
+  isDestructor :: Bool
 }
   deriving stock Show
 
 data ArgumentSpec = ArgumentSpec {
   name :: String,
   index :: Integer,
-  argType :: ArgumentType
+  argType :: ArgumentType,
+  nullable :: Bool
 }
   deriving stock Show
 
@@ -333,41 +335,73 @@ parseInterface element = do
   }
 
 parseRequest :: MonadFail m => String -> (Opcode, Element) -> m RequestSpec
-parseRequest x y = RequestSpec <$> parseMessage x y
+parseRequest x y = RequestSpec <$> parseMessage True x y
 
 parseEvent :: MonadFail m => String -> (Opcode, Element) -> m EventSpec
-parseEvent x y = EventSpec <$> parseMessage x y
+parseEvent x y = EventSpec <$> parseMessage False x y
+
+parseMessage :: MonadFail m => Bool -> String -> (Opcode, Element) -> m MessageSpec
+parseMessage isRequest interfaceName (opcode, element) = do
+  let isEvent = not isRequest
 
-parseMessage :: MonadFail m => String -> (Opcode, Element) -> m MessageSpec
-parseMessage interfaceName (opcode, element) = do
   name <- getAttr "name" element
+
+  let description = interfaceName <> "." <> name
+
+  mtype <- peekAttr "type" element
   since <- read <<$>> peekAttr "since" element
-  arguments <- mapM parseArgument $ zip [0..] $ findChildren (qname "arg") element
+  arguments <- mapM (parseArgument description) $ zip [0..] $ findChildren (qname "arg") element
+
+  isDestructor <-
+    case mtype of
+      Nothing -> pure False
+      Just "destructor" -> pure True
+      Just messageType -> fail $ "Unknown message type: " <> messageType
+
+  when
+    do isEvent && isDestructor
+    do fail $ "Event cannot be a destructor: " <> description
+
+  when
+    do (foldr (\arg -> if isNewId arg.argType then (+ 1) else id) 0 arguments) > 1
+    do fail $ "Message creates multiple objects: " <> description
+
   forM_ arguments \arg -> do
     when
       do arg.argType == GenericNewIdArgument && (interfaceName /= "wl_registry" || name /= "bind")
-      do fail $ "Invalid 'new_id' argument without 'interface' attribute encountered on " <> interfaceName <> "." <> name <> " (only valid on wl_registry.bind)"
+      do fail $ "Invalid \"new_id\" argument without \"interface\" attribute encountered on " <> description <> " (only valid on wl_registry.bind)"
     when
       do arg.argType == GenericObjectArgument && (interfaceName /= "wl_display" || name /= "error")
-      do fail $ "Invalid 'object' argument without 'interface' attribute encountered on " <> interfaceName <> "." <> name <> " (only valid on wl_display.error)"
+      do fail $ "Invalid \"object\" argument without \"interface\" attribute encountered on " <> description <> " (only valid on wl_display.error)"
+
   pure MessageSpec  {
     name,
     since,
     opcode,
-    arguments
+    arguments,
+    isDestructor
   }
 
 
-parseArgument :: forall m. MonadFail m => (Integer, Element) -> m ArgumentSpec
-parseArgument (index, element) = do
+parseArgument :: forall m. MonadFail m => String -> (Integer, Element) -> m ArgumentSpec
+parseArgument messageDescription (index, element) = do
   name <- getAttr "name" element
   argTypeStr <- getAttr "type" element
   interface <- peekAttr "interface" element
   argType <- parseArgumentType argTypeStr interface
+
+  let description = messageDescription <> "." <> name
+
+  nullable <- peekAttr "allow-null" element >>= \case
+    Just "true" -> pure True
+    Just "false" -> pure False
+    Just x -> fail $ "Invalid value for attribute \"allow-null\" on " <> description <> ": " <> x
+    Nothing -> pure False
   pure ArgumentSpec {
     name,
     index,
-    argType
+    argType,
+    nullable
   }
   where
     parseArgumentType :: String -> Maybe String -> m ArgumentType