Skip to content

Commit

Permalink
type as type
Browse files Browse the repository at this point in the history
Summary:
```
isinstance(list[str], type) # True
isinstance(1, type)         # False
```

Python [allows `type` function used as type](https://docs.python.org/3/library/typing.html#the-type-of-class-objects).

Antlir uses types in function signatures by (incorrectly) enumerating all possibilities.

But this is done in preparation to remove `Ty::name`.

Reviewed By: ianlevesque

Differential Revision: D48936317

fbshipit-source-id: 28622cbaf348a322095a01b8cc2f5bdf2f6994b6
  • Loading branch information
stepancheg authored and facebook-github-bot committed Sep 6, 2023
1 parent 6ffad06 commit 1548298
Show file tree
Hide file tree
Showing 12 changed files with 179 additions and 40 deletions.
3 changes: 2 additions & 1 deletion starlark/src/stdlib/funcs/other.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ use crate::values::tuple::TupleRef;
use crate::values::types::int_or_big::StarlarkInt;
use crate::values::types::int_or_big::StarlarkIntRef;
use crate::values::typing::never::StarlarkNever;
use crate::values::typing::ty::AbstractType;
use crate::values::typing::StarlarkIter;
use crate::values::value_of_unchecked::ValueOfUnchecked;
use crate::values::AllocValue;
Expand Down Expand Up @@ -862,7 +863,7 @@ pub(crate) fn register_other(builder: &mut GlobalsBuilder) {
/// type("hello") == "string"
/// # "#);
/// ```
#[starlark(speculative_exec_safe)]
#[starlark(speculative_exec_safe, as_type = AbstractType)]
fn r#type<'v>(#[starlark(require = pos)] a: Value) -> anyhow::Result<FrozenStringValue> {
Ok(a.get_type_value())
}
Expand Down
54 changes: 36 additions & 18 deletions starlark/src/tests/docs/rustdocs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,41 +17,59 @@

use std::collections::HashMap;

use allocative::Allocative;
use starlark_derive::starlark_module;
use starlark_derive::starlark_value;
use starlark_derive::NoSerialize;
use starlark_derive::ProvidesStaticType;
use starlark_map::small_map::SmallMap;

use crate as starlark;
use crate::assert;
use crate::assert::Assert;
use crate::docs::DocItem;
use crate::docs::DocMember;
use crate::environment::GlobalsBuilder;
use crate::eval::Arguments;
use crate::eval::Evaluator;
use crate::typing::Ty;
use crate::values::none::NoneType;
use crate::values::type_repr::StarlarkTypeRepr;
use crate::values::starlark_value_as_type::StarlarkValueAsType;
use crate::values::Heap;
use crate::values::StarlarkValue;
use crate::values::StringValue;
use crate::values::Value;
use crate::values::ValueOfUnchecked;

#[derive(
Debug,
derive_more::Display,
Allocative,
NoSerialize,
ProvidesStaticType
)]
#[display(fmt = "input")]
struct InputTypeRepr;
#[derive(
Debug,
derive_more::Display,
Allocative,
NoSerialize,
ProvidesStaticType
)]
#[display(fmt = "output")]
struct OutputTypeRepr;

impl StarlarkTypeRepr for InputTypeRepr {
fn starlark_type_repr() -> Ty {
Ty::name_static("input")
}
}
impl StarlarkTypeRepr for OutputTypeRepr {
fn starlark_type_repr() -> Ty {
Ty::name_static("output")
}
}
#[starlark_value(type = "input")]
impl<'v> StarlarkValue<'v> for InputTypeRepr {}

#[starlark_value(type = "output")]
impl<'v> StarlarkValue<'v> for OutputTypeRepr {}

