diff --git a/scripts/phutil_symbols.php b/scripts/phutil_symbols.php --- a/scripts/phutil_symbols.php +++ b/scripts/phutil_symbols.php @@ -138,7 +138,8 @@ $have = array(); // For symbols we declare. $need = array(); // For symbols we use. -$xmap = array(); // For extended classes and implemented interfaces. +$xmap = array(); // For extended classes. +$imap = array(); // For implemented interfaces. // -( Functions )------------------------------------------------------------- @@ -397,7 +398,7 @@ ); // Track 'class ... implements' in the extension map. - $xmap[$class_name][] = $interface->getConcreteString(); + $imap[$class_name][] = $interface->getConcreteString(); } } @@ -465,6 +466,7 @@ 'have' => $declared_symbols, 'need' => $required_symbols, 'xmap' => $xmap, + 'imap' => $imap, ); diff --git a/src/__phutil_library_init__.php b/src/__phutil_library_init__.php --- a/src/__phutil_library_init__.php +++ b/src/__phutil_library_init__.php @@ -17,7 +17,7 @@ try { $loader = new PhutilSymbolLoader(); $symbols = $loader - ->setType('class') + ->setType(array('class', 'interface')) ->setName($class_name) ->selectAndLoadSymbols(); diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -255,7 +255,6 @@ 'PhutilParserGeneratorException' => 'parser/generator/exception/PhutilParserGeneratorException.php', 'PhutilParserGeneratorTestCase' => 'parser/__tests__/PhutilParserGeneratorTestCase.php', 'PhutilPayPalAPIFuture' => 'future/paypal/PhutilPayPalAPIFuture.php', - 'PhutilPerson' => 'internationalization/PhutilPerson.php', 'PhutilPersonTest' => 'internationalization/__tests__/PhutilPersonTest.php', 'PhutilPersonaAuthAdapter' => 'auth/PhutilPersonaAuthAdapter.php', 'PhutilPhabricatorAuthAdapter' => 'auth/PhutilPhabricatorAuthAdapter.php', @@ -266,7 +265,6 @@ 'PhutilProxyException' => 'error/PhutilProxyException.php', 'PhutilPygmentsSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilPygmentsSyntaxHighlighter.php', 'PhutilPythonFragmentLexer' => 'lexer/PhutilPythonFragmentLexer.php', - 'PhutilQsprintfInterface' => 'xsprintf/PhutilQsprintfInterface.php', 'PhutilQueryStringParser' => 'parser/PhutilQueryStringParser.php', 'PhutilQueryStringParserTestCase' => 'parser/__tests__/PhutilQueryStringParserTestCase.php', 'PhutilRainbowSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilRainbowSyntaxHighlighter.php', @@ -306,7 +304,6 @@ 'PhutilRope' => 'utils/PhutilRope.php', 'PhutilRopeTestCase' => 'utils/__tests__/PhutilRopeTestCase.php', 'PhutilSafeHTML' => 'markup/PhutilSafeHTML.php', - 'PhutilSafeHTMLProducerInterface' => 'markup/PhutilSafeHTMLProducerInterface.php', 'PhutilSafeHTMLTestCase' => 'markup/__tests__/PhutilSafeHTMLTestCase.php', 'PhutilSaturateStdoutDaemon' => 'daemon/torture/PhutilSaturateStdoutDaemon.php', 'PhutilServiceProfiler' => 'serviceprofiler/PhutilServiceProfiler.php', @@ -492,13 +489,32 @@ 'xsprintf_terminal' => 'xsprintf/tsprintf.php', 'xsprintf_uri' => 'xsprintf/urisprintf.php', ), - 'xmap' => array( - 'AASTNode' => 'Phobject', + 'imap' => array( 'AASTNodeList' => array( - 'Phobject', 'Countable', 'Iterator', ), + 'AphrontDatabaseConnection' => 'PhutilQsprintfInterface', + 'FutureIterator' => 'Iterator', + 'LinesOfALarge' => 'Iterator', + 'Phobject' => 'Iterator', + 'PhutilArray' => array( + 'Countable', + 'ArrayAccess', + 'Iterator', + ), + 'PhutilBufferedIterator' => 'Iterator', + 'PhutilChunkedIterator' => 'Iterator', + 'PhutilPersonTest' => 'PhutilPerson', + ), + 'interface' => array( + 'PhutilPerson' => 'internationalization/PhutilPerson.php', + 'PhutilQsprintfInterface' => 'xsprintf/PhutilQsprintfInterface.php', + 'PhutilSafeHTMLProducerInterface' => 'markup/PhutilSafeHTMLProducerInterface.php', + ), + 'xmap' => array( + 'AASTNode' => 'Phobject', + 'AASTNodeList' => 'Phobject', 'AASTToken' => 'Phobject', 'AASTTree' => 'Phobject', 'AbstractDirectedGraph' => 'Phobject', @@ -509,10 +525,7 @@ 'AphrontConnectionLostQueryException' => 'AphrontRecoverableQueryException', 'AphrontConnectionQueryException' => 'AphrontQueryException', 'AphrontCountQueryException' => 'AphrontQueryException', - 'AphrontDatabaseConnection' => array( - 'Phobject', - 'PhutilQsprintfInterface', - ), + 'AphrontDatabaseConnection' => 'Phobject', 'AphrontDatabaseTransactionState' => 'Phobject', 'AphrontDeadlockQueryException' => 'AphrontRecoverableQueryException', 'AphrontDuplicateKeyQueryException' => 'AphrontQueryException', @@ -543,10 +556,7 @@ 'FilesystemException' => 'Exception', 'FilesystemTestCase' => 'PhutilTestCase', 'Future' => 'Phobject', - 'FutureIterator' => array( - 'Phobject', - 'Iterator', - ), + 'FutureIterator' => 'Phobject', 'FutureIteratorTestCase' => 'PhutilTestCase', 'FutureProxy' => 'Future', 'HTTPFuture' => 'BaseHTTPFuture', @@ -558,10 +568,7 @@ 'HTTPFutureTransportResponseStatus' => 'HTTPFutureResponseStatus', 'HTTPSFuture' => 'BaseHTTPFuture', 'ImmediateFuture' => 'Future', - 'LinesOfALarge' => array( - 'Phobject', - 'Iterator', - ), + 'LinesOfALarge' => 'Phobject', 'LinesOfALargeExecFuture' => 'LinesOfALarge', 'LinesOfALargeExecFutureTestCase' => 'PhutilTestCase', 'LinesOfALargeFile' => 'LinesOfALarge', @@ -572,7 +579,6 @@ 'PhageAgentTestCase' => 'PhutilTestCase', 'PhagePHPAgent' => 'Phobject', 'PhagePHPAgentBootloader' => 'PhageAgentBootloader', - 'Phobject' => 'Iterator', 'PhobjectTestCase' => 'PhutilTestCase', 'PhutilAPCKeyValueCache' => 'PhutilKeyValueCache', 'PhutilAWSEC2Future' => 'PhutilAWSFuture', @@ -590,12 +596,7 @@ 'PhutilArgumentSpecificationTestCase' => 'PhutilTestCase', 'PhutilArgumentUsageException' => 'PhutilArgumentParserException', 'PhutilArgumentWorkflow' => 'Phobject', - 'PhutilArray' => array( - 'Phobject', - 'Countable', - 'ArrayAccess', - 'Iterator', - ), + 'PhutilArray' => 'Phobject', 'PhutilArrayTestCase' => 'PhutilTestCase', 'PhutilArrayWithDefaultValue' => 'PhutilArray', 'PhutilAsanaAuthAdapter' => 'PhutilOAuthAuthAdapter', @@ -609,10 +610,7 @@ 'PhutilBitbucketAuthAdapter' => 'PhutilOAuth1AuthAdapter', 'PhutilBootloaderException' => 'Exception', 'PhutilBritishEnglishLocale' => 'PhutilLocale', - 'PhutilBufferedIterator' => array( - 'Phobject', - 'Iterator', - ), + 'PhutilBufferedIterator' => 'Phobject', 'PhutilBufferedIteratorTestCase' => 'PhutilTestCase', 'PhutilBugtraqParser' => 'Phobject', 'PhutilBugtraqParserTestCase' => 'PhutilTestCase', @@ -623,10 +621,7 @@ 'PhutilChannel' => 'Phobject', 'PhutilChannelChannel' => 'PhutilChannel', 'PhutilChannelTestCase' => 'PhutilTestCase', - 'PhutilChunkedIterator' => array( - 'Phobject', - 'Iterator', - ), + 'PhutilChunkedIterator' => 'Phobject', 'PhutilChunkedIteratorTestCase' => 'PhutilTestCase', 'PhutilClassMapQuery' => 'Phobject', 'PhutilCodeSnippetContextFreeGrammar' => 'PhutilContextFreeGrammar', @@ -762,10 +757,7 @@ 'PhutilParserGeneratorException' => 'Exception', 'PhutilParserGeneratorTestCase' => 'PhutilTestCase', 'PhutilPayPalAPIFuture' => 'FutureProxy', - 'PhutilPersonTest' => array( - 'Phobject', - 'PhutilPerson', - ), + 'PhutilPersonTest' => 'Phobject', 'PhutilPersonaAuthAdapter' => 'PhutilAuthAdapter', 'PhutilPhabricatorAuthAdapter' => 'PhutilOAuthAuthAdapter', 'PhutilPhtTestCase' => 'PhutilTestCase', diff --git a/src/moduleutils/PhutilLibraryMapBuilder.php b/src/moduleutils/PhutilLibraryMapBuilder.php --- a/src/moduleutils/PhutilLibraryMapBuilder.php +++ b/src/moduleutils/PhutilLibraryMapBuilder.php @@ -22,7 +22,7 @@ const LIBRARY_MAP_VERSION = 2; const SYMBOL_CACHE_VERSION_KEY = '__symbol_cache_version__'; - const SYMBOL_CACHE_VERSION = 11; + const SYMBOL_CACHE_VERSION = 12; /* -( Mapping libphutil Libraries )---------------------------------------- */ @@ -278,12 +278,15 @@ * Build a map of all source files in a library to hashes of their content. * Returns an array like this: * - * array( - * 'src/parser/ExampleParser.php' => '60b725f10c9c85c70d97880dfe8191b3', - * // ... - * ); + * ```lang=php + * array( + * 'src/parser/ExampleParser.php' => '60b725f10c9c85c70d97880dfe8191b3', + * // ... + * ); + * ``` * * @return dict Map of library-relative paths to content hashes. + * * @task source */ private function loadSourceFileMap() { @@ -335,22 +338,25 @@ * * @param dict Symbol analysis of all source files. * @return dict Library map. + * * @task source */ private function buildLibraryMap(array $symbol_map) { $library_map = array( 'class' => array(), + 'interface' => array(), 'function' => array(), + 'xmap' => array(), + 'imap' => array(), ); // Detect duplicate symbols within the library. foreach ($symbol_map as $file => $info) { foreach ($info['have'] as $type => $symbols) { foreach ($symbols as $symbol => $declaration) { - $lib_type = ($type == 'interface') ? 'class' : $type; - if (!empty($library_map[$lib_type][$symbol])) { - $prior = $library_map[$lib_type][$symbol]; + if (!empty($library_map[$type][$symbol])) { + $prior = $library_map[$type][$symbol]; throw new Exception( pht( "Definition of %s '%s' in file '%s' duplicates prior ". @@ -361,10 +367,12 @@ $file, $prior)); } - $library_map[$lib_type][$symbol] = $file; + $library_map[$type][$symbol] = $file; } } + $library_map['xmap'] += $info['xmap']; + $library_map['imap'] += $info['imap']; } // Simplify the common case (one parent) to make the file a little easier @@ -374,6 +382,11 @@ $library_map['xmap'][$class] = reset($extends); } } + foreach ($library_map['imap'] as $class => $implements) { + if (count($implements) == 1) { + $library_map['imap'][$class] = reset($implements); + } + } // Sort the map so it is relatively stable across changes. foreach ($library_map as $key => $symbols) { @@ -388,7 +401,7 @@ /** * Write a finalized library map. * - * @param dict Library map structure to write. + * @param dict Library map structure to write. * @return void * * @task source diff --git a/src/symbols/PhutilSymbolLoader.php b/src/symbols/PhutilSymbolLoader.php --- a/src/symbols/PhutilSymbolLoader.php +++ b/src/symbols/PhutilSymbolLoader.php @@ -174,11 +174,12 @@ } if ($this->type) { - $types = array($this->type); + $types = (array)$this->type; } else { $types = array( 'class', 'function', + 'interface', ); } @@ -193,12 +194,6 @@ foreach ($libraries as $library) { $map = $bootloader->getLibraryMap($library); foreach ($types as $type) { - if ($type == 'interface') { - $lookup_map = $map['class']; - } else { - $lookup_map = $map[$type]; - } - // As an optimization, we filter the list of candidate symbols in // several passes, applying a name-based filter first if possible since // it is highly selective and guaranteed to match at most one symbol. @@ -210,9 +205,9 @@ if ($this->name) { // If we have a name filter, just pick the matching name out if it // exists. - if (isset($lookup_map[$this->name])) { + if (isset($map[$type][$this->name])) { $filtered_map = array( - $this->name => $lookup_map[$this->name], + $this->name => $map[$type][$this->name], ); } else { $filtered_map = array(); @@ -220,13 +215,13 @@ } else if ($names !== null) { $filtered_map = array(); foreach ($names as $name => $ignored) { - if (isset($lookup_map[$name])) { - $filtered_map[$name] = $lookup_map[$name]; + if (isset($map[$type][$name])) { + $filtered_map[$name] = $map[$type][$name]; } } } else { // Otherwise, start with everything. - $filtered_map = $lookup_map; + $filtered_map = $map[$type]; } if ($this->pathPrefix) { @@ -320,7 +315,7 @@ public function loadObjects(array $argv = array()) { $symbols = $this ->setConcreteOnly(true) - ->setType('class') + ->setType(array('class', 'interface')) ->selectAndLoadSymbols(); $objects = array();