Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F14072108
D10728.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
9 KB
Referenced Files
None
Subscribers
None
D10728.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D10728: Allow `arc liberate` to handle traits and namespaces
Attached
Detach File
Event Timeline
Log In to Comment