• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/python3 -i
2#
3# Copyright (c) 2013-2020 The Khronos Group Inc.
4#
5# SPDX-License-Identifier: Apache-2.0
6
7import re
8from collections import OrderedDict, namedtuple
9from functools import reduce
10from pathlib import Path
11
12from conventions import ProseListFormats as plf
13from generator import OutputGenerator, write
14from spec_tools.attributes import ExternSyncEntry, LengthEntry
15from spec_tools.util import (findNamedElem, findNamedObject, findTypedElem,
16                             getElemName, getElemType)
17from spec_tools.validity import ValidityCollection, ValidityEntry
18
19
20# For parsing/splitting queue bit names - Vulkan only
21QUEUE_BITS_RE = re.compile(r'([^,]+)')
22
23
24class UnhandledCaseError(RuntimeError):
25    def __init__(self, msg=None):
26        if msg:
27            super().__init__('Got a case in the validity generator that we have not explicitly handled: ' + msg)
28        else:
29            super().__init__('Got a case in the validity generator that we have not explicitly handled.')
30
31
32def _genericIterateIntersection(a, b):
33    """Iterate through all elements in a that are also in b.
34
35    Somewhat like a set's intersection(),
36    but not type-specific so it can work with OrderedDicts, etc.
37    It also returns a generator instead of a set,
38    so you can pick what container type you'd like,
39    if any.
40    """
41    return (x for x in a if x in b)
42
43
44def _make_ordered_dict(gen):
45    """Make an ordered dict (with None as the values) from a generator."""
46    return OrderedDict(((x, None) for x in gen))
47
48
49def _orderedDictIntersection(a, b):
50    return _make_ordered_dict(_genericIterateIntersection(a, b))
51
52
53def _genericIsDisjoint(a, b):
54    """Return true if nothing in a is also in b.
55
56    Like a set's is_disjoint(),
57    but not type-specific so it can work with OrderedDicts, etc.
58    """
59    for _ in _genericIterateIntersection(a, b):
60        return False
61    # if we never enter the loop...
62    return True
63
64
65def _parse_queue_bits(cmd):
66    """Return a generator of queue bits, with underscores turned to spaces.
67
68    Vulkan-only.
69
70    Return None if the queues attribute is not specified."""
71    queuetypes = cmd.get('queues')
72    if not queuetypes:
73        return None
74    return (qt.replace('_', ' ')
75            for qt in QUEUE_BITS_RE.findall(queuetypes))
76
77
78class ValidityOutputGenerator(OutputGenerator):
79    """ValidityOutputGenerator - subclass of OutputGenerator.
80
81    Generates AsciiDoc includes of valid usage information, for reference
82    pages and the specification. Similar to DocOutputGenerator.
83
84    ---- methods ----
85    ValidityOutputGenerator(errFile, warnFile, diagFile) - args as for
86    OutputGenerator. Defines additional internal state.
87    ---- methods overriding base class ----
88    beginFile(genOpts)
89    endFile()
90    beginFeature(interface, emit)
91    endFeature()
92    genCmd(cmdinfo)
93    """
94
95    def __init__(self, *args, **kwargs):
96        super().__init__(*args, **kwargs)
97
98        self.currentExtension = ''
99
100    @property
101    def null(self):
102        """Preferred spelling of NULL.
103
104        Delegates to the object implementing ConventionsBase.
105        """
106        return self.conventions.null
107
108    @property
109    def structtype_member_name(self):
110        """Return name of the structure type member.
111
112        Delegates to the object implementing ConventionsBase.
113        """
114        return self.conventions.structtype_member_name
115
116    @property
117    def nextpointer_member_name(self):
118        """Return name of the structure pointer chain member.
119
120        Delegates to the object implementing ConventionsBase.
121        """
122        return self.conventions.nextpointer_member_name
123
124    def makeProseList(self, elements, fmt=plf.AND,
125                      comma_for_two_elts=False, *args, **kwargs):
126        """Make a (comma-separated) list for use in prose.
127
128        Adds a connective (by default, 'and')
129        before the last element if there are more than 1.
130
131        Optionally adds a quantifier (like 'any') before a list of 2 or more,
132        if specified by fmt.
133
134        Delegates to the object implementing ConventionsBase.
135        """
136        if not elements:
137            raise ValueError(
138                'Cannot pass an empty list if you are trying to make a prose list.')
139        return self.conventions.makeProseList(elements,
140                                              fmt,
141                                              with_verb=False,
142                                              comma_for_two_elts=comma_for_two_elts,
143                                              *args, **kwargs)
144
145    def makeProseListIs(self, elements, fmt=plf.AND,
146                        comma_for_two_elts=False, *args, **kwargs):
147        """Make a (comma-separated) list for use in prose, followed by either 'is' or 'are' as appropriate.
148
149        Adds a connective (by default, 'and')
150        before the last element if there are more than 1.
151
152        Optionally adds a quantifier (like 'any') before a list of 2 or more,
153        if specified by fmt.
154
155        Delegates to the object implementing ConventionsBase.
156        """
157        if not elements:
158            raise ValueError(
159                'Cannot pass an empty list if you are trying to make a prose list.')
160        return self.conventions.makeProseList(elements,
161                                              fmt,
162                                              with_verb=True,
163                                              comma_for_two_elts=comma_for_two_elts,
164                                              *args, **kwargs)
165
166    def makeValidityCollection(self, entity_name):
167        """Create a ValidityCollection object, passing along our Conventions."""
168        return ValidityCollection(entity_name, self.conventions)
169
170    def beginFile(self, genOpts):
171        if not genOpts.conventions:
172            raise RuntimeError(
173                'Must specify conventions object to generator options')
174        self.conventions = genOpts.conventions
175        # Vulkan says 'must: be a valid pointer' a lot, OpenXR just says
176        # 'must: be a pointer'.
177        self.valid_pointer_text = ' '.join(
178            (x for x in (self.conventions.valid_pointer_prefix, 'pointer') if x))
179        OutputGenerator.beginFile(self, genOpts)
180
181    def endFile(self):
182        OutputGenerator.endFile(self)
183
184    def beginFeature(self, interface, emit):
185        # Start processing in superclass
186        OutputGenerator.beginFeature(self, interface, emit)
187        self.currentExtension = interface.get('name')
188
189    def endFeature(self):
190        # Finish processing in superclass
191        OutputGenerator.endFeature(self)
192
193    @property
194    def struct_macro(self):
195        """Get the appropriate format macro for a structure."""
196        # delegate to conventions
197        return self.conventions.struct_macro
198
199    def makeStructName(self, name):
200        """Prepend the appropriate format macro for a structure to a structure type name."""
201        # delegate to conventions
202        return self.conventions.makeStructName(name)
203
204    def makeParameterName(self, name):
205        """Prepend the appropriate format macro for a parameter/member to a parameter name."""
206        return 'pname:' + name
207
208    def makeBaseTypeName(self, name):
209        """Prepend the appropriate format macro for a 'base type' to a type name."""
210        return 'basetype:' + name
211
212    def makeEnumerationName(self, name):
213        """Prepend the appropriate format macro for an enumeration type to a enum type name."""
214        return 'elink:' + name
215
216    def makeFuncPointerName(self, name):
217        """Prepend the appropriate format macro for a function pointer type to a type name."""
218        return 'tlink:' + name
219
220    def makeExternalTypeName(self, name):
221        """Prepend the appropriate format macro for an external type like uint32_t to a type name."""
222        # delegate to conventions
223        return self.conventions.makeExternalTypeName(name)
224
225    def makeEnumerantName(self, name):
226        """Prepend the appropriate format macro for an enumerate (value) to a enum value name."""
227        return 'ename:' + name
228
229    def writeInclude(self, directory, basename, validity: ValidityCollection,
230                     threadsafety, commandpropertiesentry=None,
231                     successcodes=None, errorcodes=None):
232        """Generate an include file.
233
234        directory - subdirectory to put file in (absolute or relative pathname)
235        basename - base name of the file
236        validity - ValidityCollection to write.
237        threadsafety - List (may be empty) of thread safety statements to write.
238        successcodes - Optional success codes to document.
239        errorcodes - Optional error codes to document.
240        """
241        # Create subdirectory, if needed
242        directory = Path(directory)
243        if not directory.is_absolute():
244            directory = Path(self.genOpts.directory) / directory
245        self.makeDir(str(directory))
246
247        # Create validity file
248        filename = str(directory / (basename + '.txt'))
249        self.logMsg('diag', '# Generating include file:', filename)
250
251        with open(filename, 'w', encoding='utf-8') as fp:
252            write(self.conventions.warning_comment, file=fp)
253
254            # Valid Usage
255            if validity:
256                write('.Valid Usage (Implicit)', file=fp)
257                write('****', file=fp)
258                write(validity, file=fp, end='')
259                write('****', file=fp)
260                write('', file=fp)
261
262            # Host Synchronization
263            if threadsafety:
264                # The heading of this block differs between projects, so an Asciidoc attribute is used.
265                write('.{externsynctitle}', file=fp)
266                write('****', file=fp)
267                write(threadsafety, file=fp, end='')
268                write('****', file=fp)
269                write('', file=fp)
270
271            # Command Properties - contained within a block, to avoid table numbering
272            if commandpropertiesentry:
273                write('.Command Properties', file=fp)
274                write('****', file=fp)
275                write('[options="header", width="100%"]', file=fp)
276                write('|====', file=fp)
277                write('|<<VkCommandBufferLevel,Command Buffer Levels>>|<<vkCmdBeginRenderPass,Render Pass Scope>>|<<VkQueueFlagBits,Supported Queue Types>>|<<synchronization-pipeline-stages-types,Pipeline Type>>', file=fp)
278                write(commandpropertiesentry, file=fp)
279                write('|====', file=fp)
280                write('****', file=fp)
281                write('', file=fp)
282
283            # Success Codes - contained within a block, to avoid table numbering
284            if successcodes or errorcodes:
285                write('.Return Codes', file=fp)
286                write('****', file=fp)
287                if successcodes:
288                    write('ifndef::doctype-manpage[]', file=fp)
289                    write('<<fundamentals-successcodes,Success>>::', file=fp)
290                    write('endif::doctype-manpage[]', file=fp)
291                    write('ifdef::doctype-manpage[]', file=fp)
292                    write('On success, this command returns::', file=fp)
293                    write('endif::doctype-manpage[]', file=fp)
294                    write(successcodes, file=fp)
295                if errorcodes:
296                    write('ifndef::doctype-manpage[]', file=fp)
297                    write('<<fundamentals-errorcodes,Failure>>::', file=fp)
298                    write('endif::doctype-manpage[]', file=fp)
299                    write('ifdef::doctype-manpage[]', file=fp)
300                    write('On failure, this command returns::', file=fp)
301                    write('endif::doctype-manpage[]', file=fp)
302                    write(errorcodes, file=fp)
303                write('****', file=fp)
304                write('', file=fp)
305
306    def paramIsPointer(self, param):
307        """Check if the parameter passed in is a pointer."""
308        tail = param.find('type').tail
309        return tail is not None and '*' in tail
310
311    def paramIsStaticArray(self, param):
312        """Check if the parameter passed in is a static array."""
313        tail = param.find('name').tail
314        return tail and tail[0] == '['
315
316    def paramIsConst(self, param):
317        """Check if the parameter passed in has a type that mentions const."""
318        return param.text is not None and 'const' in param.text
319
320    def staticArrayLength(self, param):
321        """Get the length of a parameter that's been identified as a static array."""
322        paramenumsize = param.find('enum')
323        if paramenumsize is not None:
324            return paramenumsize.text
325            # TODO switch to below when cosmetic changes OK
326            # return self.makeEnumerantName(paramenumsize.text)
327
328        return param.find('name').tail[1:-1]
329
330    def paramIsArray(self, param):
331        """Check if the parameter passed in is a pointer to an array."""
332        return param.get('len') is not None
333
334    def getHandleDispatchableAncestors(self, typename):
335        """Get the ancestors of a handle object."""
336        ancestors = []
337        current = typename
338        while True:
339            current = self.getHandleParent(current)
340            if current is None:
341                return ancestors
342            if self.isHandleTypeDispatchable(current):
343                ancestors.append(current)
344
345    def isHandleTypeDispatchable(self, handlename):
346        """Check if a parent object is dispatchable or not."""
347        handle = self.registry.tree.find(
348            "types/type/[name='" + handlename + "'][@category='handle']")
349        if handle is not None and getElemType(handle) == 'VK_DEFINE_HANDLE':
350            return True
351        else:
352            return False
353
354    def isHandleOptional(self, param, params):
355        # Simple, if it's optional, return true
356        if param.get('optional') is not None:
357            return True
358
359        # If no validity is being generated, it usually means that validity is complex and not absolute, so let's say yes.
360        if param.get('noautovalidity') is not None:
361            return True
362
363        # If the parameter is an array and we haven't already returned, find out if any of the len parameters are optional
364        if self.paramIsArray(param):
365            for length in LengthEntry.parse_len_from_param(param):
366                if not length.other_param_name:
367                    # don't care about constants or "null-terminated"
368                    continue
369
370                other_param = findNamedElem(params, length.other_param_name)
371                if other_param is None:
372                    self.logMsg('warn', length.other_param_name,
373                                'is listed as a length for parameter', param, 'but no such parameter exists')
374                if other_param and other_param.get('optional'):
375                    return True
376
377        return False
378
379    def makeParamValidityPre(self, param, params, selector):
380        """Make the start of an entry for a parameter's validity, including a chunk of text if it is an array."""
381        param_name = getElemName(param)
382        paramtype = getElemType(param)
383
384        # General pre-amble. Check optionality and add stuff.
385        entry = ValidityEntry(anchor=(param_name, 'parameter'))
386
387        # This is for a union member, and the valid member is chosen by an enum selection
388        if selector:
389            selection = param.get('selection')
390
391            entry += 'If {} is {}, '.format(
392                self.makeParameterName(selector),
393                self.makeEnumerantName(selection))
394
395            return entry
396
397        if self.paramIsStaticArray(param):
398            if paramtype != 'char':
399                entry += 'Any given element of '
400            return entry
401
402        is_optional = param.get('optional') is not None and param.get('optional').split(',')[0] == 'true'
403
404        if self.paramIsArray(param) and param.get('len') != LengthEntry.NULL_TERMINATED_STRING:
405            # Find all the parameters that are called out as optional,
406            # so we can document that they might be zero, and the array may be ignored
407            optionallengths = []
408            for length in LengthEntry.parse_len_from_param(param):
409                if not length.other_param_name:
410                    # Only care about length entries that are parameter names
411                    continue
412
413                other_param = findNamedElem(params, length.other_param_name)
414                other_param_optional = (other_param is not None) and (
415                    other_param.get('optional') is not None)
416
417                if other_param is None or not other_param_optional:
418                    # Don't care about not-found params or non-optional params
419                    continue
420
421                if self.paramIsPointer(other_param):
422                    optionallengths.append(
423                        'the value referenced by ' + self.makeParameterName(length.other_param_name))
424                else:
425                    optionallengths.append(
426                        self.makeParameterName(length.other_param_name))
427
428            # Document that these arrays may be ignored if any of the length values are 0
429            if optionallengths or is_optional:
430                entry += 'If '
431            if optionallengths:
432                entry += self.makeProseListIs(optionallengths, fmt=plf.OR)
433                entry += ' not %s, ' % self.conventions.zero
434            # TODO enabling this in OpenXR, as used in Vulkan, causes nonsensical things like
435            # "If pname:propertyCapacityInput is not `0`, and pname:properties is not `NULL`, pname:properties must: be a pointer to an array of pname:propertyCapacityInput slink:XrApiLayerProperties structures"
436            if optionallengths and is_optional:
437                entry += 'and '
438            if is_optional:
439                entry += self.makeParameterName(param_name)
440                # TODO switch when cosmetic changes OK
441                # entry += ' is not {}, '.format(self.null)
442                entry += ' is not `NULL`, '
443            return entry
444
445        if param.get('optional'):
446            # Don't generate this stub for bitflags
447            type_category = self.getTypeCategory(paramtype)
448            is_optional = param.get('optional').split(',')[0] == 'true'
449            if type_category != 'bitmask' and is_optional:
450                if self.paramIsArray(param) or self.paramIsPointer(param):
451                    optional_val = self.null
452                elif type_category == 'handle':
453                    if self.isHandleTypeDispatchable(paramtype):
454                        optional_val = self.null
455                    else:
456                        optional_val = 'dlink:' + self.conventions.api_prefix + 'NULL_HANDLE'
457                else:
458                    optional_val = self.conventions.zero
459
460                entry += 'If {} is not {}, '.format(
461                    self.makeParameterName(param_name),
462                    optional_val)
463            return entry
464
465        # If none of the early returns happened, we at least return an empty
466        # entry with an anchor.
467        return entry
468
469    def createValidationLineForParameterImpl(self, blockname, param, params, typetext, selector, parentname):
470        """Make the generic validity portion used for all parameters.
471
472        May return None if nothing to validate.
473        """
474        if param.get('noautovalidity') is not None:
475            return None
476
477        validity = self.makeValidityCollection(blockname)
478        param_name = getElemName(param)
479        paramtype = getElemType(param)
480
481        entry = self.makeParamValidityPre(param, params, selector)
482
483        # This is for a child member of a union
484        if selector:
485            entry += 'the {} member of {} must: be '.format(self.makeParameterName(param_name), self.makeParameterName(parentname))
486        else:
487            entry += '{} must: be '.format(self.makeParameterName(param_name))
488
489        if self.paramIsStaticArray(param) and paramtype == 'char':
490            # TODO this is a minor hack to determine if this is a command parameter or a struct member
491            if self.paramIsConst(param) or blockname.startswith(self.conventions.type_prefix):
492                entry += 'a null-terminated UTF-8 string whose length is less than or equal to '
493                entry += self.staticArrayLength(param)
494            else:
495                # This is a command's output parameter
496                entry += 'a character array of length %s ' % self.staticArrayLength(param)
497            validity += entry
498            return validity
499
500        elif self.paramIsArray(param):
501            # Arrays. These are hard to get right, apparently
502
503            lengths = LengthEntry.parse_len_from_param(param)
504
505            for i, length in enumerate(LengthEntry.parse_len_from_param(param)):
506                if i == 0:
507                    # If the first index, make it singular.
508                    entry += 'a '
509                    array_text = 'an array'
510                    pointer_text = self.valid_pointer_text
511                else:
512                    array_text = 'arrays'
513                    pointer_text = self.valid_pointer_text + 's'
514
515                if length.null_terminated:
516                    # This should always be the last thing.
517                    # If it ever isn't for some bizarre reason, then this will need some massaging.
518                    entry += 'null-terminated '
519                elif length.number == 1:
520                    entry += pointer_text
521                    entry += ' to '
522                else:
523                    entry += pointer_text
524                    entry += ' to '
525                    entry += array_text
526                    entry += ' of '
527                    # Handle equations, which are currently denoted with latex
528                    if length.math:
529                        # Handle equations, which are currently denoted with latex
530                        entry += str(length)
531                    else:
532                        entry += self.makeParameterName(str(length))
533                    entry += ' '
534
535            # Void pointers don't actually point at anything - remove the word "to"
536            if paramtype == 'void':
537                if lengths[-1].number == 1:
538                    if len(lengths) > 1:
539                        # Take care of the extra s added by the post array chunk function. #HACK#
540                        entry.drop_end(5)
541                    else:
542                        entry.drop_end(4)
543
544                    # This hasn't been hit, so this hasn't been tested recently.
545                    raise UnhandledCaseError(
546                        "Got void pointer param/member with last length 1")
547                else:
548                    # An array of void values is a byte array.
549                    entry += 'byte'
550
551            elif paramtype == 'char':
552                # A null terminated array of chars is a string
553                if lengths[-1].null_terminated:
554                    entry += 'UTF-8 string'
555                else:
556                    # Else it's just a bunch of chars
557                    entry += 'char value'
558
559            elif self.paramIsConst(param):
560                # If a value is "const" that means it won't get modified, so it must be valid going into the function.
561                if 'const' in param.text:
562
563                    if not self.isStructAlwaysValid(paramtype):
564                        entry += 'valid '
565
566            # Check if the array elements are optional
567            array_element_optional = param.get('optional') is not None    \
568                      and len(param.get('optional').split(',')) == len(LengthEntry.parse_len_from_param(param)) + 1 \
569                      and param.get('optional').split(',')[-1] == 'true'
570            if array_element_optional and self.getTypeCategory(paramtype) != 'bitmask': # bitmask is handled later
571                entry += 'or dlink:' + self.conventions.api_prefix + 'NULL_HANDLE '
572
573            entry += typetext
574
575            # pluralize
576            if len(lengths) > 1 or (lengths[0] != 1 and not lengths[0].null_terminated):
577                entry += 's'
578
579            return self.handleRequiredBitmask(blockname, param, paramtype, entry, 'true' if array_element_optional else None)
580
581        if self.paramIsPointer(param):
582            # Handle pointers - which are really special case arrays (i.e. they don't have a length)
583            # TODO  should do something here if someone ever uses some intricate comma-separated `optional`
584            pointercount = param.find('type').tail.count('*')
585
586            # Treat void* as an int
587            if paramtype == 'void':
588                optional = param.get('optional')
589                # If there is only void*, it is just optional int - we don't need any language.
590                if pointercount == 1 and optional is not None:
591                    return None  # early return
592                # Treat the inner-most void* as an int
593                pointercount -= 1
594
595            # Could be multi-level pointers (e.g. ppData - pointer to a pointer). Handle that.
596            entry += 'a '
597            entry += (self.valid_pointer_text + ' to a ') * pointercount
598
599            # Handle void* and pointers to it
600            if paramtype == 'void':
601                if optional is None or optional.split(',')[pointercount]:
602                    # The last void* is just optional int (e.g. to be filled by the impl.)
603                    typetext = 'pointer value'
604
605            # If a value is "const" that means it won't get modified, so it must be valid going into the function.
606            elif self.paramIsConst(param) and paramtype != 'void':
607                entry += 'valid '
608
609            entry += typetext
610            return self.handleRequiredBitmask(blockname, param, paramtype, entry, param.get('optional'))
611
612        # Add additional line for non-optional bitmasks
613        if self.getTypeCategory(paramtype) == 'bitmask':
614            # TODO does not really handle if someone tries something like optional="true,false"
615            # TODO OpenXR has 0 or a valid combination of flags, for optional things.
616            # Vulkan doesn't...
617            # isMandatory = param.get('optional') is None
618            # if not isMandatory:
619            #     entry += self.conventions.zero
620            #     entry += ' or '
621            # Non-pointer, non-optional things must be valid
622            entry += 'a valid {}'.format(typetext)
623
624            return self.handleRequiredBitmask(blockname, param, paramtype, entry, param.get('optional'))
625
626        # Non-pointer, non-optional things must be valid
627        entry += 'a valid {}'.format(typetext)
628        return entry
629
630    def handleRequiredBitmask(self, blockname, param, paramtype, entry, optional):
631        # TODO does not really handle if someone tries something like optional="true,false"
632        if self.getTypeCategory(paramtype) != 'bitmask' or optional == 'true':
633            return entry
634        if self.paramIsPointer(param) and not self.paramIsArray(param):
635            # This is presumably an output parameter
636            return entry
637
638        param_name = getElemName(param)
639        # If mandatory, then we need two entries instead of just one.
640        validity = self.makeValidityCollection(blockname)
641        validity += entry
642
643        entry2 = ValidityEntry(anchor=(param_name, 'requiredbitmask'))
644        if self.paramIsArray(param):
645            entry2 += 'Each element of '
646        entry2 += '{} must: not be {}'.format(
647            self.makeParameterName(param_name), self.conventions.zero)
648        validity += entry2
649        return validity
650
651    def createValidationLineForParameter(self, blockname, param, params, typecategory, selector, parentname):
652        """Make an entire validation entry for a given parameter."""
653        param_name = getElemName(param)
654        paramtype = getElemType(param)
655
656        is_array = self.paramIsArray(param)
657        is_pointer = self.paramIsPointer(param)
658        needs_recursive_validity = (is_array
659                                    or is_pointer
660                                    or not self.isStructAlwaysValid(paramtype))
661        typetext = None
662        if paramtype in ('void', 'char'):
663            # Chars and void are special cases - we call the impl function,
664            # but don't use the typetext.
665            # A null-terminated char array is a string, else it's chars.
666            # An array of void values is a byte array, a void pointer is just a pointer to nothing in particular
667            typetext = ''
668
669        elif typecategory == 'bitmask':
670            bitsname = paramtype.replace('Flags', 'FlagBits')
671            bitselem = self.registry.tree.find("enums[@name='" + bitsname + "']")
672
673            # If bitsname is an alias, then use the alias to get bitselem.
674            typeElem = self.registry.lookupElementInfo(bitsname, self.registry.typedict)
675            if typeElem is not None:
676                alias = self.registry.getAlias(typeElem.elem, self.registry.typedict)
677                if alias is not None:
678                    bitselem = self.registry.tree.find("enums[@name='" + alias + "']")
679
680            if bitselem is None or len(bitselem.findall('enum[@required="true"]')) == 0:
681                # Empty bit mask: presumably just a placeholder (or only in
682                # an extension not enabled for this build)
683                entry = ValidityEntry(
684                    anchor=(param_name, 'zerobitmask'))
685                entry += self.makeParameterName(param_name)
686                entry += ' must: be '
687                entry += self.conventions.zero
688                # Early return
689                return entry
690
691            is_const = self.paramIsConst(param)
692
693            if is_array:
694                if is_const:
695                    # input an array of bitmask values
696                    template = 'combinations of {bitsname} value'
697                else:
698                    template = '{paramtype} value'
699            elif is_pointer:
700                if is_const:
701                    template = 'combination of {bitsname} values'
702                else:
703                    template = '{paramtype} value'
704            else:
705                template = 'combination of {bitsname} values'
706
707            # The above few cases all use makeEnumerationName, just with different context.
708            typetext = template.format(
709                bitsname=self.makeEnumerationName(bitsname),
710                paramtype=self.makeEnumerationName(paramtype))
711
712        elif typecategory == 'handle':
713            typetext = '{} handle'.format(self.makeStructName(paramtype))
714
715        elif typecategory == 'enum':
716            typetext = '{} value'.format(self.makeEnumerationName(paramtype))
717
718        elif typecategory == 'funcpointer':
719            typetext = '{} value'.format(self.makeFuncPointerName(paramtype))
720
721        elif typecategory == 'struct':
722            if needs_recursive_validity:
723                typetext = '{} structure'.format(
724                    self.makeStructName(paramtype))
725
726        elif typecategory == 'union':
727            if needs_recursive_validity:
728                typetext = '{} union'.format(self.makeStructName(paramtype))
729
730        elif self.paramIsArray(param) or self.paramIsPointer(param):
731            # TODO sync cosmetic changes from OpenXR?
732            typetext = '{} value'.format(self.makeBaseTypeName(paramtype))
733
734        elif typecategory is None:
735            if not self.isStructAlwaysValid(paramtype):
736                typetext = '{} value'.format(
737                    self.makeExternalTypeName(paramtype))
738
739            # "a valid uint32_t value" doesn't make much sense.
740            pass
741
742        # If any of the above conditions matched and set typetext,
743        # we call using it.
744        if typetext is not None:
745            return self.createValidationLineForParameterImpl(
746                blockname, param, params, typetext, selector, parentname)
747        return None
748
749    def makeHandleValidityParent(self, param, params):
750        """Make a validity entry for a handle's parent object.
751
752        Creates 'parent' VUID.
753        """
754        param_name = getElemName(param)
755        paramtype = getElemType(param)
756
757        # Deal with handle parents
758        handleparent = self.getHandleParent(paramtype)
759        if handleparent is None:
760            return None
761
762        otherparam = findTypedElem(params, handleparent)
763        if otherparam is None:
764            return None
765
766        parent_name = getElemName(otherparam)
767        entry = ValidityEntry(anchor=(param_name, 'parent'))
768
769        is_optional = self.isHandleOptional(param, params)
770
771        if self.paramIsArray(param):
772            template = 'Each element of {}'
773            if is_optional:
774                template += ' that is a valid handle'
775        elif is_optional:
776            template = 'If {} is a valid handle, it'
777        else:
778            # not optional, not an array. Just say the parameter name.
779            template = '{}'
780
781        entry += template.format(self.makeParameterName(param_name))
782
783        entry += ' must: have been created, allocated, or retrieved from {}'.format(
784            self.makeParameterName(parent_name))
785
786        return entry
787
788    def makeAsciiDocHandlesCommonAncestor(self, blockname, handles, params):
789        """Make an asciidoc validity entry for a common ancestors between handles.
790
791        Only handles parent validity for signatures taking multiple handles
792        any ancestors also being supplied to this function.
793        (e.g. "Each of x, y, and z must: come from the same slink:ParentHandle")
794        See self.makeAsciiDocHandleParent() for instances where the parent
795        handle is named and also passed.
796
797        Creates 'commonparent' VUID.
798        """
799        # TODO Replace with refactored code from OpenXR
800        entry = None
801
802        if len(handles) > 1:
803            ancestormap = {}
804            anyoptional = False
805            # Find all the ancestors
806            for param in handles:
807                paramtype = getElemType(param)
808
809                if not self.paramIsPointer(param) or (param.text and 'const' in param.text):
810                    ancestors = self.getHandleDispatchableAncestors(paramtype)
811
812                    ancestormap[param] = ancestors
813
814                    anyoptional |= self.isHandleOptional(param, params)
815
816            # Remove redundant ancestor lists
817            for param in handles:
818                paramtype = getElemType(param)
819
820                removals = []
821                for ancestors in ancestormap.items():
822                    if paramtype in ancestors[1]:
823                        removals.append(ancestors[0])
824
825                if removals != []:
826                    for removal in removals:
827                        del(ancestormap[removal])
828
829            # Intersect
830
831            if len(ancestormap.values()) > 1:
832                current = list(ancestormap.values())[0]
833                for ancestors in list(ancestormap.values())[1:]:
834                    current = [val for val in current if val in ancestors]
835
836                if len(current) > 0:
837                    commonancestor = current[0]
838
839                    if len(ancestormap.keys()) > 1:
840
841                        entry = ValidityEntry(anchor=('commonparent',))
842
843                        parametertexts = []
844                        for param in ancestormap.keys():
845                            param_name = getElemName(param)
846                            parametertext = self.makeParameterName(param_name)
847                            if self.paramIsArray(param):
848                                parametertext = 'the elements of ' + parametertext
849                            parametertexts.append(parametertext)
850
851                        parametertexts.sort()
852
853                        if len(parametertexts) > 2:
854                            entry += 'Each of '
855                        else:
856                            entry += 'Both of '
857
858                        entry += self.makeProseList(parametertexts,
859                                                    comma_for_two_elts=True)
860                        if anyoptional is True:
861                            entry += ' that are valid handles of non-ignored parameters'
862                        entry += ' must: have been created, allocated, or retrieved from the same '
863                        entry += self.makeStructName(commonancestor)
864
865        return entry
866
867    def makeStructureTypeFromName(self, structname):
868        """Create text for a structure type name, like ename:VK_STRUCTURE_TYPE_CREATE_INSTANCE_INFO"""
869        return self.makeEnumerantName(self.conventions.generate_structure_type_from_name(structname))
870
871    def makeStructureTypeValidity(self, structname):
872        """Generate an validity line for the type value of a struct.
873
874        Creates VUID named like the member name.
875        """
876        info = self.registry.typedict.get(structname)
877        assert(info is not None)
878
879        # If this fails (meaning we have something other than a struct in here),
880        # then the caller is wrong:
881        # probably passing the wrong value for structname.
882        members = info.getMembers()
883        assert(members)
884
885        # If this fails, see caller: this should only get called for a struct type with a type value.
886        param = findNamedElem(members, self.structtype_member_name)
887        # OpenXR gets some structs without a type field in here, so can't assert
888        assert(param is not None)
889        # if param is None:
890        #     return None
891
892        entry = ValidityEntry(
893            anchor=(self.structtype_member_name, self.structtype_member_name))
894        entry += self.makeParameterName(self.structtype_member_name)
895        entry += ' must: be '
896
897        values = param.get('values', '').split(',')
898        if values:
899            # Extract each enumerant value. They could be validated in the
900            # same fashion as validextensionstructs in
901            # makeStructureExtensionPointer, although that's not relevant in
902            # the current extension struct model.
903            entry += self.makeProseList((self.makeEnumerantName(v)
904                                         for v in values), 'or')
905            return entry
906
907        if 'Base' in structname:
908            # This type doesn't even have any values for its type,
909            # and it seems like it might be a base struct that we'd expect to lack its own type,
910            # so omit the entire statement
911            return None
912
913        self.logMsg('warn', 'No values were marked-up for the structure type member of',
914                    structname, 'so making one up!')
915        entry += self.makeStructureTypeFromName(structname)
916
917        return entry
918
919    def makeStructureExtensionPointer(self, blockname, param):
920        """Generate an validity line for the pointer chain member value of a struct."""
921        param_name = getElemName(param)
922
923        if param.get('validextensionstructs') is not None:
924            self.logMsg('warn', blockname,
925                        'validextensionstructs is deprecated/removed', '\n')
926
927        entry = ValidityEntry(
928            anchor=(param_name, self.nextpointer_member_name))
929        validextensionstructs = self.registry.validextensionstructs.get(
930            blockname)
931        extensionstructs = []
932        duplicatestructs = []
933
934        if validextensionstructs is not None:
935            # Check each structure name and skip it if not required by the
936            # generator. This allows tagging extension structs in the XML
937            # that are only included in validity when needed for the spec
938            # being targeted.
939            # Track the required structures, and of the required structures,
940            # those that allow duplicates in the pNext chain.
941            for struct in validextensionstructs:
942                # Unpleasantly breaks encapsulation. Should be a method in the registry class
943                t = self.registry.lookupElementInfo(
944                    struct, self.registry.typedict)
945                if t is None:
946                    self.logMsg('warn', 'makeStructureExtensionPointer: struct', struct,
947                                'is in a validextensionstructs= attribute but is not in the registry')
948                elif t.required:
949                    extensionstructs.append('slink:' + struct)
950                    if t.elem.get('allowduplicate') == 'true':
951                        duplicatestructs.append('slink:' + struct)
952                else:
953                    self.logMsg(
954                        'diag', 'makeStructureExtensionPointer: struct', struct, 'IS NOT required')
955
956        if not extensionstructs:
957            entry += '{} must: be {}'.format(
958                self.makeParameterName(param_name), self.null)
959            return entry
960
961        if len(extensionstructs) == 1:
962            entry += '{} must: be {} or a pointer to a valid instance of {}'.format(self.makeParameterName(param_name), self.null,
963                                                                                    extensionstructs[0])
964        else:
965            # More than one extension struct.
966            entry += 'Each {} member of any structure (including this one) in the pname:{} chain '.format(
967                self.makeParameterName(param_name), self.nextpointer_member_name)
968            entry += 'must: be either {} or a pointer to a valid instance of '.format(
969                self.null)
970
971            entry += self.makeProseList(extensionstructs, fmt=plf.OR)
972
973        validity = self.makeValidityCollection(blockname)
974        validity += entry
975
976        # Generate VU statement requiring unique structures in the pNext
977        # chain.
978        # NOTE: OpenXR always allows non-unique type values. Instances other
979        # than the first are just ignored
980
981        vu = ('The pname:' +
982              self.structtype_member_name +
983              ' value of each struct in the pname:' +
984              self.nextpointer_member_name +
985              ' chain must: be unique')
986        anchor = (self.conventions.member_used_for_unique_vuid, 'unique')
987
988        # If duplicates of some structures are allowed, they are called out
989        # explicitly.
990        num = len(duplicatestructs)
991        if num > 0:
992            vu = (vu +
993                  ', with the exception of structures of type ' +
994                  self.makeProseList(duplicatestructs, fmt=plf.OR))
995
996        validity.addValidityEntry(vu, anchor = anchor )
997
998        return validity
999
1000    def addSharedStructMemberValidity(self, struct, blockname, param, validity):
1001        """Generate language to independently validate a parameter, for those validated even in output.
1002
1003        Return value indicates whether it was handled internally (True) or if it may need more validity (False)."""
1004        param_name = getElemName(param)
1005        paramtype = getElemType(param)
1006        if param.get('noautovalidity') is None:
1007
1008            if self.conventions.is_structure_type_member(paramtype, param_name):
1009                validity += self.makeStructureTypeValidity(blockname)
1010                return True
1011
1012            if self.conventions.is_nextpointer_member(paramtype, param_name):
1013                # Vulkan: the addition of validity here is conditional unlike OpenXR.
1014                if struct.get('structextends') is None:
1015                    validity += self.makeStructureExtensionPointer(
1016                        blockname, param)
1017                return True
1018        return False
1019
1020    def makeOutputOnlyStructValidity(self, cmd, blockname, params):
1021        """Generate all the valid usage information for a struct that's entirely output.
1022
1023        That is, it is only ever filled out by the implementation other than
1024        the structure type and pointer chain members.
1025        Thus, we only create validity for the pointer chain member.
1026        """
1027        # Start the validity collection for this struct
1028        validity = self.makeValidityCollection(blockname)
1029
1030        for param in params:
1031            self.addSharedStructMemberValidity(
1032                cmd, blockname, param, validity)
1033
1034        return validity
1035
1036    def makeStructOrCommandValidity(self, cmd, blockname, params):
1037        """Generate all the valid usage information for a given struct or command."""
1038        validity = self.makeValidityCollection(blockname)
1039        handles = []
1040        arraylengths = dict()
1041        for param in params:
1042            param_name = getElemName(param)
1043            paramtype = getElemType(param)
1044
1045            # Valid usage ID tags (VUID) are generated for various
1046            # conditions based on the name of the block (structure or
1047            # command), name of the element (member or parameter), and type
1048            # of VU statement.
1049
1050            # Get the type's category
1051            typecategory = self.getTypeCategory(paramtype)
1052
1053            if not self.addSharedStructMemberValidity(
1054                    cmd, blockname, param, validity):
1055                if not param.get('selector'):
1056                    validity += self.createValidationLineForParameter(
1057                        blockname, param, params, typecategory, None, None)
1058                else:
1059                    selector = param.get('selector')
1060                    if typecategory != 'union':
1061                        self.logMsg('warn', 'selector attribute set on non-union parameter', param_name, 'in', blockname)
1062
1063                    paraminfo = self.registry.lookupElementInfo(paramtype, self.registry.typedict)
1064
1065                    for member in paraminfo.getMembers():
1066                        membertype = getElemType(member)
1067                        membertypecategory = self.getTypeCategory(membertype)
1068
1069                        validity += self.createValidationLineForParameter(
1070                            blockname, member, paraminfo.getMembers(), membertypecategory, selector, param_name)
1071
1072            # Ensure that any parenting is properly validated, and list that a handle was found
1073            if typecategory == 'handle':
1074                handles.append(param)
1075
1076            # Get the array length for this parameter
1077            lengths = LengthEntry.parse_len_from_param(param)
1078            if lengths:
1079                arraylengths.update({length.other_param_name: length
1080                                     for length in lengths
1081                                     if length.other_param_name})
1082
1083        # For any vkQueue* functions, there might be queue type data
1084        if 'vkQueue' in blockname:
1085            # The queue type must be valid
1086            queuebits = _parse_queue_bits(cmd)
1087            if queuebits:
1088                entry = ValidityEntry(anchor=('queuetype',))
1089                entry += 'The pname:queue must: support '
1090                entry += self.makeProseList(queuebits,
1091                                            fmt=plf.OR, comma_for_two_elts=True)
1092                entry += ' operations'
1093                validity += entry
1094
1095        if 'vkCmd' in blockname:
1096            # The commandBuffer parameter must be being recorded
1097            entry = ValidityEntry(anchor=('commandBuffer', 'recording'))
1098            entry += 'pname:commandBuffer must: be in the <<commandbuffers-lifecycle, recording state>>'
1099            validity += entry
1100
1101            #
1102            # Start of valid queue type validation - command pool must have been
1103            # allocated against a queue with at least one of the valid queue types
1104            entry = ValidityEntry(anchor=('commandBuffer', 'cmdpool'))
1105
1106            #
1107            # This test for vkCmdFillBuffer is a hack, since we have no path
1108            # to conditionally have queues enabled or disabled by an extension.
1109            # As the VU stuff is all moving out (hopefully soon), this hack solves the issue for now
1110            if blockname == 'vkCmdFillBuffer':
1111                entry += 'The sname:VkCommandPool that pname:commandBuffer was allocated from must: support '
1112                if 'VK_KHR_maintenance1' in self.registry.requiredextensions:
1113                    entry += 'transfer, graphics or compute operations'
1114                else:
1115                    entry += 'graphics or compute operations'
1116            else:
1117                # The queue type must be valid
1118                queuebits = _parse_queue_bits(cmd)
1119                assert(queuebits)
1120                entry += 'The sname:VkCommandPool that pname:commandBuffer was allocated from must: support '
1121                entry += self.makeProseList(queuebits,
1122                                            fmt=plf.OR, comma_for_two_elts=True)
1123                entry += ' operations'
1124            validity += entry
1125
1126            # Must be called inside/outside a renderpass appropriately
1127            renderpass = cmd.get('renderpass')
1128
1129            if renderpass != 'both':
1130                entry = ValidityEntry(anchor=('renderpass',))
1131                entry += 'This command must: only be called '
1132                entry += renderpass
1133                entry += ' of a render pass instance'
1134                validity += entry
1135
1136            # Must be in the right level command buffer
1137            cmdbufferlevel = cmd.get('cmdbufferlevel')
1138
1139            if cmdbufferlevel != 'primary,secondary':
1140                entry = ValidityEntry(anchor=('bufferlevel',))
1141                entry += 'pname:commandBuffer must: be a '
1142                entry += cmdbufferlevel
1143                entry += ' sname:VkCommandBuffer'
1144                validity += entry
1145
1146        # Any non-optional arraylengths should specify they must be greater than 0
1147        array_length_params = ((param, getElemName(param))
1148                               for param in params
1149                               if getElemName(param) in arraylengths)
1150
1151        for param, param_name in array_length_params:
1152            if param.get('optional') is not None:
1153                continue
1154
1155            length = arraylengths[param_name]
1156            full_length = length.full_reference
1157
1158            # Is this just a name of a param? If false, then it's some kind of qualified name (a member of a param for instance)
1159            simple_param_reference = (len(length.param_ref_parts) == 1)
1160            if not simple_param_reference:
1161                # Loop through to see if any parameters in the chain are optional
1162                array_length_parent = cmd
1163                array_length_optional = False
1164                for part in length.param_ref_parts:
1165                    # Overwrite the param so it ends up as the bottom level parameter for later checks
1166                    param = array_length_parent.find("*/[name='{}']".format(part))
1167
1168                    # If any parameter in the chain is optional, skip the implicit length requirement
1169                    array_length_optional |= (param.get('optional') is not None)
1170
1171                    # Lookup the type of the parameter for the next loop iteration
1172                    type = param.findtext('type')
1173                    array_length_parent = self.registry.tree.find("./types/type/[@name='{}']".format(type))
1174
1175                if array_length_optional:
1176                    continue
1177
1178            # Get all the array dependencies
1179            arrays = cmd.findall(
1180                "param/[@len='{}'][@optional='true']".format(full_length))
1181
1182            # Get all the optional array dependencies, including those not generating validity for some reason
1183            optionalarrays = arrays + \
1184                cmd.findall(
1185                    "param/[@len='{}'][@noautovalidity='true']".format(full_length))
1186
1187            entry = ValidityEntry(anchor=(full_length, 'arraylength'))
1188            # Allow lengths to be arbitrary if all their dependents are optional
1189            if optionalarrays and len(optionalarrays) == len(arrays):
1190                entry += 'If '
1191                # TODO sync this section from OpenXR once cosmetic changes OK
1192
1193                optional_array_names = (self.makeParameterName(getElemName(array))
1194                                        for array in optionalarrays)
1195                entry += self.makeProseListIs(optional_array_names,
1196                                              plf.ANY_OR, comma_for_two_elts=True)
1197
1198                entry += ' not {}, '.format(self.null)
1199
1200            # TODO end needs sync cosmetic
1201            if self.paramIsPointer(param):
1202                entry += 'the value referenced by '
1203
1204            # Split and re-join here to insert pname: around ::
1205            entry += '::'.join(self.makeParameterName(part)
1206                               for part in full_length.split('::'))
1207            # TODO replace the previous statement with the following when cosmetic changes OK
1208            # entry += length.get_human_readable(make_param_name=self.makeParameterName)
1209
1210            entry += ' must: be greater than '
1211            entry += self.conventions.zero
1212            validity += entry
1213
1214        # Find the parents of all objects referenced in this command
1215        for param in handles:
1216            # Don't detect a parent for return values!
1217            if not self.paramIsPointer(param) or self.paramIsConst(param):
1218                validity += self.makeHandleValidityParent(param, params)
1219
1220        # Find the common ancestor of all objects referenced in this command
1221        validity += self.makeAsciiDocHandlesCommonAncestor(
1222            blockname, handles, params)
1223
1224        return validity
1225
1226    def makeThreadSafetyBlock(self, cmd, paramtext):
1227        """Generate thread-safety validity entries for cmd/structure"""
1228        # See also makeThreadSafetyBlock in validitygenerator.py
1229        validity = self.makeValidityCollection(getElemName(cmd))
1230
1231        # This text varies between projects, so an Asciidoctor attribute is used.
1232        extsync_prefix = "{externsyncprefix} "
1233
1234        # Find and add any parameters that are thread unsafe
1235        explicitexternsyncparams = cmd.findall(paramtext + "[@externsync]")
1236        if explicitexternsyncparams is not None:
1237            for param in explicitexternsyncparams:
1238                externsyncattribs = ExternSyncEntry.parse_externsync_from_param(
1239                    param)
1240                param_name = getElemName(param)
1241
1242                for attrib in externsyncattribs:
1243                    entry = ValidityEntry()
1244                    entry += extsync_prefix
1245                    if attrib.entirely_extern_sync:
1246                        if self.paramIsArray(param):
1247                            entry += 'each member of '
1248                        elif self.paramIsPointer(param):
1249                            entry += 'the object referenced by '
1250
1251                        entry += self.makeParameterName(param_name)
1252
1253                        if attrib.children_extern_sync:
1254                            entry += ', and any child handles,'
1255
1256                    else:
1257                        entry += 'pname:'
1258                        entry += str(attrib.full_reference)
1259                        # TODO switch to the following when cosmetic changes OK
1260                        # entry += attrib.get_human_readable(make_param_name=self.makeParameterName)
1261                    entry += ' must: be externally synchronized'
1262                    validity += entry
1263
1264        # Vulkan-specific
1265        # For any vkCmd* functions, the command pool is externally synchronized
1266        if cmd.find('proto/name') is not None and 'vkCmd' in cmd.find('proto/name').text:
1267            entry = ValidityEntry()
1268            entry += extsync_prefix
1269            entry += 'the sname:VkCommandPool that pname:commandBuffer was allocated from must: be externally synchronized'
1270            validity += entry
1271
1272        # Find and add any "implicit" parameters that are thread unsafe
1273        implicitexternsyncparams = cmd.find('implicitexternsyncparams')
1274        if implicitexternsyncparams is not None:
1275            for elem in implicitexternsyncparams:
1276                entry = ValidityEntry()
1277                entry += extsync_prefix
1278                entry += elem.text
1279                entry += ' must: be externally synchronized'
1280                validity += entry
1281
1282        return validity
1283
1284    def makeCommandPropertiesTableEntry(self, cmd, name):
1285
1286        if 'vkCmd' in name:
1287            # Must be called inside/outside a renderpass appropriately
1288            cmdbufferlevel = cmd.get('cmdbufferlevel')
1289            cmdbufferlevel = (' + \n').join(cmdbufferlevel.title().split(','))
1290
1291            renderpass = cmd.get('renderpass')
1292            renderpass = renderpass.capitalize()
1293
1294            #
1295            # This test for vkCmdFillBuffer is a hack, since we have no path
1296            # to conditionally have queues enabled or disabled by an extension.
1297            # As the VU stuff is all moving out (hopefully soon), this hack solves the issue for now
1298            if name == 'vkCmdFillBuffer':
1299                if 'VK_KHR_maintenance1' in self.registry.requiredextensions:
1300                    queues = 'Transfer + \nGraphics + \nCompute'
1301                else:
1302                    queues = 'Graphics + \nCompute'
1303            else:
1304                queues = cmd.get('queues')
1305                queues = (' + \n').join(queues.title().split(','))
1306
1307            pipeline = cmd.get('pipeline')
1308            if pipeline:
1309                pipeline = pipeline.capitalize()
1310            else:
1311                pipeline = ''
1312
1313            return '|' + cmdbufferlevel + '|' + renderpass + '|' + queues + '|' + pipeline
1314        elif 'vkQueue' in name:
1315            # Must be called inside/outside a renderpass appropriately
1316
1317            queues = cmd.get('queues')
1318            if queues is None:
1319                queues = 'Any'
1320            else:
1321                queues = (' + \n').join(queues.upper().split(','))
1322
1323            return '|-|-|' + queues + '|-'
1324
1325        return None
1326
1327
1328    def findRequiredEnums(self, enums):
1329        """Check each enumerant name in the enums list and remove it if not
1330        required by the generator. This allows specifying success and error
1331        codes for extensions that are only included in validity when needed
1332        for the spec being targeted."""
1333        return self.keepOnlyRequired(enums, self.registry.enumdict)
1334
1335    def findRequiredCommands(self, commands):
1336        """Check each command name in the commands list and remove it if not
1337        required by the generator.
1338
1339        This will allow some state operations to take place before endFile."""
1340        return self.keepOnlyRequired(commands, self.registry.cmddict)
1341
1342    def keepOnlyRequired(self, names, info_dict):
1343        """Check each element name in the supplied dictionary and remove it if not
1344        required by the generator.
1345
1346        This will allow some operations to take place before endFile no matter the order of generation."""
1347        # TODO Unpleasantly breaks encapsulation. Should be a method in the registry class
1348
1349        def is_required(name):
1350            info = self.registry.lookupElementInfo(name, info_dict)
1351            if info is None:
1352                return False
1353            if not info.required:
1354                self.logMsg('diag', 'keepOnlyRequired: element',
1355                            name, 'IS NOT required, skipping')
1356            return info.required
1357
1358        return [name
1359                for name in names
1360                if is_required(name)]
1361
1362    def makeReturnCodeList(self, attrib, cmd, name):
1363        """Return a list of possible return codes for a function.
1364
1365        attrib is either 'successcodes' or 'errorcodes'.
1366        """
1367        return_lines = []
1368        RETURN_CODE_FORMAT = '* ename:{}'
1369
1370        codes_attr = cmd.get(attrib)
1371        if codes_attr:
1372            codes = self.findRequiredEnums(codes_attr.split(','))
1373            if codes:
1374                return_lines.extend((RETURN_CODE_FORMAT.format(code)
1375                                     for code in codes))
1376
1377        applicable_ext_codes = (ext_code
1378                                for ext_code in self.registry.commandextensionsuccesses
1379                                if ext_code.command == name)
1380        for ext_code in applicable_ext_codes:
1381            line = RETURN_CODE_FORMAT.format(ext_code.value)
1382            if ext_code.extension:
1383                line += ' [only if {} is enabled]'.format(
1384                    self.conventions.formatExtension(ext_code.extension))
1385
1386            return_lines.append(line)
1387        if return_lines:
1388            return '\n'.join(return_lines)
1389
1390        return None
1391
1392    def makeSuccessCodes(self, cmd, name):
1393        return self.makeReturnCodeList('successcodes', cmd, name)
1394
1395    def makeErrorCodes(self, cmd, name):
1396        return self.makeReturnCodeList('errorcodes', cmd, name)
1397
1398    def genCmd(self, cmdinfo, name, alias):
1399        """Command generation."""
1400        OutputGenerator.genCmd(self, cmdinfo, name, alias)
1401
1402        # @@@ (Jon) something needs to be done here to handle aliases, probably
1403
1404        validity = self.makeValidityCollection(name)
1405
1406        # OpenXR-only: make sure extension is enabled
1407        # validity.possiblyAddExtensionRequirement(self.currentExtension, 'calling flink:')
1408
1409        validity += self.makeStructOrCommandValidity(
1410            cmdinfo.elem, name, cmdinfo.getParams())
1411
1412        threadsafety = self.makeThreadSafetyBlock(cmdinfo.elem, 'param')
1413        commandpropertiesentry = None
1414
1415        # Vulkan-specific
1416        commandpropertiesentry = self.makeCommandPropertiesTableEntry(
1417            cmdinfo.elem, name)
1418        successcodes = self.makeSuccessCodes(cmdinfo.elem, name)
1419        errorcodes = self.makeErrorCodes(cmdinfo.elem, name)
1420
1421        # OpenXR-specific
1422        # self.generateStateValidity(validity, name)
1423
1424        self.writeInclude('protos', name, validity, threadsafety,
1425                          commandpropertiesentry, successcodes, errorcodes)
1426
1427    def genStruct(self, typeinfo, typeName, alias):
1428        """Struct Generation."""
1429        OutputGenerator.genStruct(self, typeinfo, typeName, alias)
1430
1431        # @@@ (Jon) something needs to be done here to handle aliases, probably
1432
1433        # Anything that's only ever returned can't be set by the user, so shouldn't have any validity information.
1434        validity = self.makeValidityCollection(typeName)
1435        threadsafety = []
1436
1437        # OpenXR-only: make sure extension is enabled
1438        # validity.possiblyAddExtensionRequirement(self.currentExtension, 'using slink:')
1439
1440        if typeinfo.elem.get('category') != 'union':
1441            if typeinfo.elem.get('returnedonly') is None:
1442                validity += self.makeStructOrCommandValidity(
1443                    typeinfo.elem, typeName, typeinfo.getMembers())
1444                threadsafety = self.makeThreadSafetyBlock(typeinfo.elem, 'member')
1445
1446            else:
1447                # Need to generate structure type and next pointer chain member validation
1448                validity += self.makeOutputOnlyStructValidity(
1449                    typeinfo.elem, typeName, typeinfo.getMembers())
1450
1451        self.writeInclude('structs', typeName, validity,
1452                          threadsafety, None, None, None)
1453
1454    def genGroup(self, groupinfo, groupName, alias):
1455        """Group (e.g. C "enum" type) generation.
1456        For the validity generator, this just tags individual enumerants
1457        as required or not.
1458        """
1459        OutputGenerator.genGroup(self, groupinfo, groupName, alias)
1460
1461        # @@@ (Jon) something needs to be done here to handle aliases, probably
1462
1463        groupElem = groupinfo.elem
1464
1465        # Loop over the nested 'enum' tags. Keep track of the minimum and
1466        # maximum numeric values, if they can be determined; but only for
1467        # core API enumerants, not extension enumerants. This is inferred
1468        # by looking for 'extends' attributes.
1469        for elem in groupElem.findall('enum'):
1470            name = elem.get('name')
1471            ei = self.registry.lookupElementInfo(name, self.registry.enumdict)
1472
1473            # Tag enumerant as required or not
1474            ei.required = self.isEnumRequired(elem)
1475
1476    def genType(self, typeinfo, name, alias):
1477        """Type Generation."""
1478        OutputGenerator.genType(self, typeinfo, name, alias)
1479
1480        # @@@ (Jon) something needs to be done here to handle aliases, probably
1481
1482        category = typeinfo.elem.get('category')
1483        if category in ('struct', 'union'):
1484            self.genStruct(typeinfo, name, alias)
1485