Contributing: Code Samples
All code samples are automatically type checked and executed, to ensure they're correct.
Basic Usage
Code samples require triple backticks, and a file name.
```example.hack
<<__EntryPoint>>
function main(): void {
echo "Hello world!\n";
}
```
The build script will create a file of this name, type check it, run it, and include the output in the rendered HTML.
Boilerplate
For small examples, the wrapper function can be distracting. You can use standalone statements and they will be automatically wrapped in an <<__EntryPoint>>
function.
We can simplify the previous example to this.
```example.hack
echo "Hello world!\n";
```
Opting Out
If you really need to write a code sample that isn't checked or executed, you can omit the file name. The code sample will be included in the docs without any checking.
```
// This example is not run at all.
// It doesn't even need to be hack code!
```
Sharing Code
By default, each extracted example file has its own namespace based on the file name. This allows multiple examples to define the same class or function.
If you want to share code between examples, use a file name prefix followed by .
. Namespaces are generated based on the first name component, so foo.x.hack
and foo.y.hack
will have the same namespace.
Here's an example class:
```example_class.definition.hack
class C {}
```
And its usage:
```example_class.usage.hack
$c = new C();
```
Namespaces include the name of the page they're on, e.g. Hack\UserDocumentation\Fundamentals\Namespaces\Examples\Main
, so you cannot share code between different pages.
Autoloading
HHVM requires an "autoloader" to be explicitly initialized whenever any Hack file references definitions from another Hack file.
The build script will insert the necessary initialization code automatically
into any <<__EntryPoint>>
function, so it is OK to rely on definitions from
other examples inside any <<__EntryPoint>>
function or functions called by it,
but not elsewhere.
For example, HHVM can never successfully run a file containing e.g. a class definition that references a parent class or other definition from another file (this is not a limitation specific to the docs site).
```example_hierarchy.parent.hack
abstract class Parent {}
```
```example_hierarchy.child.hack
// This file will NEVER successfully run in HHVM.
final class Child extends Parent {}
```
In practice, this is fine because running a file containing a class definition
is generally not needed. However, it does mean that trying to add an
<<__EntryPoint>>
function to example_hierarchy.child.hack
won't work,
because HHVM will fail with an "Undefined class Parent" error before it even
reaches it.
```example_hierarchy.child.hack
// This file will NEVER successfully run in HHVM.
final class Child extends Parent {}
<<__EntryPoint>>
function main(): void {
// This EntryPoint function is useless because HHVM will fail above.
}
```
The workaround is to put any code that depends on definitions from more than one other example into a separate code block.
```example_hierarchy.usage.hack
$c = new Child();
```
This can also be more convenient because we can rely on the automatic
boilerplate addition by the build script, instead of manually writing the
<<__EntryPoint>>
function header.
Examples with Hack Errors
Examples that are expected to fail typechecking should use the .type-errors
extension:
```error_example.hack.type-errors
function missing_return_type() {}
```
The build script will run the Hack typechecker and include its output in the rendered HTML (instead of HHVM runtime output).
Supporting Files
An example code block may specify additional files to be extracted alongside the example code using the following format:
```nondeterministic_example.hack
echo "Your lucky number is: ".\mt_rand(0, 99);
```.example.hhvm.out
Your lucky number is: 42
```.expectf
Your lucky number is: %d
```
Supported extensions are inherited from the HHVM test runner:
.hhconfig
if the example requires specific typechecker flags (e.g. demonstrating a feature that is not yet enabled by default).ini
for runtime flags.hhvm.expect
if you want to manually specify the expected output, instead of the build script doing it automatically.hhvm.expectf
to specify the expected output using printf-style syntax, like in the example above.expectregex
to specify the expected output using a regular expression.example.hhvm.out
should contain one possible output (this will be included in the rendered HTML instead of theexpectf
/expectregex
file; it is not needed for regularexpect
files).typechecker.expect
,.typechecker.expectf
,.typechecker.expectregex
,.example.typechecker.out
are the same but for typechecker (Hack) output instead of runtime (HHVM) output; they should only be included if the example code is expected to fail typechecking and you don't want the build script to generate them automatically.skipif
should contain valid Hack code that will print "skip" if the example should not be run (e.g. a MySQL example that should not run if there isn't a MySQL server running), otherwise print nothing
Testing Changes
We have a test suite to ensure consistency across the changes we make to the guides, API references, and examples.
You can run it as follows:
$ vendor/bin/hacktest tests/
Running the Examples
Nearly all of the code examples you see in the guides and API documentation are actual Hack or PHP source files that are embedded at site build time into the content itself.
As opposed to embedded the code examples directly within the markdown itself, this provides the flexibility of actually having running examples within this repo.
You must have HHVM installed in order to run these examples since most of them are written in Hack (e.g., <?hh
), and HHVM is the only runtime to currently support Hack.
You will find the examples in directories named with the pattern:
guides/[hhvm | hack]/##-topic/##-subtopic-examples
e.g.,
$ guides/hack/23-collections/06-constructing-examples
Standalone
You can run any example standalone. For example:
# Assuming you are in the user-documentation repo directory
$ cd guides/hack/23-collections/10-examples-examples/
$ hhvm lazy.php
And you will see output like:
object(HH\Vector)#4 (5) {
[0]=>
int(0)
[1]=>
int(2)
[2]=>
int(4)
[3]=>
int(6)
[4]=>
int(8)
}
Time non lazy: 0.10859489440918
object(HH\Vector)#10 (5) {
[0]=>
int(0)
[1]=>
int(2)
[2]=>
int(4)
[3]=>
int(6)
[4]=>
int(8)
}
Time non lazy: 0.0096559524536133
Using the HHVM Test Runner
Each example is structured to be run with the HHVM test runner. We use the test runner internally to ensure that any changes made to HHVM do not cause a regression. The examples in the documentation here can be used for that purpose as well.
You can run the HHVM test runner on the entire suite of examples, on one directory of examples or just one example itself.
Normally you will use our test suite described above to test any changes you make (because it tests our examples as well). However, sometimes it is actually faster and more explicit to test one example directly with the HHVM test runner.
# Assuming you are in the user-documentation repo root
# This runs every example in the test runner.
# Won't normally need to do this; just use our test suite instead.
# Test with the typechecker
$ api-sources/hhvm/hphp/test/run --hhserver-binary-path $(which hh_server) --typechecker guides/hack/05-statements/
# Test with the runtime
$ api-sources/hhvm/hphp/test/run --hhvm-binary-path $(which hhvm) guides/hack/05-statements/
Here is the output you should see when you run the test runner. Assume we are running the examples in the collections topic:
$ hhvm api-sources/hhvm/hphp/test/run guides/hack/23-collections/
Running 32 tests in 32 threads (0 in serial)
All tests passed.
| | |
)_) )_) )_)
)___))___))___)\
)____)____)_____)\
_____|____|____|____\\__
---------\ SHIP IT /---------
^^^^^ ^^^^^^^^^^^^^^^^^^^^^
^^^^ ^^^^ ^^^ ^^
^^^^ ^^^
Total time for all executed tests as run: 11.57s
You can use --verbose
to see all the tests that are running.