#[starlark_module]
#[allow(unused_variables)] // Since this is for a test
fn globals(builder: &mut GlobalsBuilder) {
const Input: StarlarkValueAsType<InputTypeRepr> = StarlarkValueAsType::new();
const Output: StarlarkValueAsType<OutputTypeRepr> = StarlarkValueAsType::new();

fn simple(
arg_int: i32,
arg_bool: bool,
Expand Down Expand Up @@ -97,18 +115,18 @@ fn globals(builder: &mut GlobalsBuilder) {
#[test]
fn test_rustdoc() {
let got = GlobalsBuilder::new().with(globals).build();
let expected = assert::pass_module(
r#"
let mut a = Assert::new();
a.globals_add(globals);
let expected = a.pass_module(r#"
# @starlark-rust: allow_string_literals_in_type_expr
def args_kwargs(*args, **kwargs: typing.Any) -> None: pass
def custom_types(arg1: str, arg2: "input") -> "output": pass
def custom_types(arg1: str, arg2: Input) -> Output: pass
def default_arg(arg1 = "_", arg2: typing.Any = None) -> list[str]: pass
def pos_named(arg1: int, *, arg2: int) -> int: pass
def simple(arg_int: int, arg_bool: bool, arg_vec: list[str], arg_dict: dict[str, (bool, int)]) -> None: pass
def with_arguments(*args, **kwargs) -> int: pass
"#,
);
"#);

fn unpack(x: DocItem) -> HashMap<String, DocItem> {
match x {
Expand Down
4 changes: 4 additions & 0 deletions starlark/src/typing/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ pub enum TyBasic {
Iter(ArcTy),
/// `typing.Callable`.
Callable,
/// `type`.
Type,
/// A list.
List(ArcTy),
/// A tuple. May be empty, to indicate the empty tuple.
Expand Down Expand Up @@ -120,6 +122,7 @@ impl TyBasic {
TyBasic::List(_) => Some("list"),
TyBasic::Tuple(_) => Some("tuple"),
TyBasic::Dict(..) => Some("dict"),
TyBasic::Type => Some("type"),
TyBasic::Custom(c) => c.as_name(),
TyBasic::Any | TyBasic::Iter(_) | TyBasic::Callable => None,
}
Expand Down Expand Up @@ -169,6 +172,7 @@ impl Display for TyBasic {
TyBasic::List(x) => write!(f, "list[{}]", x),
TyBasic::Tuple(tuple) => Display::fmt(tuple, f),
TyBasic::Dict(k, v) => write!(f, "dict[{}, {}]", k, v),
TyBasic::Type => write!(f, "type"),
TyBasic::Custom(c) => Display::fmt(c, f),
}
}
Expand Down
16 changes: 12 additions & 4 deletions starlark/src/typing/oracle/ctx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ impl<'a> TypingOracleCtx<'a> {
ty: fun.to_string(),
},
)),
TyBasic::Iter(_) => {
TyBasic::Iter(_) | TyBasic::Type => {
// Unknown type, may be callable.
Ok(Ty::any())
}
Expand Down Expand Up @@ -349,6 +349,7 @@ impl<'a> TypingOracleCtx<'a> {
TyBasic::Dict(k, _v) => Ok((**k).dupe()),
TyBasic::Tuple(tuple) => Ok(tuple.item_ty()),
TyBasic::Callable => Ok(Ty::any()),
TyBasic::Type => Ok(Ty::any()),
TyBasic::Iter(ty) => Ok(ty.to_ty()),
TyBasic::Custom(ty) => ty.0.iter_item_dyn(),
TyBasic::Name(_) => Ok(Ty::any()),
Expand All @@ -374,7 +375,9 @@ impl<'a> TypingOracleCtx<'a> {
index: Spanned<&TyBasic>,
) -> Result<Result<Ty, ()>, InternalError> {
match array {
TyBasic::Any | TyBasic::Callable | TyBasic::Iter(_) => Ok(Ok(Ty::any())),
TyBasic::Any | TyBasic::Callable | TyBasic::Iter(_) | TyBasic::Type => {
Ok(Ok(Ty::any()))
}
TyBasic::Tuple(tuple) => {
if !self.intersects_basic(index.node, &TyBasic::int()) {
return Ok(Err(()));
Expand Down Expand Up @@ -467,7 +470,7 @@ impl<'a> TypingOracleCtx<'a> {

fn expr_dot_basic(&self, array: &TyBasic, attr: &str) -> Result<Ty, ()> {
match array {
TyBasic::Any | TyBasic::Callable | TyBasic::Iter(_) => Ok(Ty::any()),
TyBasic::Any | TyBasic::Callable | TyBasic::Iter(_) | TyBasic::Type => Ok(Ty::any()),
TyBasic::StarlarkValue(s) => s.attr(attr),
TyBasic::Tuple(_) => Err(()),
TyBasic::List(elem) => match attr {
Expand Down Expand Up @@ -567,7 +570,7 @@ impl<'a> TypingOracleCtx<'a> {
rhs: Spanned<&TyBasic>,
) -> Result<Ty, ()> {
match lhs {
TyBasic::Any | TyBasic::Iter(_) | TyBasic::Callable => Ok(Ty::any()),
TyBasic::Any | TyBasic::Iter(_) | TyBasic::Callable | TyBasic::Type => Ok(Ty::any()),
TyBasic::StarlarkValue(lhs) => lhs.bin_op(bin_op, rhs.node),
lhs @ TyBasic::List(elem) => match bin_op {
TypingBinOp::Less => {
Expand Down Expand Up @@ -846,6 +849,11 @@ impl<'a> TypingOracleCtx<'a> {
Err(()) => false,
},
(TyBasic::Custom(x), y) => x.intersects_with(y),
(TyBasic::Type, TyBasic::StarlarkValue(y)) => y.is_type(),
(TyBasic::Type, _) => {
// TODO(nga): more precise.
true
}
(x, y) if x.is_function() && y.is_function() => true,
// There are lots of other cases that overlap, but add them as we need them
_ => false,
Expand Down
11 changes: 11 additions & 0 deletions starlark/src/typing/starlark_value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,17 @@ impl TyStarlarkValue {
vtable.HAS_iterate || vtable.HAS_iterate_collect
}

/// Instance of this type can be evaluated as a type.
#[inline]
pub(crate) fn is_type_from_vtable(vtable: &StarlarkValueVTable) -> bool {
vtable.HAS_eval_type
}

pub(crate) fn is_type(self) -> bool {
self.self_check();
Self::is_type_from_vtable(&self.vtable.vtable)
}

pub(crate) fn iter_item(self) -> Result<Ty, ()> {
if Self::is_iterable(&self.vtable.vtable) {
Ok(Ty::any())
Expand Down
13 changes: 0 additions & 13 deletions starlark/src/typing/ty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,6 @@ impl TyName {
TyName(s.into())
}

pub(crate) fn new_static(s: &'static str) -> TyName {
TyName(ArcStr::new_static(s))
}

/// Get the underlying `str` for a `TyName`.
pub fn as_str(&self) -> &str {
&self.0
Expand Down Expand Up @@ -190,15 +186,6 @@ impl Ty {
}
}

/// Create a [`Ty::Name`], or one of the standard functions.
pub(crate) fn name_static(name: &'static str) -> Self {
if let Some(x) = Self::try_name_special(name) {
x
} else {
Ty::basic(TyBasic::Name(TyName::new_static(name)))
}
}

pub(crate) fn name(name: &str) -> Self {
if let Some(x) = Self::try_name_special(name) {
x
Expand Down
6 changes: 4 additions & 2 deletions starlark/src/values/types/starlark_value_as_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ use crate::values::layout::avalue::AValueImpl;
use crate::values::layout::avalue::Basic;
use crate::values::layout::heap::repr::AValueRepr;
use crate::values::type_repr::StarlarkTypeRepr;
use crate::values::typing::ty::AbstractType;
use crate::values::AllocFrozenValue;
use crate::values::FrozenHeap;
use crate::values::FrozenValue;
Expand All @@ -45,6 +46,8 @@ struct StarlarkValueAsTypeStarlarkValue(fn() -> Ty);

#[starlark_value(type = "type")]
impl<'v> StarlarkValue<'v> for StarlarkValueAsTypeStarlarkValue {
type Canonical = AbstractType;

fn eval_type(&self) -> Option<Ty> {
Some((self.0)())
}
Expand Down Expand Up @@ -118,8 +121,7 @@ impl<T: StarlarkTypeRepr> Default for StarlarkValueAsType<T> {

impl<T: StarlarkTypeRepr> StarlarkTypeRepr for StarlarkValueAsType<T> {
fn starlark_type_repr() -> Ty {
// TODO(nga): make it proper type.
Ty::name_static("type")
AbstractType::starlark_type_repr()
}
}

Expand Down
1 change: 1 addition & 0 deletions starlark/src/values/typing/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ pub(crate) mod globals;
pub(crate) mod iter;
pub mod macro_refs;
pub(crate) mod never;
pub(crate) mod ty;
pub(crate) mod type_compiled;

pub use crate::values::types::type_instance_id::TypeInstanceId;
Expand Down
96 changes: 96 additions & 0 deletions starlark/src/values/typing/ty.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Copyright 2019 The Starlark in Rust Authors.
* Copyright (c) Facebook, Inc. and its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

use allocative::Allocative;
use starlark_derive::starlark_value;
use starlark_derive::NoSerialize;
use starlark_derive::ProvidesStaticType;

use crate as starlark;
use crate::typing::Ty;
use crate::typing::TyBasic;
use crate::values::StarlarkValue;

/// Type of type.
#[derive(
Debug,
derive_more::Display,
Allocative,
ProvidesStaticType,
NoSerialize
)]
#[display(fmt = "type")]
#[allocative(skip)] // TODO(nga): derive.
pub enum AbstractType {}

#[starlark_value(type = "type")]
impl<'v> StarlarkValue<'v> for AbstractType {
fn get_type_starlark_repr() -> Ty {
Ty::basic(TyBasic::Type)
}

fn eval_type(&self) -> Option<Ty> {
unreachable!(
"This is unreachable, but this function is needed \
so `TyStarlarkValue` could think this is a type"
)
}
}

#[cfg(test)]
mod tests {
use crate::assert;

#[test]
fn test_isinstance() {
assert::is_true("isinstance(int, type)");
assert::is_false("isinstance(1, type)");
assert::is_true("isinstance(list[str], type)");
assert::is_true("isinstance(eval_type(list), type)");
}

#[test]
fn test_pass() {
assert::pass(
r#"
def accepts_type(t: type):
pass
def test():
accepts_type(int)
accepts_type(list[str])
accepts_type(None | int)
test()
"#,
);
}

#[test]
fn test_fail_compile_time() {
assert::fail(
r#"
def accepts_type(t: type):
pass
def test():
accepts_type(1)
"#,
"Expected type `type` but got `int`",
);
}
}
2 changes: 2 additions & 0 deletions starlark/src/values/typing/type_compiled/alloc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ use crate::values::typing::type_compiled::matchers::IsName;
use crate::values::typing::type_compiled::matchers::IsNever;
use crate::values::typing::type_compiled::matchers::IsNone;
use crate::values::typing::type_compiled::matchers::IsStr;
use crate::values::typing::type_compiled::matchers::IsType;
use crate::values::typing::type_compiled::matchers::StarlarkTypeIdMatcher;
use crate::values::typing::type_compiled::type_matcher_factory::TypeMatcherFactory;

Expand Down Expand Up @@ -147,6 +148,7 @@ pub trait TypeMatcherAlloc: Sized {
TyBasic::Dict(k, v) => self.dict_of(k, v),
TyBasic::Iter(_item) => self.alloc(IsIterable),
TyBasic::Callable => self.alloc(IsCallable),
TyBasic::Type => self.alloc(IsType),
TyBasic::Custom(custom) => self.custom(custom),
}
}
Expand Down
Loading

0 comments on commit 1548298

Please sign in to comment.