Skip to content

Instantly share code, notes, and snippets.

@Wilfred
Forked from fredemmott/XHP ns token.md
Created October 9, 2019 19:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Wilfred/446937c2cd85258fdc93157d7dfc04d7 to your computer and use it in GitHub Desktop.
Save Wilfred/446937c2cd85258fdc93157d7dfc04d7 to your computer and use it in GitHub Desktop.
XHP ns token

Migration

These strategies are based on XHP being practically unusable in combination with namespace; assumptions are:

  • all code that uses XHP is in the root namespace
  • all code that defines XHP is in the root namespace

Required

  • temporarily change XHPClassNameToken to mangle differently, so that :foo:bar and \foo\bar (or class \foo\xhp_bar, depending on proposal) are considered equivalent
  • temporarily change allow class :foo:bar to define class \foo\bar {} (or class \foo\xhp_bar)
    • this does not affect resolution of names within the definition (example below)
  • once that's been supported for $n (>=1) releases, migrate with hackast/hhast, and remove old behavior

Resolution example

// TEMPORARY: this defines class \foo\bar or class \foo\xhp_bar
class :foo:bar {
  attribute MyAttr foo; // this references \MyAttr - *NOT* \foo\MyAttr
}

This is likely straightforward to implement, as explicitly declaring class Foo\Bar {} was accidentally supported in the initial release of HackC until explicitly banned.

