hs-bindgen-1.0.0.0: src-internal/HsBindgen/Frontend/Pass/Select.hs
module HsBindgen.Frontend.Pass.Select (
selectDecls
) where
import Data.Foldable qualified as Foldable
import Data.List qualified as List
import Data.List.NonEmpty qualified as NonEmpty
import Data.Map.Strict qualified as Map
import Data.Maybe (listToMaybe, maybeToList)
import Data.Set ((\\))
import Data.Set qualified as Set
import Clang.HighLevel.Types
import HsBindgen.Errors (panicPure)
import HsBindgen.Frontend.Analysis.DeclIndex (DeclIndex, Entry (..),
Success (..), UnusableEntry (..),
UnusableReason (..),
UsableEntry (..))
import HsBindgen.Frontend.Analysis.DeclIndex qualified as DeclIndex
import HsBindgen.Frontend.Analysis.DeclUseGraph (DeclUseGraph)
import HsBindgen.Frontend.Analysis.DeclUseGraph qualified as DeclUseGraph
import HsBindgen.Frontend.Analysis.IncludeGraph (IncludeGraph)
import HsBindgen.Frontend.Analysis.IncludeGraph qualified as IncludeGraph
import HsBindgen.Frontend.Analysis.UseDeclGraph (UseDeclGraph)
import HsBindgen.Frontend.Analysis.UseDeclGraph qualified as UseDeclGraph
import HsBindgen.Frontend.DeclMeta
import HsBindgen.Frontend.Pass.ConstructTranslationUnit.IsPass
import HsBindgen.Frontend.Pass.Parse.Msg
import HsBindgen.Frontend.Pass.Select.IsPass
import HsBindgen.Frontend.Pass.TranslateTypes.IsPass
import HsBindgen.Frontend.Predicate
import HsBindgen.Frontend.TranslationUnit qualified as C
import HsBindgen.Imports
import HsBindgen.IR.C qualified as C
import HsBindgen.IR.Pass
import HsBindgen.IR.Translation
import HsBindgen.Util.Tracer
{-------------------------------------------------------------------------------
Data types
-------------------------------------------------------------------------------}
-- Declaration itself.
type Decl l = C.Decl l Select
-- | Internal data type! This data type
-- 'HsBindgen.Frontend.Pass.Select.Unselectable' refers to declarations that are
-- not selectable _from the perspective of `hs-bindgen`_; and, in particular,
-- not from the perspective of the user (they can change the select predicate).
--
-- Also, (and in contrast to 'UsableEntry'/'UnusableEntry'), selectability _is
-- concerned with transitivity_. All transitive dependencies of a selectable
-- declaration must also be selectable.
data Unselectable =
-- | We (i.e., `hs-bindgen`) can not select a declaration selected because
-- it or one of its dependencies is unusable.
Unselectable UnusableEntry
-- | We (i.e., `hs-bindgen`) can not select a declaration because one of its
-- dependencies has not been selected by the user.
| UnselectableDependencyNotSelected
deriving stock (Show)
-- | We have to treat with two notions of usability here:
--
-- 1. A declaration can be usable because it is in the list of declarations
-- attached to the translation unit.
--
-- 2. A declaration is available if the declaration itself and all of its
-- transitive dependencies are available.
--
-- @TransitiveSelectability@ deals with the second type.
--
-- (We avoid the term available, because it is overloaded with Clang's
-- CXAvailabilityKind).
data TransitiveSelectability =
TransitivelySelectable
-- | For each transitive dependency, we try to give the root cause of
-- unavailability.
--
-- We should use a "non-empty" map here.
| TransitivelyUnselectable (Map C.DeclId Unselectable)
deriving stock (Show)
{-------------------------------------------------------------------------------
Select
-------------------------------------------------------------------------------}
selectDecls ::
forall l. HasCallStack
=> IsMainHeader
-> IsInMainHeaderDir
-> SelectConfig
-> C.TranslationUnit l TranslateTypes
-> (C.TranslationUnit l Select, [AnnMsg Select])
selectDecls isMainHeader isInMainHeaderDir config unit =
let -- Directly match the selection predicate on the 'DeclIndex', obtaining
-- information about succeeded _and failed_ selection roots.
selectionRootsIndex :: DeclIndex l
selectionRootsIndex = selectDeclIndex unit.meta.declUseGraph match index
-- Identifiers of selection roots. Some of them may be unavailable
-- (i.e., not in the 'succeeded' map, and hence, not in the list of
-- declarations attached to the translation unit).
rootIds :: Set C.DeclId
rootIds = DeclIndex.keysSet selectionRootsIndex
-- Identifiers of transitive dependencies including selection roots.
rootAndTransDepIds :: Set C.DeclId
rootAndTransDepIds = UseDeclGraph.getTransitiveDeps useDeclGraph rootIds
-- Identifiers of transitive dependencies excluding selection roots.
strictTransDepIds :: Set C.DeclId
strictTransDepIds = rootAndTransDepIds \\ rootIds
-- Identifiers of all selected declarations.
selectedIds :: Set C.DeclId
-- Identifiers of (additional) transitive dependencies selected due to
-- program slicing. This is the only point where we differentiate
-- between selection with or without program slicing.
additionalSelectedTransDepIds :: Set C.DeclId
(selectedIds, additionalSelectedTransDepIds) = case config.programSlicing of
DisableProgramSlicing -> (rootIds , Set.empty)
EnableProgramSlicing -> (rootAndTransDepIds, strictTransDepIds)
additionalSelectedTransDepsIndex :: DeclIndex l
additionalSelectedTransDepsIndex = DeclIndex.restrictKeys index additionalSelectedTransDepIds
notSelectedIndex :: DeclIndex l
notSelectedIndex = DeclIndex.withoutKeys index selectedIds
getTransitiveSelectability :: C.DeclId -> TransitiveSelectability
getTransitiveSelectability x
| Map.null unusabilityReasons = TransitivelySelectable
| otherwise = TransitivelyUnselectable unusabilityReasons
where
transDeps :: Set C.DeclId
transDeps =
UseDeclGraph.getStrictTransitiveDeps
useDeclGraph
(Set.singleton x)
unusables :: Map C.DeclId Unselectable
unusables =
Unselectable <$> DeclIndex.getUnusables index transDeps
nonselected :: Map C.DeclId Unselectable
nonselected =
Map.fromSet (const UnselectableDependencyNotSelected) $
transDeps \\ selectedIds
unusabilityReasons :: Map C.DeclId Unselectable
unusabilityReasons =
Map.unionWith
getMostNaturalUnselectable
unusables
nonselected
-- Get the reason that is most useful to the user about why a
-- declaration is unselectable.
getMostNaturalUnselectable ::
Unselectable -> Unselectable -> Unselectable
getMostNaturalUnselectable l r =
case (l,r) of
(UnselectableDependencyNotSelected, Unselectable u) ->
case u of
UnusableReason _ UnusableUnavailable -> r
UnusableReason _ UnusableOmitted -> r
_otherReason -> l
(Unselectable u, UnselectableDependencyNotSelected) ->
case u of
UnusableReason _ UnusableUnavailable -> l
UnusableReason _ UnusableOmitted{} -> l
_otherReason -> r
(_, _) -> l
selectDecl :: Decl l -> Maybe (Decl l)
selectDecl =
selectDeclWith
selectedIds
getTransitiveSelectability
unitDecls :: [Decl l]
unitDecls = map coercePass unit.decls
selectedUnitDecls :: [Decl l]
selectedUnitDecls = mapMaybe selectDecl unitDecls
selectMsgs :: [AnnMsg Select]
selectMsgs =
getSelectMsgs
rootIds
additionalSelectedTransDepIds
getTransitiveSelectability
index
unitSelect :: C.TranslationUnit l Select
unitSelect = C.TranslationUnit {
decls = selectedUnitDecls
, includeGraph = unit.includeGraph
, meta = unit.meta
}
-- If there were no predicate matches we issue a warning to the user.
noDeclarationsMatchedMsg :: [AnnMsg Select]
noDeclarationsMatchedMsg = [
withCallStack C.WithLocationInfo{
loc = C.LocationUnavailable
, msg = SelectNoDeclarationsMatched
}
| Set.null rootIds
]
selectionRootMsgs :: [AnnMsg Select]
selectionRootMsgs = getDelayedMsgsSelectionRoots selectionRootsIndex
-- Number of selected macros that hs-bindgen dropped.
droppedMacroCount :: Int
droppedMacroCount =
length $ filter (\m -> isDroppedMacro m.traceMsg.msg) selectionRootMsgs
-- Single summary of dropped macros.
macrosDroppedSummaryMsg :: [AnnMsg Select]
macrosDroppedSummaryMsg = [
withCallStack C.WithLocationInfo{
loc = C.LocationUnavailable
, msg = SelectMacrosDropped droppedMacroCount
}
| droppedMacroCount > 0
]
msgs :: [AnnMsg Select]
msgs =
concat [
selectMsgs
, selectionRootMsgs
, getDelayedMsgsAdditionalSelectedTransDeps additionalSelectedTransDepsIndex
, getBugMsgsNotSelected notSelectedIndex
, noDeclarationsMatchedMsg
]
in ( unitSelect
, sortSelectMsgs unit.includeGraph msgs ++ macrosDroppedSummaryMsg
)
where
index :: DeclIndex l
index = unit.meta.declIndex
useDeclGraph :: UseDeclGraph
useDeclGraph = unit.meta.useDeclGraph
match :: Match
match name loc availability =
matchSelect
isMainHeader
isInMainHeaderDir
(singleLocPath loc)
name
availability
config.selectionPredicate
{-------------------------------------------------------------------------------
Filter list of declarations
-------------------------------------------------------------------------------}
selectDeclWith ::
-- | Selected declaration IDs.
Set C.DeclId
-> (C.DeclId -> TransitiveSelectability)
-> Decl l
-> Maybe (Decl l)
selectDeclWith
selectedIds
getTransitiveSelectability
decl =
case (isSelected, transitiveSelectability) of
(True, TransitivelySelectable) -> Just decl
_otherwise -> Nothing
where
declId :: C.DeclId
declId = decl.info.id.cName
isSelected :: Bool
isSelected = Set.member declId selectedIds
transitiveSelectability :: TransitiveSelectability
transitiveSelectability = getTransitiveSelectability declId
{-------------------------------------------------------------------------------
Select traces
-------------------------------------------------------------------------------}
getSelectMsgs ::
forall l. HasCallStack
=> Set C.DeclId
-- ^ Selection roots.
-> Set C.DeclId
-- ^ Additionally selected transitive dependencies (non-empty when program
-- slicing is enabled).
->(C.DeclId -> TransitiveSelectability)
-> DeclIndex l
-> [AnnMsg Select]
getSelectMsgs
rootIds
additionalSelectedTransDepIds
getTransitiveSelectability
declIndex
= concatMap (uncurry aux) $ DeclIndex.toList declIndex
where
aux ::
HasCallStack
=> C.DeclId
-> Entry l
-> [AnnMsg Select]
aux =
getSelectMsgsDeclId
getTransitiveSelectability
declIndex
rootIds
additionalSelectedTransDepIds
getSelectMsgsDeclId ::
HasCallStack
=> (C.DeclId -> TransitiveSelectability)
-> DeclIndex l
-- | Selection roots.
-> Set C.DeclId
-- | Additionally selected transitive dependencies (non-empty when program
-- slicing is enabled).
-> Set C.DeclId
-> C.DeclId
-> Entry l
-> [AnnMsg Select]
getSelectMsgsDeclId
getTransitiveSelectability
declIndex
rootIds
additionalSelectedTransDepIds
declId
entry
= case ( isSelectedRoot
, isAdditionalSelectedTransDep
, transitiveSelectability ) of
-- Declaration is a selection root.
(True, False, TransitivelySelectable) ->
getMsgsFor SelectionRoot
(True, False, TransitivelyUnselectable rs) ->
maybeToList $ getUnavailMsg SelectionRoot rs
-- Declaration is an additionally selected transitive dependency.
(False, True, TransitivelySelectable) ->
getMsgsFor TransitiveDependency
(False, True, TransitivelyUnselectable rs) ->
maybeToList $ getUnavailMsg TransitiveDependency rs
-- Declaration is not selected.
(False, False, _) ->
[withLoc $ SelectStatusInfo NotSelected]
-- Declaration is a selection root and a transitive dependency. This
-- should be impossible and we consider it a bug.
(True, True, _) ->
panicPure $
"Declaration is selection root and transitive dependency: "
++ show (withLoc declId)
where
-- We check three conditions:
isSelectedRoot = Set.member declId rootIds
-- These are also always strict transitive dependencies.
isAdditionalSelectedTransDep =
Set.member declId additionalSelectedTransDepIds
transitiveSelectability = getTransitiveSelectability declId
getMsgsFor :: HasCallStack => SelectReason -> [AnnMsg Select]
getMsgsFor selectReason = concat [
[ withLoc $ SelectStatusInfo (Selected selectReason) ]
, [ withLoc $ SelectDeprecated selectReason | isDeprecated ]
]
withLoc :: HasCallStack => a -> WithCallStack (C.WithLocationInfo a)
withLoc x = withCallStack C.WithLocationInfo{
loc = C.declIdLocationInfo declId $
C.declLocsToList $
DeclIndex.entryToLoc entry
, msg = x
}
getUnavailMsg ::
HasCallStack
=> SelectReason
-> Map C.DeclId Unselectable
-> Maybe (AnnMsg Select)
getUnavailMsg selectReason unavailReasons =
if null msgs then
Nothing
else
Just $ withLoc $ TransitiveDependenciesMissing selectReason msgs
where
msgs = [
case r of
Unselectable u ->
TransitiveDependencyUnusable i u
UnselectableDependencyNotSelected ->
TransitiveDependencyNotSelected i locs
| (i, r) <- Map.toList unavailReasons
, let locs = case DeclIndex.lookupLoc i declIndex of
Nothing -> []
Just xs -> C.declLocsToList xs
]
isDeprecated :: Bool
isDeprecated = DeclIndex.entryToAvailability entry == C.Deprecated
{-------------------------------------------------------------------------------
Delayed traces
-------------------------------------------------------------------------------}
mkSuccessMessages ::
HasCallStack
=> C.DeclId
-> Success l ConstructTranslationUnit
-> [AnnMsg Select]
mkSuccessMessages declId success = concat [
fmap (mkAnnMsg . SelectDelayedParseMsg)
delayedParseMsgs
, fmap (mkAnnMsg . SelectDelayedPrepareReparseMsg)
delayedPrepareReparseMsgs
, fmap (mkAnnMsg . SelectDelayedReparseMacroExpansionsMsg)
delayedReparseMacroExpansionsMsgs
, fmap (mkAnnMsg . SelectDelayedTranslateTypesMsg)
delayedTranslateTypesMsgs
]
where
DeclIndex.Success _
delayedParseMsgs
delayedPrepareReparseMsgs
delayedReparseMacroExpansionsMsgs
delayedTranslateTypesMsgs = success
mkAnnMsg :: HasCallStack => SelectMsg -> AnnMsg Select
mkAnnMsg msg = withCallStack C.WithLocationInfo{
loc = C.declIdLocationInfo declId [success.decl.info.loc]
, msg = msg
}
getDelayedMsgsSelectionRoots :: HasCallStack => DeclIndex l -> [AnnMsg Select]
getDelayedMsgsSelectionRoots = concatMap (uncurry aux) . DeclIndex.toList
where
aux ::
HasCallStack
=> C.DeclId
-> Entry l
-> [AnnMsg Select]
aux declId = \case
UsableEntry e -> case e of
UsableSuccess success ->
mkSuccessMessages declId success
UsableExternal{} -> []
-- Parse messages are unavailable for squashed entries. We are OK with
-- this; instead we have issued a notice that the @typedef@ was squashed.
UsableSquashed x ->
List.singleton $ withCallStack C.WithLocationInfo{
loc = C.declIdLocationInfo declId [x.typedefLoc]
, msg = SelectMangleNamesSquashed x
}
UnusableEntry e -> case e of
UnusableReason loc s ->
List.singleton $ withCallStack C.WithLocationInfo{
loc = C.declIdLocationInfo declId [loc]
, msg = SelectUnusable s
}
UnusableConflict c ->
List.singleton $ withCallStack C.WithLocationInfo{
loc = C.declIdLocationInfo declId $
NonEmpty.toList $ C.conflictToList c
, msg = SelectConflict
}
getDelayedMsgsAdditionalSelectedTransDeps ::
HasCallStack
=> DeclIndex l
-> [AnnMsg Select]
getDelayedMsgsAdditionalSelectedTransDeps = concatMap (uncurry aux) . DeclIndex.toList
where
aux ::
HasCallStack
=> C.DeclId
-> Entry l
-> [AnnMsg Select]
aux declId = \case
UsableEntry e -> case e of
UsableSuccess success ->
mkSuccessMessages declId success
UsableExternal{} -> []
-- Parse messages are unavailable for squashed entries. We are OK with
-- this; instead we have issued a notice that the @typedef@ was squashed.
UsableSquashed x ->
[ withCallStack C.WithLocationInfo{
loc = C.declIdLocationInfo declId [x.typedefLoc]
, msg = SelectMangleNamesSquashed x
}
]
-- Messages for unusable transitive dependencies are already attached to
-- the traces of the reverse transitive dependencies.
UnusableEntry _ -> []
-- NOTE: We emit delayed BUG-level parse messages even for declarations that are
-- not selected. We do not have a test for this; please ensure delayed BUG-level
-- parse messages are emitted for all declarations also in the future.
getBugMsgsNotSelected :: HasCallStack => DeclIndex l -> [AnnMsg Select]
getBugMsgsNotSelected = concatMap (uncurry aux) . DeclIndex.toList
where
aux ::
HasCallStack
=> C.DeclId
-> Entry l
-> [AnnMsg Select]
aux declId = \case
UsableEntry e -> case e of
UsableSuccess success ->
let isBugLevel x = getDefaultLogLevel x == Bug
in filter isBugLevel $ mkSuccessMessages declId success
UsableExternal{} -> []
UsableSquashed{} -> []
UnusableEntry e -> case e of
UnusableReason loc reason ->
if isBug reason then
List.singleton $ withCallStack C.WithLocationInfo{
loc = C.declIdLocationInfo declId [loc]
, msg = SelectUnusable reason
}
else
[]
UnusableConflict{} -> []
isBug :: UnusableReason -> Bool
isBug = \case
UnusableUnavailable -> False
UnusableOmitted -> False
UnusableParseFailure err -> getDefaultLogLevel err == Bug
UnusableMangleNamesFailure err -> getDefaultLogLevel err == Bug
UnusableMacroTypecheckFailure err -> getDefaultLogLevel err == Bug
UnusableMacroResolutionFailure err -> getDefaultLogLevel err == Bug
-- | Does this selection-root message represent a macro that hs-bindgen dropped?
--
isDroppedMacro :: SelectMsg -> Bool
isDroppedMacro = \case
SelectUnusable reason -> case reason of
UnusableMacroTypecheckFailure _ -> True
UnusableMacroResolutionFailure _ -> True
-- A parse failure counts as a dropped macro iff it is a macro parse
-- failure.
UnusableParseFailure p -> isMacroParseFailure p
UnusableUnavailable -> False
UnusableOmitted -> False
UnusableMangleNamesFailure _ -> False
_otherwise -> False
where
isMacroParseFailure :: DelayedParseMsg -> Bool
isMacroParseFailure = \case
ParseMacroDefinitionNoMacroName{} -> True
ParseMacroEmpty{} -> False
ParseMacroErrorParse{} -> True
_otherwise -> False
{-------------------------------------------------------------------------------
Sort traces
-------------------------------------------------------------------------------}
-- | Location a trace message is sorted by
--
-- Only conflicting declarations carry more than one location; we sort by the
-- first.
msgLoc :: AnnMsg Select -> Maybe (SingleLoc C.DeclPath)
msgLoc msg = listToMaybe $ C.locationInfoLocs msg.traceMsg.loc
-- | Sort key of a trace message
--
-- The constructor order /is/ the specification: messages not attached to a
-- declaration sort to the back. Do not reorder.
data MsgSortKey =
MsgAt {
msgSource :: IncludeGraph.IncludeOrderIx
, msgLine :: Int
, msgColumn :: Int
}
| MsgNoLocation
deriving stock (Eq, Ord)
msgSortKey :: IncludeGraph.IncludeOrder -> AnnMsg Select -> MsgSortKey
msgSortKey order msg = case msgLoc msg of
Nothing -> MsgNoLocation
Just loc -> MsgAt {
msgSource = IncludeGraph.lookupIncludeOrder order (singleLocPath loc)
, msgLine = singleLocLine loc
, msgColumn = singleLocColumn loc
}
sortSelectMsgs :: IncludeGraph -> [AnnMsg Select] -> [AnnMsg Select]
sortSelectMsgs includeGraph msgs =
bugMsgs ++ List.sortOn (msgSortKey order) msgs
where
-- Compute the include order once.
order :: IncludeGraph.IncludeOrder
order = IncludeGraph.toIncludeOrder includeGraph
-- We do not know where to sort a message whose source is not in the include
-- graph, but a mis-sorted message is no reason to abort: report the bug and
-- sort the message to the back.
bugMsgs :: [AnnMsg Select]
bugMsgs = [
withCallStack C.WithLocationInfo{
loc = C.LocationUnavailable
, msg = SelectSourceNotInIncludeGraph path
}
| path <- Set.toList unknownPaths
]
unknownPaths :: Set RealPath
unknownPaths = Set.fromList [
path
| msg <- msgs
, Just loc <- [msgLoc msg]
, C.InHeader path <- [singleLocPath loc]
, IncludeGraph.lookupIncludeOrder order (C.InHeader path)
== IncludeGraph.NotInIncludeGraph
]
{-------------------------------------------------------------------------------
Apply the selection predicate
-------------------------------------------------------------------------------}
-- Match function to find selection roots.
type Match = C.DeclName -> SingleLoc C.DeclPath -> C.Availability -> Bool
-- | Limit the declaration index to those entries that match the select
-- predicate. Do not include anything external nor omitted.
selectDeclIndex :: DeclUseGraph -> Match -> DeclIndex l -> DeclIndex l
selectDeclIndex declUseGraph predicate declIndex =
DeclIndex.filter matchEntry declIndex
where
matchEntry :: C.DeclId -> Entry l -> Bool
matchEntry declId entry =
case entryInfo entry of
Nothing ->
-- External or omittted declarations have already been removed from
-- the list of declarations
False
Just (locs, availability) ->
if declId.isUnnamed
then matchUnnamed declId
else Foldable.any
(\loc -> predicate declId.name loc availability)
(C.declLocsToList locs)
matchDeclId :: C.DeclId -> Bool
matchDeclId declId =
case DeclIndex.lookupEntry declId declIndex of
Just entry -> matchEntry declId entry
Nothing -> panicPure $ "Unknown declaration ID: " ++ show declId
-- Extract info from 'Entry' needed to match against the selection predicate
--
-- Returns 'Nothing' for external or omitted declarations.
-- Returns 'Just _' for squashed declarations. Those can still be selected.
-- Returns multiple locations only for conflicts.
entryInfo :: Entry l -> Maybe (C.DeclLocs, C.Availability)
entryInfo entry = case entry of
UsableEntry UsableExternal{} ->
Nothing
UnusableEntry (UnusableReason _loc UnusableOmitted{}) ->
Nothing
_otherwise ->
Just (DeclIndex.entryToLoc entry, DeclIndex.entryToAvailability entry)
-- We match unnamed declarations based on their use sites.
--
-- Looking at the use sites means we treat unnamed declarations in a very
-- similar way to auxiliary declarations. It's however worth spelling out
-- why this is possible. There are two main classes of unnamed
-- declarations. The first is unnamed declarations inside of typedefs:
--
-- > typedef struct { .. } foo;
--
-- Selecting the typedef but not the struct makes no sense; selecting the
-- struct but not the typedef does, but is already captured by support for
-- squashing typedefs.
--
-- The second class is unnamed declarations inside structs or unions:
--
-- > struct outer {
-- > struct { .. } field1, field2;
-- > struct { .. } field3;
-- > }
-- >
--
-- Like for typedefs, it doesn't really make sense to select the outer
-- struct but not the inner struct. It could /in principle/ make sense to
-- select the inner but not the outer, but it seems a pretty rare use case,
-- and not a big deal if the outer struct will "come along" even if only the
-- inner struct is needed.
matchUnnamed :: C.DeclId -> Bool
matchUnnamed unnamed =
case DeclUseGraph.getUseSites declUseGraph unnamed of
(declId, _) :_ ->
-- The only way that unnamed declarations can have multiple use
-- sites is when multiple fields are defined together:
--
-- > struct foo {
-- > struct { .. } x, y, z;
-- > }
--
-- From the perspective of selection, any of these will do.
matchDeclId declId
[] ->
-- An unnamed declaration can have no use sites if those
-- dependencies were removed from the graph because the parent
-- declaration is opaqued using a prescriptive binding specification.
False