Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 51 additions & 5 deletions lib/IRGen/ESTreeIRGen-expr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1044,6 +1044,8 @@ ESTreeIRGen::MemberExpressionResult ESTreeIRGen::genMemberExpression(
Builder.getLiteralUndefined()};
}
if (llvh::isa<flow::ClassType>(
flowContext_.getNodeTypeOrAny(superNode)->info) ||
llvh::isa<flow::ClassConstructorType>(
flowContext_.getNodeTypeOrAny(superNode)->info)) {
auto *property = llvh::dyn_cast<ESTree::IdentifierNode>(mem->_property);
if (!property || mem->_computed) {
Expand Down Expand Up @@ -1439,9 +1441,8 @@ ESTreeIRGen::MemberExpressionResult ESTreeIRGen::emitMemberLoad(
}
} else {
auto *loadProp = Builder.createLoadPropertyInst(baseValue, propValue);
loadProp->setType(
getTypeContext().unionTy(
Type::createString(), Type::createUndefined()));
loadProp->setType(getTypeContext().unionTy(
Type::createString(), Type::createUndefined()));
auto *cast =
Builder.createCheckedTypeCastInst(loadProp, Type::createString());
return MemberExpressionResult{cast, nullptr, baseValue};
Expand All @@ -1460,8 +1461,11 @@ ESTreeIRGen::MemberExpressionResult ESTreeIRGen::emitMemberLoad(
ESTreeIRGen::MemberExpressionResult ESTreeIRGen::emitTypedSuperLoad(
ESTree::SuperNode *superNode,
ESTree::IdentifierNode *property) {
auto *classType = llvh::cast<flow::ClassType>(
flowContext_.getNodeTypeOrAny(superNode)->info);
auto *superType = flowContext_.getNodeTypeOrAny(superNode)->info;
const bool isStatic = llvh::isa<flow::ClassConstructorType>(superType);
auto *classType = isStatic
? llvh::cast<flow::ClassConstructorType>(superType)->getClassTypeInfo()
: llvh::cast<flow::ClassType>(superType);
auto propName = Identifier::getFromPointer(
llvh::cast<ESTree::IdentifierNode>(property)->_name);
Value *thisValue = genThisExpression();
Expand All @@ -1475,6 +1479,26 @@ ESTreeIRGen::MemberExpressionResult ESTreeIRGen::emitTypedSuperLoad(
closure, declFunctions_.lookup(decl), thisValue};
}

// Static members are represented by scope variables rather than slots on
// the constructor object. Resolve static accessors through the superclass
// type and call them with the current class as the receiver, as required by
// super property semantics.
if (isStatic) {
auto *staticType = classType->getStaticObjectTypeInfo();
assert(staticType && "typed static super must have a static object type");
auto optField = staticType->findPublicField(propName);
assert(optField && "static super property must typecheck");
const auto *field = optField->getField();
if (field->hasGetter()) {
assert(field->method && "static getter must have a method");
auto *getterKey = ESTree::getPropertyIdentifier(field->method->_key);
sema::Decl *decl = semCtx_.getExpressionDecl(getterKey);
Value *call = emitTypedGetterCall(decl, thisValue, field->type);
return MemberExpressionResult{call, nullptr, thisValue};
}
llvm_unreachable("static super member should have a Decl");
}

if (auto optFieldLookup = classType->findPublicField(propName)) {
// Found the field on the class, so load it directly from 'this'.
size_t fieldIndex = *optFieldLookup->getField()->layoutSlotIR;
Expand Down Expand Up @@ -1565,6 +1589,28 @@ void ESTreeIRGen::emitMemberStore(

// If \p thisValue is set, this is a store to `super`.
if (thisValue) {
// Static super accessors use the superclass's static type, while the
// setter receiver remains the current class.
if (auto *consType = llvh::dyn_cast<flow::ClassConstructorType>(
flowContext_.getNodeTypeOrAny(mem->_object)->info)) {
if (!mem->_computed) {
auto *classTypeInfo =
llvh::cast<flow::ClassType>(consType->getClassType()->info);
auto *staticInfo = classTypeInfo->getStaticObjectTypeInfo();
auto optField = staticInfo
? findFieldForMemberProp(mem, staticInfo)
: OptValue<flow::ClassType::FieldLookupEntry>();
if (optField && optField->getField()->hasSetter()) {
const auto *field = optField->getField();
assert(field->setterMethod && "static setter must have a method");
auto *setterKey =
ESTree::getPropertyIdentifier(field->setterMethod->_key);
sema::Decl *decl = semCtx_.getExpressionDecl(setterKey);
emitTypedSetterCall(decl, thisValue, storedValue);
return;
}
}
}
// Check for typed accessor setter on super.
if (auto *classType = llvh::dyn_cast<flow::ClassType>(
flowContext_.getNodeTypeOrAny(mem->_object)->info)) {
Expand Down
22 changes: 16 additions & 6 deletions lib/IRGen/ESTreeIRGen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -480,11 +480,21 @@ LReference ESTreeIRGen::createLRef(ESTree::Node *node, bool declInit) {
LLVM_DEBUG(llvh::dbgs() << "Creating an LRef for member expression.\n");

Value *obj;
Value *thisVal = nullptr;
if (llvh::isa<ESTree::SuperNode>(ME->_object)) {
auto *homeObjectVar = curFunction()->capturedState.homeObject;
auto *RSI = emitResolveScopeInstIfNeeded(homeObjectVar->getParent());
Value *homeObjectVal = Builder.createLoadFrameInst(RSI, homeObjectVar);
obj = Builder.createLoadParentNoTrapsInst(homeObjectVal);
auto *superType = flowContext_.getNodeTypeOrAny(ME->_object)->info;
if (llvh::isa<flow::ClassConstructorType>(superType)) {
// Typed static members are resolved through their Decl or static
// accessor metadata. Do not load the instance home object here: static
// methods don't have one. The receiver is supplied separately below.
thisVal = genThisExpression();
obj = thisVal;
} else {
auto *homeObjectVar = curFunction()->capturedState.homeObject;
auto *RSI = emitResolveScopeInstIfNeeded(homeObjectVar->getParent());
Value *homeObjectVal = Builder.createLoadFrameInst(RSI, homeObjectVar);
obj = Builder.createLoadParentNoTrapsInst(homeObjectVal);
}
} else {
obj = genExpression(ME->_object);
}
Expand All @@ -508,10 +518,10 @@ LReference ESTreeIRGen::createLRef(ESTree::Node *node, bool declInit) {
prop = genMemberExpressionProperty(ME);
}

Value *thisVal = nullptr;
// `thisVal` should only be set for `super` references.
if (llvh::isa<ESTree::SuperNode>(ME->_object)) {
thisVal = genThisExpression();
if (!thisVal)
thisVal = genThisExpression();
}
return LReference(
LReference::Kind::Member,
Expand Down
33 changes: 29 additions & 4 deletions lib/Sema/FlowChecker-expr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2679,7 +2679,9 @@ class FlowChecker::ExprVisitor {
"ft: 'super' call outside class");
return;
}
thisArgType = outer_.curClassContext_->classType;
thisArgType = outer_.curFunctionContext_->thisParamType
? outer_.curFunctionContext_->thisParamType
: outer_.curClassContext_->classType;
} else {
thisArgType = outer_.getNodeTypeOrAny(methodCallee->_object);
}
Expand Down Expand Up @@ -3435,8 +3437,21 @@ class FlowChecker::ExprVisitor {
// super() call calls the constructor of the super class.
outer_.setNodeType(node, superClassType->getConstructorType());
} else if (llvh::isa<ESTree::MemberExpressionNode>(parent)) {
// super.property lookup is on the super class.
outer_.setNodeType(node, curClassType->getSuperClass());
// A static method's home object is the class constructor, so static
// super.property lookup is on the superclass constructor. Instance
// methods use the superclass instance type.
if (outer_.curFunctionContext_ &&
outer_.curFunctionContext_->thisParamType &&
llvh::isa<ClassConstructorType>(
outer_.curFunctionContext_->thisParamType->info)) {
outer_.setNodeType(
node,
outer_.flowContext_.createType(
outer_.flowContext_.createClassConstructor(
curClassType->getSuperClass())));
} else {
outer_.setNodeType(node, curClassType->getSuperClass());
}
} else {
outer_.sm_.error(node->getSourceRange(), "ft: invalid usage of super");
outer_.flowContext_.setNodeType(node, outer_.flowContext_.getAny());
Expand Down Expand Up @@ -3646,8 +3661,18 @@ class FlowChecker::ExprVisitor {
continue;
typeArgs = std::move(inferred);
}
Type *thisType = objType;
if (llvh::isa<ESTree::SuperNode>(callee->_object) &&
outer_.curFunctionContext_ &&
outer_.curFunctionContext_->thisParamType) {
thisType = outer_.curFunctionContext_->thisParamType;
}
auto result = outer_.specializeGenericMethodWithParsedTypes(
overloadMethod, node->getSourceRange(), typeArgs, objType);
overloadMethod,
node->getSourceRange(),
typeArgs,
objType,
thisType);
if (!result.type)
continue;
overloadType = result.type;
Expand Down
38 changes: 27 additions & 11 deletions lib/Sema/FlowChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1351,7 +1351,8 @@ void FlowChecker::visit(ESTree::MethodDefinitionNode *node) {
*this,
fe,
funcType,
curClassContext_->classType,
node->_static ? curClassContext_->newTargetType
: curClassContext_->classType,
/* newTargetType */ flowContext_.getVoid());

// For static methods on generic classes, replace the class type
Expand Down Expand Up @@ -3324,7 +3325,8 @@ std::pair<ESTree::Node *, bool> FlowChecker::specializeGenericImpl(
GenericInfo<ESTree::Node> &generic,
sema::LexicalScope *cloneScope,
SMRange errorRange,
llvh::ArrayRef<Type *> typeArgTypes) {
llvh::ArrayRef<Type *> typeArgTypes,
Type *cacheDiscriminator) {
assert(cloneScope && "must have cloneScope");

// Extract info from types.
Expand All @@ -3333,6 +3335,13 @@ std::pair<ESTree::Node *, bool> FlowChecker::specializeGenericImpl(
assert(type->info && "missing type info for generic specialization");
typeArgs.push_back(type->info);
}
// Include the receiver in a generic method's cache key. Inherited generic
// methods can be specialized for different receiver types, which affects
// the type of `this` in the specialized body.
if (cacheDiscriminator) {
assert(cacheDiscriminator->info && "missing cache discriminator type info");
typeArgs.push_back(cacheDiscriminator->info);
}

// Retrieve specialization if it exists.
GenericInfo<ESTree::Node>::TypeArgsRef typeArgsRef = typeArgs;
Expand Down Expand Up @@ -3942,9 +3951,14 @@ void FlowChecker::resolveCallToGenericMethodSpecialization(

// Get the object type to use as the 'this' type.
Type *objType = getNodeTypeOrAny(callee->_object);
Type *thisType = objType;
if (llvh::isa<ESTree::SuperNode>(callee->_object) && curFunctionContext_ &&
curFunctionContext_->thisParamType) {
thisType = curFunctionContext_->thisParamType;
}

auto result = specializeGenericMethodWithParsedTypes(
method, node->getSourceRange(), typeArgTypes, objType);
method, node->getSourceRange(), typeArgTypes, objType, thisType);

if (result.type) {
setNodeType(callee, result.type);
Expand All @@ -3964,9 +3978,14 @@ void FlowChecker::resolveCallToGenericMethodSpecializationWithParsedTypes(

// Get the object type to use as the 'this' type.
Type *objType = getNodeTypeOrAny(callee->_object);
Type *thisType = objType;
if (llvh::isa<ESTree::SuperNode>(callee->_object) && curFunctionContext_ &&
curFunctionContext_->thisParamType) {
thisType = curFunctionContext_->thisParamType;
}

auto result = specializeGenericMethodWithParsedTypes(
method, node->getSourceRange(), typeArgTypes, objType);
method, node->getSourceRange(), typeArgTypes, objType, thisType);

if (result.type) {
setNodeType(callee, result.type);
Expand Down Expand Up @@ -4031,11 +4050,12 @@ FlowChecker::specializeGenericMethodWithParsedTypes(
ESTree::MethodDefinitionNode *method,
SMRange errorRange,
llvh::ArrayRef<Type *> typeArgTypes,
Type *classType) {
Type *classType,
Type *thisType) {
GenericInfo<ESTree::Node> &generic = getGenericMethodInfoMustExist(method);

auto [specialization, wasNewlyCreated] = specializeGenericImpl(
generic, generic.classScope, errorRange, typeArgTypes);
generic, generic.classScope, errorRange, typeArgTypes, thisType);
if (!specialization) {
return {};
}
Expand Down Expand Up @@ -4094,11 +4114,7 @@ FlowChecker::specializeGenericMethodWithParsedTypes(
classTypeForContext = ct->getClassType();
ClassContext classContext(*this, classTypeForContext, classType, nullptr);
FunctionContext functionContext(
*this,
specializedFE,
ftype,
classTypeForContext,
flowContext_.getVoid());
*this, specializedFE, ftype, thisType, flowContext_.getVoid());
visitFunctionLike(
specializedFE, specializedFE->_body, specializedFE->_params);
} else {
Expand Down
6 changes: 4 additions & 2 deletions lib/Sema/FlowChecker.h
Original file line number Diff line number Diff line change
Expand Up @@ -1000,7 +1000,8 @@ class FlowChecker : public ESTree::RecursionDepthTracker<FlowChecker> {
ESTree::MethodDefinitionNode *method,
SMRange errorRange,
llvh::ArrayRef<Type *> typeArgTypes,
Type *classType);
Type *classType,
Type *thisType);

/// Implementation for specializing generics: handles cloning, caching,
/// and insertion. Caller handles scope management and typechecking.
Expand All @@ -1015,7 +1016,8 @@ class FlowChecker : public ESTree::RecursionDepthTracker<FlowChecker> {
GenericInfo<ESTree::Node> &generic,
sema::LexicalScope *cloneScope,
SMRange errorRange,
llvh::ArrayRef<Type *> typeArgTypes);
llvh::ArrayRef<Type *> typeArgTypes,
Type *cacheDiscriminator = nullptr);

/// \param key the property key to attempt to convert to an identifier,
/// e.g. StringLiteralNode, IdentifierNode, or NumericLiteralNode.
Expand Down
82 changes: 82 additions & 0 deletions test/IRGen/flow/class-static-super.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

// RUN: %shermes -typed -exec %s | %FileCheck --match-full-lines %s

class Base {
static value: number = 3;
static backing: number = 3;
static label: string = "base";

static method(): number {
return Base.value + 4;
}

@Hermes.final static get current(): number {
return this.backing;
}

@Hermes.final static set current(value: number) {
this.backing = value;
}

@Hermes.final static identify<T>(value: T): string {
return this.label;
}
}

class Child extends Base {
static label: string = "child";

static testMethod(): number {
return super.method();
}

static testAssignment(): number {
super.value = 9;
return Base.value;
}

static testAccessors(): number {
super.current = 42;
return super.current;
}

static testGenericThis(): string {
return super.identify<number>(1);
}
}

class ChildA extends Base {
static label: string = "a";

static testGenericThis(): string {
return super.identify<number>(1);
}
}

class ChildB extends Base {
static label: string = "b";

static testGenericThis(): string {
return super.identify<number>(1);
}
}

print(Child.testMethod());
print(Child.testAssignment());
print(Child.testAccessors());
print(Child.testGenericThis());
print(ChildA.testGenericThis());
print(ChildB.testGenericThis());

// CHECK: 7
// CHECK-NEXT: 9
// CHECK-NEXT: 42
// CHECK-NEXT: child
// CHECK-NEXT: a
// CHECK-NEXT: b
Loading