Page MenuHomePhabricator

D10728.diff
No OneTemporary

D10728.diff

diff --git a/scripts/phutil_symbols.php b/scripts/phutil_symbols.php
--- a/scripts/phutil_symbols.php
+++ b/scripts/phutil_symbols.php
@@ -76,30 +76,55 @@
$root = $tree->getRootNode();
$root->buildSelectCache();
-// -( Unsupported Constructs )------------------------------------------------
+// -( Namespace and Use resolution )------------------------------------------
$namespaces = $root->selectDescendantsOfType('n_NAMESPACE');
+$file_namespace = '';
foreach ($namespaces as $namespace) {
- phutil_fail_on_unsupported_feature($namespace, $path, pht('namespaces'));
+ if ($file_namespace) {
+ // Make no attempt to support multiple namespaces per file - this is silly,
+ // overly complicated, and unused in practice
+ phutil_fail_on_unsupported_feature($namespace, $path, pht('namespaces'));
+ } else {
+ $file_namespace = $namespace->getChildByIndex(0)->getConcreteString().'\\';
+ }
}
+// Map imported classes as rule => class
+$imports = array();
$uses = $root->selectDescendantsOfType('n_USE');
-foreach ($namespaces as $namespace) {
- phutil_fail_on_unsupported_feature(
- $namespace, $path, pht('namespace `use` statements'));
-}
-
-$possible_traits = $root->selectDescendantsOfType('n_CLASS_DECLARATION');
-foreach ($possible_traits as $possible_trait) {
- $attributes = $possible_trait->getChildByIndex(0);
- // Can't use getChildByIndex here because not all classes have attributes
- foreach ($attributes->getChildren() as $attribute) {
- if (strtolower($attribute->getConcreteString()) === 'trait') {
- phutil_fail_on_unsupported_feature($possible_trait, $path, pht('traits'));
+foreach ($uses as $use) {
+ if (2 !== count($use->getChildren())) {
+ // This is the "outer" T_USE statement; the actual contents will get picked
+ // up below
+ continue;
+ }
+ $import_from = $use->getChildByIndex(0)->getConcreteString();
+ $import_as = $use->getChildByIndex(1)->getConcreteString();
+ // If something is not explicitly imported with an alias, the alias is
+ // implied to be the deepest sub-namespace.
+ if (!$import_as) {
+ if (false === strpos($import_from, '\\')) { // Global NS
+ $import_as = $import_from;
+ } else {
+ $import_as = substr($import_from, strrpos($import_from, '\\') + 1);
}
}
+ $imports[$import_as] = $import_from;
}
-
+// It's possible to explicitly request the current namespace, although it's
+// implied by using anything other than a fully qualified name. Add it (without
+// the trailing backslash) to the import rules. In effect:
+// namespace Foo\Bar;
+// use Foo\Bar as namespace;
+// As a result, the two are functionally identical:
+// new namespace\Foo();
+// new Foo();
+$imports['namespace'] = substr($file_namespace, 0, -1);
+
+// TODO: "use function foo"
+// TODO: "use function foo as bar"
+// TODO: "use const FOO"
// -( Marked Externals )------------------------------------------------------
@@ -228,14 +253,36 @@
// Find classes declared by this file.
-// This is "class X ... { ... }".
+// This is "class X ... { ... }" or "trait X ... { ... }".
$classes = $root->selectDescendantsOfType('n_CLASS_DECLARATION');
foreach ($classes as $class) {
+ $class_attributes = $class->getChildByIndex(0);
+ $is_trait = false;
+ foreach ($class_attributes->getChildren() as $attribute) {
+ if (strtolower($attribute->getConcreteString() === 'trait')) {
+ $is_trait = true;
+ }
+ }
$class_name = $class->getChildByIndex(1);
+ $type = $is_trait ? 'trait' : 'class';
$have[] = array(
- 'type' => 'class',
+ 'type' => $type,
'symbol' => $class_name,
);
+ // Trait use in trait and class declaration
+ $trait_use_statements = $class->selectDescendantsOfType('n_TRAIT_USE');
+ foreach ($trait_use_statements as $trait_use_statement) {
+ foreach ($trait_use_statement->getChildren() as $used_trait) {
+ // n_TRAIT_ADAPTATION_LIST - conflict resolution OR n_EMPTY
+ if ($used_trait->getTypeName() !== 'n_CLASS_NAME') {
+ continue;
+ }
+ $need[] = array(
+ 'type' => 'trait',
+ 'symbol' => $used_trait,
+ );
+ }
+ }
}
@@ -268,7 +315,10 @@
);
// Track all 'extends' in the extension map.
- $xmap[$class_name][] = $parent->getConcreteString();
+ $xmap['class'][$class_name][] = array(
+ $parent->getConcreteString(),
+ 'class',
+ );
}
}
@@ -397,7 +447,10 @@
);
// Track 'class ... implements' in the extension map.
- $xmap[$class_name][] = $interface->getConcreteString();
+ $xmap['class'][$class_name][] = array(
+ $interface->getConcreteString(),
+ 'interface',
+ );
}
}
@@ -415,7 +468,10 @@
);
// Track 'interface ... extends' in the extension map.
- $xmap[$interface_name][] = $parent->getConcreteString();
+ $xmap['interface'][$interface_name][] = array(
+ $parent->getConcreteString(),
+ 'interface',
+ );
}
}
@@ -425,7 +481,7 @@
$declared_symbols = array();
foreach ($have as $key => $spec) {
- $name = $spec['symbol']->getConcreteString();
+ $name = $file_namespace.$spec['symbol']->getConcreteString();
$declared_symbols[$spec['type']][$name] = $spec['symbol']->getOffset();
}
@@ -437,6 +493,7 @@
}
$type = $spec['type'];
+ $name = phutil_resolve_namespace($name, $type, $file_namespace, $imports);
foreach (explode('/', $type) as $libtype) {
if (!$show_all) {
if (!empty($externals[$libtype][$name])) {
@@ -452,6 +509,13 @@
// We declare this symbol, so don't treat it as a requirement.
continue 2;
}
+ if ($libtype === 'function' &&
+ !empty($declared_symbols[$libtype][$file_namespace.$name])) {
+ // Function calls can't be resolved except at runtime. If the file has
+ // a namespace, try the function in the current namespace. If found, the
+ // function was declared in this file (as above) and isn't a requirement.
+ continue 2;
+ }
}
if (!empty($required_symbols[$type][$name])) {
// Report only the first use of a symbol, since reporting all of them
@@ -461,10 +525,33 @@
$required_symbols[$type][$name] = $spec['symbol']->getOffset();
}
+$parsed_xmap = array();
+foreach ($xmap as $type => $contents) {
+ foreach ($contents as $declaration => $requirements) {
+ $parsed_declaration = phutil_resolve_namespace($declaration,
+ $type,
+ $file_namespace,
+ $imports);
+ foreach ($requirements as $requirement) {
+ list($req_name, $req_type) = $requirement;
+ $parsed_requirement = phutil_resolve_namespace($req_name,
+ $req_type,
+ $file_namespace,
+ $imports);
+ if (!empty($declared_symbols[$req_type][$parsed_requirement])) {
+ // Declared in this file, don't treat as a requirement.
+ continue;
+ }
+ $parsed_xmap[$parsed_declaration][] = $parsed_requirement;
+ }
+ }
+}
+
+
$result = array(
'have' => $declared_symbols,
'need' => $required_symbols,
- 'xmap' => $xmap,
+ 'xmap' => $parsed_xmap,
);
@@ -551,3 +638,44 @@
'interface' => array_fill_keys($builtin['interfaces'], true),
);
}
+
+/**
+ * Resolve the fully qualified identifier of a provided name based on the
+ * file's namespace and imported symbols
+ *
+ * @param string The symbol name as it exists in code form
+ * @param string The symbol type (class, function, interface, etc)
+ * @param string The current namespace
+ * @param map<string, string> Import rules: imported_as => identifier
+ *
+ * @return string Fully qualified identifier name
+ */
+function phutil_resolve_namespace($name, $type, $file_ns, array $imports) {
+ if (false === $separator = strpos($name, '\\')) { // Unqualified name
+ if ($type === 'function') {
+ // Do nothing - resolving function usage can't be done outside of runtime
+ // since it depends on the symbol table as it exists at execution
+ } else {
+ // Automatically resolve special keywords
+ if (in_array($name, array('self', 'static', 'parent'))) {
+ // Do nothing
+ } else if (isset($imports[$name])) {
+ $name = $imports[$name];
+ } else {
+ $name = $file_ns.$name;
+ }
+ }
+ } else { // Qualified or fully qualified name
+ if ($separator === 0) { // Fully qualified name
+ $name = substr($name, 1);
+ } else { // Qualified name
+ $base = substr($name, 0, $separator);
+ if (isset($imports[$base])) { // Check the "use" list
+ $name = $imports[$base].substr($name, $separator);
+ } else { // Not hit in "use", it's the current NS
+ $name = $file_ns.$name;
+ }
+ }
+ }
+ return $name;
+}
diff --git a/src/moduleutils/PhutilLibraryMapBuilder.php b/src/moduleutils/PhutilLibraryMapBuilder.php
--- a/src/moduleutils/PhutilLibraryMapBuilder.php
+++ b/src/moduleutils/PhutilLibraryMapBuilder.php
@@ -343,7 +343,16 @@
foreach ($symbol_map as $file => $info) {
foreach ($info['have'] as $type => $symbols) {
foreach ($symbols as $symbol => $declaration) {
- $lib_type = ($type == 'interface') ? 'class' : $type;
+ // Treat interfaces and traits provided as classes
+ switch ($type) {
+ case 'interface':
+ case 'trait':
+ $lib_type = 'class';
+ break;
+ default:
+ $lib_type = $type;
+ break;
+ }
if (!empty($library_map[$lib_type][$symbol])) {
$prior = $library_map[$lib_type][$symbol];
throw new Exception(

File Metadata

Mime Type
text/plain
Expires
Thu, Nov 21, 7:33 PM (3 h, 56 m)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6771883
Default Alt Text
D10728.diff (9 KB)

Event Timeline