Optional

  • Provide a flag to entirely preserve the existing "only works in root namespace" behavior and mangling. Deployments with this flag enabled would be unable to combine XHP with namespaces.
  • Never delete the flag to allow class :foo:bar -> class foo\bar {} (or class foo\xhp_bar {} mangling
    • ban this syntax outside of the root namespace
    • removes need for mass migration of existing code, but allows use of namespaced XHP for new use cases
    • not completely transparent: error messages would report class foo\bar instead of foo:bar
    • alternatively, when rendering class names in error messages, render foo:bar if the class is an XHP class; this trades consistency for existing code against consistency for new namespace-using XHP code (e.g. namespace MyFeature { class :foo {} } would be reported as :MyFeature:foo

XHP Namespaces

I am not addressing migration/compatibility in this post. This is addressed in the documents linked in 'previous context'

Previous context

TL;DR:

It is currently impossible to use XHP with namespaces:

  • class :foo extends \:x:element{} is a parse error
  • <\foo:bar> is a parse error (and confusing with </foo:bar>

<\foo\bar>baz</\foo\bar> is the obvious way, but </\foo\bar> and <\foo\bar /> are unreadable and annoying to type. Aim for : to be used as a replacement for \ in XHP contexts.

Remaining problem

How should use type and similar work for XHP? I see this as two strongly coupled questions:

  1. how should XHP names be mangled?
  2. what namespace separator should be used in what contexts? Contexts include:
    • type specifier: function foo(:bar:baz $xhp) (current)
    • xhp constructor calls: <bar:baz> (current)
    • imports: use type :bar:baz; use namespace :foo; use namespace :foo:bar; (proposal 1)

With an example:

namespace MyNS {
  class MyClass {}
  class :myxhpclass {}
}

namespace {
  use type MyNS\MyClass;
  // and what for :myxhpclass?
  use type MyNS\{MyClass, /* ? */ };
}

Current situation

  1. :foo:bar-baz::class is xhp_foo__bar_baz
  2. no namespace separator works correctly with XHP

Proposal 1: : always for XHP, \ always for non-XHP

  1. :foo:bar-baz::class is foo\xhp_bar_baz
  2. anything relating to XHP uses :; this keeps XHP class names seeming like they have another symbol table (i.e. class :foo doesn't conflict with class foo)
    • type specifier: function foo(:bar:baz $xhp)
    • XHP constructor calls: <bar:baz> if use namespace bar is present or bar is relative to the current namespace, <:bar:baz> for relative to the root namespace. </:bar:baz> to close.
    • imports: use type :bar:baz;, `use namespace :bar:baz;
namespace foo {
  class MyClass {}
  class :MyXHPClass {}
}

namespace foo\bar {
  class MyOtherClass;
  class :MyOtherXHPClass {}
}

namespace {
  use type foo\MyXHPClass;
  $_ = <MyXHPClass />; // tries to instantiate \xhp_bar_baz; not affected by non-XHP `use type`

  use type :foo:MyXHPClass;
  $_ = <MyXHPClass />; // \foo\xhp_MyXHPClass

  use type foo\{MyClass, MyXHPClass}; // second class is not defined.

  use namespace foo\bar;
  new bar\MyOtherClass(); // works
  $_ = <bar:MyOtherXHPClass />; // unaffected by use ns; this is \bar\xhp_MyOtherXHPClass;

  use namespace :foo:bar;
  <bar:MyOtherXHPClass>; // \foo\bar\xhp_MyOtherXHPClass

  // fully-qualified
  $_ = <:foo:MyXHPClass />;
  $_ = <:foo:bar:MyOtherXHPClass />;


  // Problem?
  use type :foo:MyXHPClass;
  function takes_foo_MyXHPClass(:MyXHPClass $_); // ?
  function takes_root_MyXCHPClass(::MyXHPClass $_); // ?
}

Notes

  • through mangling, effectively keeps XHP and non-XHP classes in non-colliding name tables (unless you call non-XHP classes xhp_foo...)
  • using the same symbol to specify 'this is XHP class' and to qualify names leads to confusing cases like the last few lines of the example

Proposal 2: interchangeable always

Allow : in all contexts where \ is a namespace separator.

  1. :foo:bar-baz::class is \foo\bar_baz
  2. anything relating to XHP uses :; this keeps XHP class names seeming like they have another symbol table (i.e. class :foo doesn't conflict with class foo)
    • type specifier: function foo(:bar:baz $xhp) or function foo(\bar\baz $xhp) or function foo(:bar\baz $xhp)
    • XHP constructor calls: <bar:baz> or <bar\baz
    • imports: use type :bar:baz;, use namespace :bar:baz;, use type \bar\baz etc.

Example

use type :foo:bar;
new bar(); // creates a new \foo\bar
$_ = <bar />; // also creates a new \foo\bar

 // no problem:
use type :foo:MyXHPClass;
function takes_foo_MyXHPClass(MyXHPClass $_);
function takes_root_MyXCHPClass(\MyXHPClass $_);
function takes_root_MyXCHPClass(:MyXHPClass $_); // same thing

Notes

  • completely unified XHP and non-XHP namespaces
  • class :foo {} and class foo {} define a class with the same name and collide
  • requires - to be permitted in type names (though may be banned in non-XHP decls)
  • return types are slightly weird: function foo(): :bar:baz; this already exists, but could become more prevalent if not limited to returning XHP, which is rare
  • ternary operators also weird
  • unknowns: other uses of : token
  • allows users to decide (3) or (4) ... or whatever else they want and enforce with linters

Proposal 3: \ always, except for : in XHP constructor calls

  1. :foo:bar-baz::class is invalid as :foo:bar-baz is not a type. an XHP class bar-baz in the foo namespace is foo\bar_baz.
  2. XHP constructor calls use :; all other contexts use \.
    • type specifier: function foo(\bar\baz $xhp)
    • XHP constructor calls: <bar:baz> if use namespace bar is present or bar is relative to the current namespace, <:bar:baz> for relative to the root namespace.
    • imports: use type \bar\baz;, use namespace bar\baz;

Example

namespace foo {
  class MyClass {}
  class :MyXHPClass {}
}

namespace foo\bar {
  class MyOtherClass;
  class :MyOtherXHPClass {}
}

namespace {
  use type foo\MyXHPClass;
  $_ = <MyXHPClass />; this is fine

  use type :foo:MyXHPClass; // parse error

  use type foo\{MyClass, MyXHPClass};
  $_ = new MyClass(); // fine
  $_ = new <MyXHPClass />; // fine   

  use namespace foo\bar;
  $_ = new bar\MyOtherClass(); // works
  $_ = <bar:MyOtherXHPClass />; // works

  use namespace :foo:bar; // parse error

  // fully-qualified
  $_ = <:foo:MyXHPClass />;
  $_ = <:foo:bar:MyOtherXHPClass />;

  // no problem:
  use type Foo\MyXHPClass;
  function takes_foo_MyXHPClass(MyXHPClass $_);
  function takes_root_MyXCHPClass(\MyXHPClass $_);
  function takes_root_MyXCHPClass(:MyXHPClass $_); // parse error
}

Notes

  • completely unifies XHP and non-XHP Hack namespaces
  • class :foo {} and class foo {} define a class with the same name and collide
  • requires - to be permitted in type names (though may be banned in non-XHP decls)

Proposal 4: \ in XHP

  1. :foo:bar-baz::class is invalid as :foo:bar-baz is not a type. an XHP class bar-baz in the foo namespace is \foo\bar_baz.
  2. \ everyehere:
    • type specifier: function foo(\bar\baz $xhp)
    • XHP constructor calls: <bar\baz> if use namespace bar is present or bar is relative to the current namespace, <\bar\baz> for relative to the root namespace.
    • imports: use type \bar\baz;, use namespace bar\baz;

Notes

  • largely equivalent to (3):
    • only difference is the token used in XHP constructor calls is not dicated
    • (4) is more consistent, but has more 'eww'
  • the main argument against this is "eww </\foo\bar> and <\foo\bar /> are icky". That said, everyone strongly agrees with that :p
  • completely unifies XHP and non-XHP Hack namespaces
  • class :foo {} and class foo {} define a class with the same name and collide
  • requires - to be permitted in type names (though may be banned in non-XHP decls)

Related question: replace class :foo with xhp class foo

Every proposal except for (1) require that a normal class called foo and an xhp class called foo in the same namespace collide - i.e. they require that use type foo\bar; works regardless of if bar if an XHP or non-XHP class.

It is likely to be confusing that class :foo and class foo collide; this is much clearer for xhp class foo {} and class foo {}.

Some marker is needed at the declaration, for example:

  • to autogenerate the constructor
  • to permit the use of attribute and children declarations
  • to permit the use of - in type names (or mangle it to _ like we currently do)

Suggested path

  • xhp class foo {}
  • proposal 3: : for XHP constructor calls, \ everywhere else.
  • these can/should be independently landed changes, but should aim for the period of time when we have one without the other to be as small as possible

Rationale

  • xhp class rationale covered in previous section
  • (4) (\ everywhere) is largely equivalent to (3) (: at XHP constructor calls, \ everywhere else), but less readable and with more churn needed. Drop.
  • (1) has ambiguities in type names. Drop.
  • This leaves (2) (interchangeable tokens) and (3). Go for 3 as:
    • we generally prefer having one way to do things; if there are multiple, we'd still need to decide on one approach to use consistently in FB codebases and docs.hhvm.com. Likely that whatever we use there will be the de-facto standard.
    • smaller chance of side-effects
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment