Skip to content

Instantly share code, notes, and snippets.

@fredemmott
Last active October 22, 2019 20:16
Show Gist options
  • Save fredemmott/7316287e3fb9cc43b312b3e51c155ba5 to your computer and use it in GitHub Desktop.
Save fredemmott/7316287e3fb9cc43b312b3e51c155ba5 to your computer and use it in GitHub Desktop.
XHP NS, post design review

Context

Except for the 'changes/decisions' section, this document aims to be standalone - though without going into rationale of things that have been agreed on.

For detailed rationale and alternatives, see https://gist.github.com/fredemmott/9db26f3d14652a510d56327c956f4a57 and comments

Changes/decisions

  1. Defining a class in a sub-namespace (class :foo:bar/xhp class foo:bar) is no longer intended to be temporary
    • avoid proliferation of fully-qualified names and explicit use namespace etc
    • this will not be permitted for non-XHP classes, e.g.:
      • xhp class foo:bar {} is permitted
      • class foo:bar {} is not
      • class foo\bar {} is not
  2. Defining a class in a sub-namespace is permitted in any namespace, not just the root namespace
    • allows new use cases of XHP to be largely defined in a consistent way with existing uses of (1) in the same codebase
  3. xhp class foo:bar instead of class :foo:bar : make it so that : is only a namespace separator, not a sigil
    • avoids most cases of problems like 'does :foo:bar mean \foo\bar or namespace\foo\bar?', especially in declarations
  4. ban - in XHP class names: use explicit _ instead (which it currently mangles to).
    • in combination with : just being a namespace separator, this entirely removes mangling from XHP
    • consistent with non-XHP classes
    • consistent with JSX/React

Open questions

  1. When exactly should : be permitted? Goal is "XHP contexts"
    • definitely: XHP constructor calls ($_ = <HERE />)
    • definitely: XHP class names at declarations (xhp class HERE {})
    • undecided: XHP parents: xhp class foo extends HERE {}
      • relates to (3) - namespace MyFeature { class :foo:bar extends :x:element {} } : needs to be \x\element
    • undecided: XHP children statements: xhp class foo { children (HERE *); }
    • undecided: other uses of XHP as a type - foo(:bar:baz $_), foo ? :bar:baz : qux
  2. Is :foo:bar -> foo\bar a safe codemod excluding those situations?
    • ->:foo would need to stop being parsed as ->{XHP_Class_Name}
    • chilren cases (children(:foo:bar*) includes a postfix unary expression) are complicated
  3. If we need to keep :foo:bar supported in places other than XHP class declarations, how do we disambiguate between \foo\bar and namespace\foo\bar ?
    • :foo:bar vs ::foo:bar ?
    • do we have real problems along the lines of foo ? bar:baz : qux
  4. Should xhp class :foo:bar be permitted?
    • for: more familiar for existing users
    • against: namespace myns { xhp class :foo:bar {} } - for consistency with <:foo:bar>, this should define \foo\bar ; but a namespace should not contain a definition of something that is not a member of that namespace
    • alt: only in root NS. for as above, consistency against

Assumptions

  1. Dynamic cases are covered by later enforcement of <<__DynamicallyConstructible>>; we will keep existing mangling until that logs/throws. This may require class_exists() and similar to log/throw if <<__DynamicallyConstructible>> is not declared on the class
  2. If a context already permits both qualified names and XHP class names, a straightforward replacement is safe
  3. No code using XHP is in a namespace at present, as it is effectively unusable
    • impossible to reference \:x:element as a superclass
    • impossible to reference \:foo from an XHP constructor call

Details on XHP_class_name token usage

Seen by hhast at:

  • classish_declaration.classish_name
  • member_selection_expression.member_name (attributes: $this->:attr - RHS is an XHP class name)
  • postfix_unary_expression.postfix_unary_operand (children: children (:foo*) - :foo* has operand :foo and oeprator *)) xhp_children_*
  • places that also accept a qualified name

Examples

