/** * Executes this check * * @param xp.compiler.ast.Node node * @param xp.compiler.types.Scope scope * @return bool */ public function verify(\xp\compiler\ast\Node $node, \xp\compiler\types\Scope $scope) { $access = \cast($node, 'xp.compiler.ast.ArrayAccessNode'); $type = $scope->typeOf($access->target); $result = TypeName::$VAR; $message = null; if ($type->isArray()) { $result = $type->arrayComponentType(); } else { if ($type->isMap()) { $result = $type->mapComponentType(); } else { if ($type->isClass()) { $ptr = new TypeInstance($scope->resolveType($type)); if ($ptr->hasIndexer()) { $result = $ptr->getIndexer()->type; } else { $message = ['T305', 'Type ' . $ptr->name() . ' does not support offset access']; } } else { if ($type->isVariable()) { $message = ['T203', 'Array access (var)' . $access->hashCode() . ' verification deferred until runtime']; } else { if ('string' === $type->name) { $result = $type; } else { $message = ['T305', 'Using array-access on unsupported type ' . $type->toString()]; } } } } } $scope->setType($access, $result); return $message; }
/** * Emit member access * * @param xp.compiler.emit.Buffer b * @param xp.compiler.ast.MemberAccessNode access */ public function emitMemberAccess($b, $access) { $mark = $b->mark(); $this->emitOne($b, $access->target); $type = $this->scope[0]->typeOf($access->target); // Overload [...].length if ($type->isArray() && 'length' === $access->name) { $b->insert('sizeof(', $mark); $b->append(')'); $this->scope[0]->setType($access, new TypeName('int')); return; } // Manually verify as we can then rely on call target type being available if (!$this->checks->verify($access, $this->scope[0], $this, true)) { return; } // Navigation operator if ($access->nav) { $var = $this->tempVar(); $b->insert('(NULL === (' . $var . '=', $mark); $b->append(') ? NULL : ')->append($var)->append('->'); $b->append($access->name); $b->append(')'); } else { // Rewrite for unsupported syntax // - new Person().name to (new Person()).name // - (<expr>).name to an inline ternary if ($access->target instanceof InstanceCreationNode) { $b->insert('(', $mark); $b->append(')'); } else { if (!$access->target instanceof ArrayAccessNode && !$access->target instanceof MethodCallNode && !$access->target instanceof MemberAccessNode && !$access->target instanceof VariableNode && !$access->target instanceof StaticMemberAccessNode && !$access->target instanceof StaticMethodCallNode) { $var = $this->tempVar(); $b->insert('(NULL === (' . $var . '=', $mark); $b->append(')) ? NULL : ' . $var); } } $b->append('->' . $access->name); } // Record type $ptr = new TypeInstance($this->resolveType($type)); if ($ptr->hasField($access->name)) { $result = $ptr->getField($access->name)->type; } else { if ($ptr->hasProperty($access->name)) { $result = $ptr->getProperty($access->name)->type; } else { $result = TypeName::$VAR; } } $this->scope[0]->setType($access, $result); }