Skip to content

Commit

Permalink
Now uses static type info to collate class types
Browse files Browse the repository at this point in the history
  • Loading branch information
Troels Knak-Nielsen committed Dec 7, 2008
1 parent 2c1014c commit 66d6702
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 63 deletions.
46 changes: 42 additions & 4 deletions reflector.inc.php
@@ -1,6 +1,35 @@
<?php
class StaticReflector {
interface ClassCollator {
function collate($first, $second);
}

class DummyClassCollator implements ClassCollator {
function collate($first, $second) {
return 'mixed';
}
}

class TraceIncludesLogger {
protected $reflector;
protected $includes = array();
function __construct(StaticReflector $reflector) {
$this->reflector = $reflector;
}
function log($trace) {
$filename = isset($trace['filename']) ? $trace['filename'] : '';
if (!isset($this->includes[$filename]) && is_file($filename)) {
$this->reflector->scanFile($filename);
}
$this->includes[$filename] = true;
}
function log_include($trace) {
$this->log($trace);
}
}

class StaticReflector implements ClassCollator {
protected $scanner;
protected $names = array();
protected $typemap = array();
protected $collate_cache = array();
protected $ancestors_cache = array();
Expand All @@ -12,6 +41,8 @@ function __construct() {
$inheritance_scanner->notifyOnImplements(array($this, 'logSupertype'));
}
function logSupertype($class, $super) {
$this->names[strtolower($super)] = $super;
$this->names[strtolower($class)] = $class;
$class = strtolower($class);
$super = strtolower($super);
if (!isset($this->typemap[$class])) {
Expand All @@ -34,13 +65,20 @@ function scanString($php_source) {
function export() {
return $this->typemap;
}
protected function symbolsToNames($symbols = array()) {
$names = array();
foreach ($symbols as $symbol) {
$names[] = $this->names[$symbol];
}
return $names;
}
function ancestors($class) {
$class = strtolower($class);
return isset($this->typemap[$class]) ? $this->typemap[$class] : array();
return $this->symbolsToNames(isset($this->typemap[$class]) ? $this->typemap[$class] : array());
}
function ancestorsAndSelf($class) {
$class = strtolower($class);
return isset($this->typemap[$class]) ? array_merge(array($class), $this->typemap[$class]) : array($class);
return $this->symbolsToNames(isset($this->typemap[$class]) ? array_merge(array($class), $this->typemap[$class]) : array($class));
}
function allAncestors($class) {
$class = strtolower($class);
Expand All @@ -55,7 +93,7 @@ function allAncestors($class) {
return $result;
}
function allAncestorsAndSelf($class) {
return array_merge(array(strtolower($class)), $this->allAncestors($class));
return array_merge(array($this->names[strtolower($class)]), $this->allAncestors($class));
}
/**
* Finds the first common ancestor, if possible
Expand Down
66 changes: 48 additions & 18 deletions signature.inc.php
@@ -1,10 +1,9 @@
<?php
class Signatures {
protected $signatures_array = array();
function __construct($signatures_array = array()) {
foreach ($signatures_array as $name => $sig) {
$this->signatures_array[strtolower($name)] = $sig;
}
protected $collator;
function __construct(ClassCollator $collator) {
$this->collator = $collator;
}
function has($func, $class = "") {
$name = strtolower($class ? ($class . '->' . $func) : $func);
Expand All @@ -16,26 +15,24 @@ function get($func, $class = "") {
}
$name = strtolower($class ? ($class . '->' . $func) : $func);
if (!isset($this->signatures_array[$name])) {
$this->signatures_array[$name] = new FunctionSignature();
$this->signatures_array[$name] = new FunctionSignature($this->collator);
}
return $this->signatures_array[$name];
}
}

/**
* Signatures are collected at 3 different places (in order of authority):
* static analysis (typehints)
* runtime analysis (trace)
* docblockcomments (optional)
*/
class FunctionSignature {
protected $arguments = array();
protected $return_type;
protected $collator;
function __construct(ClassCollator $collator) {
$this->collator = $collator;
}
function blend($arguments, $return_type) {
if ($arguments) {
foreach ($arguments as $id => $type) {
$arg = $this->getArgumentById($id);
$arg->blendType($type);
$arg->collateWith($type);
}
}
if ($return_type) {
Expand All @@ -47,7 +44,7 @@ function getReturnType() {
}
function getArgumentById($id) {
if (!isset($this->arguments[$id])) {
$this->arguments[$id] = new FunctionArgument($id);
$this->arguments[$id] = new FunctionArgument($id, null, '???', $this->collator);
}
return $this->arguments[$id];
}
Expand All @@ -69,10 +66,16 @@ class FunctionArgument {
protected $id;
protected $name;
protected $type;
function __construct($id, $name = null, $type = '???') {
protected $collator;
function __construct($id, $name = null, $type = '???', ClassCollator $collator) {
$this->id = $id;
$this->name = $name;
$this->type = $type;
if ($type === 'null') {
$this->type = '???';
} else {
$this->type = $type;
}
$this->collator = $collator;
}
function getId() {
return $this->id;
Expand All @@ -95,10 +98,37 @@ function getType() {
function setType($type) {
$this->type = $type;
}
function blendType($type) {
// todo: could probably be more intelligent
if ($type != '???') {
function collateWith($type) {
static $primitive = array('boolean', 'string', 'array', 'integer', 'double', 'mixed');
if ($this->type === $type) {
return;
}
if ($type === 'null') {
// todo: set this->nullable = true
return;
}
if ($this->type === '???') {
$this->type = $type;
} elseif ($type != '???') {
if (in_array($type, $primitive) || in_array($this->type, $primitive)) {
$tmp = array($this->type, $type);
sort($tmp);
switch (implode(":", $tmp)) {
case 'integer:string':
case 'double:string':
$this->type = 'string';
break;
case 'double:integer':
$this->type = 'double';
break;
default:
$this->type = 'mixed';
}
} else {
//$this->type = $type;
$collate = $this->collator->collate($this->type, $type);
$this->type = $collate === '*CANT_COLLATE*' ? 'mixed' : $collate;
}
}
}
}
65 changes: 55 additions & 10 deletions test.php
Expand Up @@ -3,9 +3,10 @@

/**
* todo:
* blendType to use StaticReflector + xtrace_TraceIncludesLogger
* xtrace -> resources ..
* use static typehints for parameter types
* use docblock comments for parameter types
* merge with existing docblock comments
*/

// You need to have simpletest in your include_path
Expand Down Expand Up @@ -108,32 +109,32 @@ function test_can_scan_sources() {
$reflector = new StaticReflector();
$reflector->scanString('<'.'?php class Foo implements Bar, Doink {}');
$reflector->scanString('<'.'?php class Zip implements Bar {}');
$this->assertEqual($reflector->ancestors('Foo'), array('bar', 'doink'));
$this->assertEqual($reflector->ancestors('zip'), array('bar'));
$this->assertEqual($reflector->ancestors('Foo'), array('Bar', 'Doink'));
$this->assertEqual($reflector->ancestors('Zip'), array('Bar'));
}
function test_can_collate_same() {
$reflector = new StaticReflector();
$reflector->scanString('<'.'?php class Foo extends Bar {}');
$reflector->scanString('<'.'?php class Zip extends Bar {}');
$this->assertEqual($reflector->collate('foo', 'foo'), 'foo');
$this->assertEqual($reflector->collate('Foo', 'Foo'), 'Foo');
}
function test_can_collate_direct_inheritance() {
$reflector = new StaticReflector();
$reflector->scanString('<'.'?php class Foo extends Bar {}');
$reflector->scanString('<'.'?php class Zip extends Bar {}');
$this->assertEqual($reflector->collate('foo', 'zip'), 'bar');
$this->assertEqual($reflector->collate('Foo', 'Zip'), 'Bar');
}
function test_can_collate_child_to_parent() {
$reflector = new StaticReflector();
$reflector->scanString('<'.'?php class Foo {}');
$reflector->scanString('<'.'?php class Bar extends Foo {}');
$this->assertEqual($reflector->collate('foo', 'bar'), 'foo');
$this->assertEqual($reflector->collate('Foo', 'Bar'), 'Foo');
}
function test_can_collate_parent_to_child() {
$reflector = new StaticReflector();
$reflector->scanString('<'.'?php class Foo {}');
$reflector->scanString('<'.'?php class Bar extends Foo {}');
$this->assertEqual($reflector->collate('bar', 'foo'), 'foo');
$this->assertEqual($reflector->collate('Bar', 'Foo'), 'Foo');
}
}

Expand Down Expand Up @@ -303,7 +304,6 @@ function tearDown() {
function test_can_execute_sandbox_code() {
chdir($this->sandbox());
$output = shell_exec('php ' . escapeshellarg($this->sandbox() . '/main.php'));
//$this->dump("\n----\n" . $output . "\n----\n");
$this->assertEqual("(completed)\n", $output);
}
function test_can_execute_sandbox_code_with_instrumentation() {
Expand All @@ -321,7 +321,7 @@ function test_instrumentation_creates_tracefile() {
function test_can_parse_tracefile() {
chdir($this->sandbox());
shell_exec(escapeshellcmd($this->bindir() . '/trace.sh') . " " . escapeshellarg($this->sandbox() . '/main.php'));
$sigs = new Signatures();
$sigs = new Signatures(new DummyClassCollator());
$this->assertFalse($sigs->has('callit'));
$trace = new xtrace_TraceReader(new SplFileObject($this->sandbox() . '/dumpfile.xt'));
$collector = new xtrace_TraceSignatureLogger($sigs);
Expand All @@ -331,10 +331,55 @@ function test_can_parse_tracefile() {
function test_can_parse_class_arg() {
chdir($this->sandbox());
shell_exec(escapeshellcmd($this->bindir() . '/trace.sh') . " " . escapeshellarg($this->sandbox() . '/main.php'));
$sigs = new Signatures();
$sigs = new Signatures(new DummyClassCollator());
$trace = new xtrace_TraceReader(new SplFileObject($this->sandbox() . '/dumpfile.xt'));
$collector = new xtrace_TraceSignatureLogger($sigs);
$trace->process(new xtrace_FunctionTracer($collector));
$this->assertEqual('Foo', $sigs->get('callit')->getArgumentById(0)->getType());
}
}

class TestOfCollation extends UnitTestCase {
function bindir() {
return dirname(__FILE__);
}
function sandbox() {
return dirname(__FILE__) . '/sandbox';
}
function setUp() {
$this->curdir = getcwd();
$dir_sandbox = $this->sandbox();
mkdir($dir_sandbox);
$source_main = '<'.'?php' . "\n" .
'class Foo {' . "\n" .
'}'. "\n" .
'class Bar extends Foo {' . "\n" .
'}'. "\n" .
'class Cuux extends Foo {' . "\n" .
'}'. "\n" .
'function do_stuff($x) {}'. "\n" .
'do_stuff(new Bar());'. "\n" .
'do_stuff(new Cuux());'
;
file_put_contents($dir_sandbox . '/main.php', $source_main);
}
function tearDown() {
chdir($this->curdir);
$dir_sandbox = $this->sandbox();
unlink($dir_sandbox . '/main.php');
if (is_file($dir_sandbox . '/dumpfile.xt')) {
unlink($dir_sandbox . '/dumpfile.xt');
}
rmdir($dir_sandbox);
}
function test_can_collate_classes() {
chdir($this->sandbox());
shell_exec(escapeshellcmd($this->bindir() . '/trace.sh') . " " . escapeshellarg($this->sandbox() . '/main.php'));
$reflector = new StaticReflector();
$sigs = new Signatures($reflector);
$trace = new xtrace_TraceReader(new SplFileObject($this->sandbox() . '/dumpfile.xt'));
$collector = new xtrace_TraceSignatureLogger($sigs, $reflector);
$trace->process(new xtrace_FunctionTracer($collector));
$this->assertEqual('Foo', $sigs->get('do_stuff')->getArgumentById(0)->getType());
}
}
9 changes: 5 additions & 4 deletions weave.php
Expand Up @@ -16,18 +16,19 @@
}

// read trace
$db = new Signatures();
$reflector = new StaticReflector();
$sigs = new Signatures();
$trace = new xtrace_TraceReader(new SplFileObject($trace_filename));
$collector = new xtrace_TraceSignatureLogger($db);
$trace->process(new xtrace_FunctionTracer($collector));
$collector = new xtrace_TraceSignatureLogger($sigs, $reflector);
$trace->process(new xtrace_FunctionTracer($collector, $reflector));

// transform file
$scanner = new ScannerMultiplexer();
$parameters_scanner = $scanner->appendScanner(new FunctionParametersScanner());
$function_body_scanner = $scanner->appendScanner(new FunctionBodyScanner());
$modifiers_scanner = $scanner->appendScanner(new ModifiersScanner());
$class_scanner = $scanner->appendScanner(new ClassScanner());
$editor = new TracerDocBlockEditor($db, $class_scanner, $function_body_scanner);
$editor = new TracerDocBlockEditor($sigs, $class_scanner, $function_body_scanner);
$transformer = $scanner->appendScanner(new DocCommentEditorTransformer($function_body_scanner, $modifiers_scanner, $parameters_scanner, $editor));
$tokenizer = new TokenStreamParser();
$token_stream = $tokenizer->scan(file_get_contents($file_to_weave));
Expand Down

0 comments on commit 66d6702

Please sign in to comment.