namespace {
  // old: defines '\xhp_foo__bar'
  class :foo:bar extends :x:element {}
  // new: defines '\foo\bar'
  xhp class foo:bar extends x:element {}
  // optional: equivalent in the root namespace
  //   allow: slightly smaller codemod/more familiar
  //   parse error: consistent with banning in sub-namespaces
  xhp class :foo:bar extends :x:element {}
  
  // old: instantiate \xhp_foo__bar
  // new: instantiate namespace\foo\bar, which is \foo\bar
  $_ = <foo:bar />;
  // old: parse error
  // new: instantiate \foo\bar
  $_ = <:foo:bar />;  
  
  // old: defines `\xhp_foo__bar_baz'
  class :foo:bar-baz {}
  // new: parse error
  xhp class foo:bar-baz {}
  // new: defines \foo\bar_baz
  xhp class foo:bar_baz {}
  
  // old: takes a \xhp_foo
  // new: open question 3: parse error or takes a namespace\foo or \foo (equivalent here)
  function dostuff(:foo $_): void {}
  // new: takes a namespace\foo, which is a \foo
  function dostuff(foo $_): void {}
}

namespace myns {
  // old: no usable equivalent
  // new: defines '\myns\foo\bar` extending `\x\element`
  xhp class foo:bar extends :x:element {}
  // new: defines '\myns\foo\bar` extending `\myns\foo\bar`
  //   probably a mistake
  xhp class foo:bar extends x:element {}
  // old: no usable equivalent
  // new: parse error or other kind of forbidden
  //   for consistency, would mean \foo\bar, which we don't want to allow
  xhp class :foo:bar {}
  
  // old: uninstantiable but parsable
  // new: instantiate namespace\foo\bar, which is myns\foo\bar
  $_ = <foo:bar />;
  // old: parse error
  // new: instantiate \foo\bar
  $_ = <:foo:bar />;
  
  // \foo\bar [
  //   \myns\herp:derp [
  //     \p [
  //       "hello"
  //     ]
  //   ]
  // ]
  $_ = <:foo:bar><herp:derp><:p>hello</:p></herp:derp></:foo:bar>;
  
  // old: no usable equivalent
  // new: open question 3: options are:
  // - parse error
  // - takes a namespace\foo (which is a myns\foo)
  // - takes a \foo
  function dostuff(:foo $_): void {}
  // old: parse error
  // new: open question 3: parse error or \foo
  function dostuff(::foo $_): void {}
}
@fredemmott
Copy link
Author

With that path, the safe codemod (2) and disambuation (3) questions don't apply.

@fredemmott
Copy link
Author

Probably best done as a follow-up, but we /could/ also permit namespace:foo:bar in any context, given that namespace\ is already magic (explicitly relative to the current namespace, not a namespace called 'namespace')

@fredemmott
Copy link
Author

Additional rules:

  • class decl names MUST NOT be fully-qualified (e.g. xhp class :foo is an error)
  • class decl extends lists MAY be fully-qualified (e.g. extends :foo:bar is fine, but means \foo\bar, not namespace foo:bar)
  • : MUST NOT be used to refer to non-XHP types
    • in some contexts (e.g. return types, parameter types), this can only be a typechecker error, to avoid breaking file-at-a-time
    • MAY be a parse error in contexts where XHP is not reasonable, e.g. attributes and standard constructor calls
  • \ MUST NOT be used for XHP class decl names (e.g xhp class foo\bar is an error)
  • \ MAY be used for XHP class extends lists (e.g. xhp class foo extends \x\element)
    • linter or typechecker errors for mixing? xhp class foo:bar extend \x\element
    • linter completely banning depending on codebase preference
  • \ MUST NOT be used for XHP constructor calls
  • \ MAY be used for XHP children declarations
    • linter depending on codebase preferences

@fredemmott
Copy link
Author

  • names without a leading : or \ are NEVER considered to be a fully-qualified name
    • e.g. <foo:bar> is namespace\foo\bar unless there are relevant use namespace statements)
  • names with a leading : or \ are ALWAYS considered to be a fully-qualified name
  • leading : are permitted in XHP constructor calls as fully-qualified names
    • e.g. <:foo and <:foo:bar are permitted, and instantiate \foo and \foo\bar

@fredemmott
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment