Skip to content

Commit

Permalink
Accept T.self_type and T.attached_class for proc binding (sorbet#…
Browse files Browse the repository at this point in the history
…4829)

* Allow binding blocks to attached_class and self_type

* Add test case for trying to bind a proc's parameter

* Look up attached class member on the singleton class

* Prevent proc bind from being used in non-block arguments

* Add more test cases and handle method level bind to attached_class
  • Loading branch information
vinistock authored Feb 19, 2022
1 parent f0d131e commit 3273564
Show file tree
Hide file tree
Showing 11 changed files with 241 additions and 10 deletions.
7 changes: 7 additions & 0 deletions core/GlobalState.cc
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,13 @@ void GlobalState::initEmpty() {
klass = Symbols::T_Private_Compiler().data(*this)->singletonClass(*this);
ENFORCE(klass == Symbols::T_Private_CompilerSingleton());

// Magic classes for special proc bindings
klass = synthesizeClass(core::Names::Constants::BindToAttachedClass());
ENFORCE(klass == Symbols::BindToAttachedClass());

klass = synthesizeClass(core::Names::Constants::BindToSelfType());
ENFORCE(klass == Symbols::BindToSelfType());

typeArgument =
enterTypeArgument(Loc::none(), Symbols::noMethod(), Names::Constants::TodoTypeArgument(), Variance::CoVariant);
ENFORCE(typeArgument == Symbols::todoTypeArgument());
Expand Down
12 changes: 10 additions & 2 deletions core/SymbolRef.h
Original file line number Diff line number Diff line change
Expand Up @@ -970,6 +970,14 @@ class Symbols {
return ClassOrModuleRef::fromRaw(88);
}

static ClassOrModuleRef BindToAttachedClass() {
return ClassOrModuleRef::fromRaw(89);
}

static ClassOrModuleRef BindToSelfType() {
return ClassOrModuleRef::fromRaw(90);
}

static constexpr int MAX_PROC_ARITY = 10;
static ClassOrModuleRef Proc0() {
return ClassOrModuleRef::fromRaw(MAX_SYNTHETIC_CLASS_SYMBOLS - MAX_PROC_ARITY * 2 - 2);
Expand All @@ -992,11 +1000,11 @@ class Symbols {
return ClassOrModuleRef::fromRaw(MAX_SYNTHETIC_CLASS_SYMBOLS - 1);
}

static constexpr int MAX_SYNTHETIC_CLASS_SYMBOLS = 203;
static constexpr int MAX_SYNTHETIC_CLASS_SYMBOLS = 205;
static constexpr int MAX_SYNTHETIC_METHOD_SYMBOLS = 45;
static constexpr int MAX_SYNTHETIC_FIELD_SYMBOLS = 4;
static constexpr int MAX_SYNTHETIC_TYPEARGUMENT_SYMBOLS = 4;
static constexpr int MAX_SYNTHETIC_TYPEMEMBER_SYMBOLS = 100;
static constexpr int MAX_SYNTHETIC_TYPEMEMBER_SYMBOLS = 102;
};

template <typename H> H AbslHashValue(H h, const SymbolRef &m) {
Expand Down
1 change: 1 addition & 0 deletions core/errors/resolver.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ constexpr ErrorClass NonClassSuperclass{5067, StrictLevel::False};
constexpr ErrorClass AmbiguousDefinitionError{5068, StrictLevel::False};
constexpr ErrorClass MultipleStatementsInSig{5069, StrictLevel::False};
constexpr ErrorClass NilableUntyped{5070, StrictLevel::False};
constexpr ErrorClass BindNonBlockParameter{5071, StrictLevel::False};
} // namespace sorbet::core::errors::Resolver

#endif
4 changes: 4 additions & 0 deletions core/tools/generate_names.cc
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,10 @@ NameDef names[] = {
{"Rational", "Rational", true},
// A magic non user-creatable class with methods to keep state between passes
{"Magic", "<Magic>", true},
// A magic non user-creatable class for binding procs to attached_class
{"BindToAttachedClass", "<BindToAttachedClass>", true},
// A magic non user-creatable class for binding procs to self_type
{"BindToSelfType", "<BindToSelfType>", true},
// A magic non user-creatable class for mimicing the decl builder during cfg
// construction
{"DeclBuilderForProcs", "<DeclBuilderForProcs>", true},
Expand Down
21 changes: 18 additions & 3 deletions infer/environment.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1364,10 +1364,25 @@ core::TypePtr Environment::processBinding(core::Context ctx, const cfg::CFG &inW
},
[&](cfg::LoadSelf &l) {
ENFORCE(l.link);
if (l.link->result->main.blockSpec.rebind.exists()) {
tp.type = l.link->result->main.blockSpec.rebind.data(ctx)->externalType();
tp.origins.emplace_back(core::Loc(ctx.file, bind.loc));
auto rebind = l.link->result->main.blockSpec.rebind;

if (rebind.exists()) {
if (rebind == core::Symbols::BindToSelfType()) {
tp.type = l.link->result->main.receiver;
} else if (rebind == core::Symbols::BindToAttachedClass()) {
auto appliedType = core::cast_type<core::AppliedType>(l.link->result->main.receiver);
auto attachedClass =
appliedType->klass.data(ctx)->findMember(ctx, core::Names::Constants::AttachedClass());

auto lambdaParam =
core::cast_type<core::LambdaParam>(attachedClass.asTypeMemberRef().data(ctx)->resultType);

tp.type = lambdaParam->upperBound;
} else {
tp.type = rebind.data(ctx)->externalType();
}

tp.origins.emplace_back(core::Loc(ctx.file, bind.loc));
} else {
tp = getTypeAndOrigin(ctx, l.fallback);
}
Expand Down
22 changes: 20 additions & 2 deletions resolver/resolver.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2897,7 +2897,14 @@ class ResolveSignaturesWalk {
method.data(ctx)->flags.isFinal = true;
}
if (sig.seen.bind) {
method.data(ctx)->rebind = sig.bind;
if (sig.bind == core::Symbols::BindToAttachedClass()) {
if (auto e = ctx.beginError(exprLoc, core::errors::Resolver::BindNonBlockParameter)) {
e.setHeader("Using `{}` is not permitted here", "bind");
e.addErrorNote("Only block arguments can use `{}`", "bind");
}
} else {
method.data(ctx)->rebind = sig.bind;
}
}

auto methodInfo = method.data(ctx);
Expand Down Expand Up @@ -2952,9 +2959,21 @@ class ResolveSignaturesWalk {
defParams.push_back(local);

auto spec = absl::c_find_if(sig.argTypes, [&](const auto &spec) { return spec.name == treeArgName; });
bool isBlkArg = arg.name == core::Names::blkArg();

if (spec != sig.argTypes.end()) {
ENFORCE(spec->type != nullptr);

// It would be nice to remove the restriction on more than these two specific binds, but that would
// raise a lot more errors
if (!isBlkArg && (spec->rebind == core::Symbols::BindToAttachedClass() ||
spec->rebind == core::Symbols::BindToSelfType())) {
if (auto e = ctx.state.beginError(spec->loc, core::errors::Resolver::BindNonBlockParameter)) {
e.setHeader("Using `{}` is not permitted here", "bind");
e.addErrorNote("Only block arguments can use `{}`", "bind");
}
}

arg.type = std::move(spec->type);
arg.loc = spec->loc;
arg.rebind = spec->rebind;
Expand All @@ -2965,7 +2984,6 @@ class ResolveSignaturesWalk {
}

// We silence the "type not specified" error when a sig does not mention the synthesized block arg.
bool isBlkArg = arg.name == core::Names::blkArg();
if (!isOverloaded && !isBlkArg && (sig.seen.params || sig.seen.returns || sig.seen.void_)) {
// Only error if we have any types
if (auto e = ctx.state.beginError(arg.loc, core::errors::Resolver::InvalidMethodSignature)) {
Expand Down
24 changes: 21 additions & 3 deletions resolver/type_syntax.cc
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,10 @@ ParsedSig parseSigWithSelfTypeParams(core::Context ctx, const ast::Send &sigSend
sig.origSend = const_cast<ast::Send *>(&sigSend);

const ast::Send *send = nullptr;
bool isProc = false;
if (isTProc(ctx, &sigSend)) {
send = &sigSend;
isProc = true;
} else {
sig.seen.sig = true;
ENFORCE(sigSend.fun == core::Names::sig());
Expand Down Expand Up @@ -290,6 +292,14 @@ ParsedSig parseSigWithSelfTypeParams(core::Context ctx, const ast::Send &sigSend
sig.bind = appType->klass;
validBind = true;
}
} else if (auto arg = ast::cast_tree<ast::Send>(send->getPosArg(0))) {
if (arg->fun == core::Names::selfType()) {
sig.bind = core::Symbols::BindToSelfType();
validBind = true;
} else if (arg->fun == core::Names::attachedClass()) {
sig.bind = core::Symbols::BindToAttachedClass();
validBind = true;
}
}

if (!validBind) {
Expand Down Expand Up @@ -361,8 +371,16 @@ ParsedSig parseSigWithSelfTypeParams(core::Context ctx, const ast::Send &sigSend
auto *lit = ast::cast_tree<ast::Literal>(key);
if (lit && lit->isSymbol(ctx)) {
core::NameRef name = lit->asSymbol(ctx);
auto resultAndBind =
getResultTypeAndBindWithSelfTypeParams(ctx, value, *parent, args.withRebind());
TypeSyntax::ResultType resultAndBind;

if (isProc) {
resultAndBind =
getResultTypeAndBindWithSelfTypeParams(ctx, value, *parent, args.withoutRebind());
} else {
resultAndBind =
getResultTypeAndBindWithSelfTypeParams(ctx, value, *parent, args.withRebind());
}

sig.argTypes.emplace_back(ParsedSig::ArgSpec{core::Loc(ctx.file, key.loc()), name,
resultAndBind.type, resultAndBind.rebind});
}
Expand Down Expand Up @@ -991,7 +1009,7 @@ TypeSyntax::ResultType getResultTypeAndBindWithSelfTypeParams(core::Context ctx,
},
[&](const ast::Send &s) {
if (isTProc(ctx, &s)) {
auto sig = parseSigWithSelfTypeParams(ctx, s, &sigBeingParsed, args.withoutSelfType());
auto sig = parseSigWithSelfTypeParams(ctx, s, &sigBeingParsed, args);
if (sig.bind.exists()) {
if (!args.allowRebind) {
if (auto e = ctx.beginError(s.loc, core::errors::Resolver::InvalidTypeDeclaration)) {
Expand Down
23 changes: 23 additions & 0 deletions test/testdata/resolver/bad_generic_bind.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# typed: true

class TypeTemplate
extend T::Sig
extend T::Generic

Elem = type_template

sig { params(block: T.proc.bind(Elem).void).void }
# ^^^^^^^^^^^^^^^^^ error: Malformed `bind`: Can only bind to simple class names
def self.before_create(&block); end
end

class TypeMember
extend T::Sig
extend T::Generic

Elem = type_member

sig { params(block: T.proc.bind(Elem).void).void }
# ^^^^^^^^^^^^^^^^^ error: Malformed `bind`: Can only bind to simple class names
def before_create(&block); end
end
14 changes: 14 additions & 0 deletions test/testdata/resolver/bind.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,22 @@ def double_bind; 1; end
sig {bind(Bar).returns(Integer)}
def bar; 1; end

sig {bind(T.attached_class).returns(Integer)}
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Using `bind` is not permitted here
def self.bar; 1; end

sig {bind(T.any(Integer, String)).void} # error: Malformed `bind`: Can only bind to simple class names
def too_complex; end

sig {params(blk: T.proc.params(x: T.proc.bind(T.attached_class).void).void).void}
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Using `bind` is not permitted here
def self.lambda_argument(&blk); end

sig {returns(T.proc.returns(T.proc.bind(T.attached_class).void))}
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Using `bind` is not permitted here
def self.lambda_returns
T.unsafe(nil)
end
end

class Bar
Expand Down
48 changes: 48 additions & 0 deletions test/testdata/resolver/bind_attached_class.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# typed: true

class Base
extend T::Sig

sig { params(block: T.proc.bind(T.attached_class).void).void }
def self.before_create(&block); end

before_create do
T.reveal_type(self) # error: Revealed type: `Base`
end
end

Base.before_create do
T.reveal_type(self) # error: Revealed type: `Base`
end

class Model < Base
before_create do
T.reveal_type(self) # error: Revealed type: `Model`

does_exist
does_not_exist # error: Method `does_not_exist` does not exist on `Model`
end

def does_exist; end
end

Model.before_create do
T.reveal_type(self) # error: Revealed type: `Model`

does_exist
does_not_exist # error: Method `does_not_exist` does not exist on `Model`
end

class BadBinds
extend T::Sig

sig {params(it: T.proc.bind(T.attached_class).void).void}
# ^^ error: Using `bind` is not permitted here
def self.non_block_argument(it); end

sig {returns(T.proc.bind(T.attached_class).void)}
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Using `bind` is not permitted here
def self.attached_class_return
T.unsafe(nil)
end
end
75 changes: 75 additions & 0 deletions test/testdata/resolver/bind_self_type.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# typed: true

module Mixin
extend T::Sig

sig { params(block: T.proc.bind(T.self_type).void).void }
def foo(&block); end
end

class IncludesMixin
include Mixin

def does_exist; end

def bar
foo do
T.reveal_type(self) # error: Revealed type: `IncludesMixin`

does_exist
does_not_exist # error: Method `does_not_exist` does not exist on `IncludesMixin`
end
end
end

IncludesMixin.new.foo do
T.reveal_type(self) # error: Revealed type: `IncludesMixin`

does_exist
does_not_exist # error: Method `does_not_exist` does not exist on `IncludesMixin`
end

class Base
extend T::Sig

sig { params(block: T.proc.bind(T.self_type).void).void }
def self.foo(&block); end

sig { params(block: T.proc.bind(T.self_type).void).void }
def instance_foo(&block); end
end

class Model < Base
def self.does_exist; end
def does_exist; end

foo do
T.reveal_type(self) # error: Revealed type: `T.class_of(Model)`

does_exist
does_not_exist # error: Method `does_not_exist` does not exist on `T.class_of(Model)`
end

def bar
instance_foo do
T.reveal_type(self) # error: Revealed type: `Model`

does_exist
does_not_exist # error: Method `does_not_exist` does not exist on `Model`
end
end
end

Model.foo do
T.reveal_type(self) # error: Revealed type: `T.class_of(Model)`

does_exist
does_not_exist # error: Method `does_not_exist` does not exist on `T.class_of(Model)`
end

Model.new.instance_foo do
T.reveal_type(self) # error: Revealed type: `Model`

does_exist
does_not_exist # error: Method `does_not_exist` does not exist on `Model`
end

0 comments on commit 3273564

Please sign in to comment.