Skip to content

Commit

Permalink
Implement literal types (#978)
Browse files Browse the repository at this point in the history
I got something that *compiles* and parses literals into the AST. Lots
of `todo!()`s left.
<!-- ELLIPSIS_HIDDEN -->

----

> [!IMPORTANT]
> Implement literal types in the codebase, including coercion,
validation, and code generation updates for Python, Ruby, and
TypeScript.
> 
>   - **Behavior**:
>     - Implement literal types in `FieldType` and `LiteralValue` enums.
>     - Add coercion logic for literals in `coerce_literal.rs`.
>     - Update `to_baml_arg.rs` to handle literal values.
>     - Modify `json_schema.rs` to support literal types in JSON schema.
>     - Update `repr.rs` to include literal types in representation.
>   - **Validation**:
>     - Add validation for literal types in `types.rs`.
>     - Introduce tests for literals in `test_literals.rs`.
>   - **Code Generation**:
> - Update Python, Ruby, and TypeScript code generation to support
literals.
>     - Modify OpenAPI generation to include literal types.
>   - **Misc**:
>     - Add new test files for validation of literal types.
>     - Update existing test files to include literal type scenarios.
> 
> <sup>This description was created by </sup>[<img alt="Ellipsis"
src="https://img.shields.io/badge/Ellipsis-blue?color=175173">](https://www.ellipsis.dev?ref=BoundaryML%2Fbaml&utm_source=github&utm_medium=referral)<sup>
for a14b308. It will automatically
update as commits are pushed.</sup>

<!-- ELLIPSIS_HIDDEN -->

---------

Co-authored-by: Greg Hale <imalsogreg@gmail.com>
Co-authored-by: Vaibhav Gupta <vbv@boundaryml.com>
  • Loading branch information
3 people authored Oct 11, 2024
1 parent d0cdf90 commit 9e7431f
Show file tree
Hide file tree
Showing 62 changed files with 859 additions and 274 deletions.
13 changes: 12 additions & 1 deletion engine/baml-lib/baml-core/src/ir/ir_helpers/to_baml_arg.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use baml_types::{BamlMap, BamlMediaType, BamlValue, FieldType, TypeValue};
use baml_types::{BamlMap, BamlMediaType, BamlValue, FieldType, LiteralValue, TypeValue};
use core::result::Result;
use std::path::PathBuf;

Expand Down Expand Up @@ -198,6 +198,17 @@ impl ArgCoercer {
Err(())
}
},
FieldType::Literal(literal) => Ok(match (literal, value) {
(LiteralValue::Int(lit), BamlValue::Int(baml)) if lit == baml => value.clone(),
(LiteralValue::String(lit), BamlValue::String(baml)) if lit == baml => {
value.clone()
}
(LiteralValue::Bool(lit), BamlValue::Bool(baml)) if lit == baml => value.clone(),
_ => {
scope.push_error(format!("Expected literal {:?}, got `{}`", literal, value));
return Err(());
}
}),
FieldType::Class(name) => match value {
BamlValue::Class(n, _) if n == name => return Ok(value.clone()),
BamlValue::Class(_, obj) | BamlValue::Map(obj) => match ir.find_class(name) {
Expand Down
3 changes: 3 additions & 0 deletions engine/baml-lib/baml-core/src/ir/json_schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,9 @@ impl<'db> WithJsonSchema for FieldType {
FieldType::Class(name) | FieldType::Enum(name) => json!({
"$ref": format!("#/definitions/{}", name),
}),
FieldType::Literal(v) => json!({
"const": v.to_string(),
}),
FieldType::Primitive(t) => match t {
TypeValue::String => json!({
"type": "string",
Expand Down
8 changes: 8 additions & 0 deletions engine/baml-lib/baml-core/src/ir/repr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,14 @@ impl WithRepr<FieldType> for ast::FieldType {
repr
}
}
ast::FieldType::Literal(arity, literal_value, ..) => {
let repr = FieldType::Literal(literal_value.clone());
if arity.is_optional() {
FieldType::Optional(Box::new(repr))
} else {
repr
}
}
ast::FieldType::Symbol(arity, idn, ..) => type_with_arity(
match db.find_type(idn) {
Some(Either::Left(class_walker)) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ fn validate_type_allowed(ctx: &mut Context<'_>, field_type: &FieldType) {
}

FieldType::Primitive(..) => {}
FieldType::Literal(..) => {}
FieldType::Symbol(..) => {}

FieldType::List(arity, field_type, ..) => {
Expand Down
30 changes: 30 additions & 0 deletions engine/baml-lib/baml-types/src/field_type/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,40 @@ impl std::fmt::Display for TypeValue {
}
}

/// Subset of [`crate::BamlValue`] allowed for literal type definitions.
#[derive(serde::Serialize, Debug, Clone, PartialOrd, Ord, PartialEq, Eq)]
pub enum LiteralValue {
String(String),
Int(i64),
Bool(bool),
}

impl LiteralValue {
pub fn literal_base_type(&self) -> FieldType {
match self {
Self::String(_) => FieldType::string(),
Self::Int(_) => FieldType::int(),
Self::Bool(_) => FieldType::bool(),
}
}
}

impl std::fmt::Display for LiteralValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
LiteralValue::String(str) => write!(f, "\"{str}\""),
LiteralValue::Int(int) => write!(f, "{int}"),
LiteralValue::Bool(bool) => write!(f, "{bool}"),
}
}
}

/// FieldType represents the type of either a class field or a function arg.
#[derive(serde::Serialize, Debug, Clone)]
pub enum FieldType {
Primitive(TypeValue),
Enum(String),
Literal(LiteralValue),
Class(String),
List(Box<FieldType>),
Map(Box<FieldType>, Box<FieldType>),
Expand All @@ -61,6 +90,7 @@ impl std::fmt::Display for FieldType {
write!(f, "{}", name)
}
FieldType::Primitive(t) => write!(f, "{}", t),
FieldType::Literal(v) => write!(f, "{}", v),
FieldType::Union(choices) => {
write!(
f,
Expand Down
2 changes: 1 addition & 1 deletion engine/baml-lib/baml-types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ mod field_type;
mod generator;

pub use baml_value::BamlValue;
pub use field_type::{FieldType, TypeValue};
pub use field_type::{FieldType, LiteralValue, TypeValue};
pub use generator::{GeneratorDefaultClientMode, GeneratorOutputType};
pub use map::Map as BamlMap;
pub use media::{BamlMedia, BamlMediaContent, BamlMediaType, MediaBase64, MediaUrl};
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class TestLiterals {
a "SingleLiteral"
b "Field" | "With" | "Multiple" | "Literals"
c "Field" | "With" | "Some" @description("Description")
d 2 | 3
e "Optional"?
f 2 | "SomeString"
g "boolean" | true | false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
class TestLiterals {
a "SingleLiteral"
b "boolean" | True | False
}

// error: Type `True` does not exist. Did you mean one of these: `true`, `string`, `int`, `bool`, `false`, `float`, `TestLiterals`?
// --> class/misspeled_boolean_literals.baml:3
// |
// 2 | a "SingleLiteral"
// 3 | b "boolean" | True | False
// |
// error: Type `False` does not exist. Did you mean one of these: `false`, `float`, `true`, `int`, `bool`, `string`, `TestLiterals`?
// --> class/misspeled_boolean_literals.baml:3
// |
// 2 | a "SingleLiteral"
// 3 | b "boolean" | True | False
// |
62 changes: 31 additions & 31 deletions engine/baml-lib/baml/tests/validation_files/class/secure_types.baml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class ComplexTypes {
q map<string, string>?
}

// error: Type `apple_pie` does not exist. Did you mean one of these: `ComplexTypes`, `float`, `bool`, `string`, `int`?
// error: Type `apple_pie` does not exist. Did you mean one of these: `false`, `ComplexTypes`, `float`, `bool`, `true`, `string`, `int`?
// --> class/secure_types.baml:3
// |
// 2 | class ComplexTypes {
Expand All @@ -31,13 +31,13 @@ class ComplexTypes {
// 2 | class ComplexTypes {
// 3 | a map<string[], (int | bool[]) | apple_pie[][]>
// |
// error: Type `char` does not exist. Did you mean one of these: `int`, `float`, `bool`, `string`, `ComplexTypes`?
// error: Type `char` does not exist. Did you mean one of these: `int`, `float`, `bool`, `true`, `false`, `string`, `ComplexTypes`?
// --> class/secure_types.baml:4
// |
// 3 | a map<string[], (int | bool[]) | apple_pie[][]>
// 4 | b (int, map<bool, string?>, (char | float)[][] | long_word_123.foobar[])
// |
// error: Type `long_word_123.foobar` does not exist. Did you mean one of these: `float`, `bool`, `ComplexTypes`, `string`, `int`?
// error: Type `long_word_123.foobar` does not exist. Did you mean one of these: `float`, `bool`, `ComplexTypes`, `string`, `int`, `true`, `false`?
// --> class/secure_types.baml:4
// |
// 3 | a map<string[], (int | bool[]) | apple_pie[][]>
Expand All @@ -49,25 +49,25 @@ class ComplexTypes {
// 3 | a map<string[], (int | bool[]) | apple_pie[][]>
// 4 | b (int, map<bool, string?>, (char | float)[][] | long_word_123.foobar[])
// |
// error: Type `apple123_456_pie` does not exist. Did you mean one of these: `ComplexTypes`, `float`, `bool`, `string`, `int`?
// error: Type `apple123_456_pie` does not exist. Did you mean one of these: `ComplexTypes`, `false`, `float`, `bool`, `true`, `string`, `int`?
// --> class/secure_types.baml:5
// |
// 4 | b (int, map<bool, string?>, (char | float)[][] | long_word_123.foobar[])
// 5 | c apple123_456_pie | (stringer, bool[], (int | char))[]
// |
// error: Type `stringer` does not exist. Did you mean one of these: `string`, `int`, `float`, `bool`, `ComplexTypes`?
// error: Type `stringer` does not exist. Did you mean one of these: `string`, `true`, `int`, `false`, `float`, `bool`, `ComplexTypes`?
// --> class/secure_types.baml:5
// |
// 4 | b (int, map<bool, string?>, (char | float)[][] | long_word_123.foobar[])
// 5 | c apple123_456_pie | (stringer, bool[], (int | char))[]
// |
// error: Type `char` does not exist. Did you mean one of these: `int`, `float`, `bool`, `string`, `ComplexTypes`?
// error: Type `char` does not exist. Did you mean one of these: `int`, `float`, `bool`, `true`, `false`, `string`, `ComplexTypes`?
// --> class/secure_types.baml:5
// |
// 4 | b (int, map<bool, string?>, (char | float)[][] | long_word_123.foobar[])
// 5 | c apple123_456_pie | (stringer, bool[], (int | char))[]
// |
// error: Type `char` does not exist. Did you mean one of these: `int`, `float`, `bool`, `string`, `ComplexTypes`?
// error: Type `char` does not exist. Did you mean one of these: `int`, `float`, `bool`, `true`, `false`, `string`, `ComplexTypes`?
// --> class/secure_types.baml:6
// |
// 5 | c apple123_456_pie | (stringer, bool[], (int | char))[]
Expand All @@ -79,19 +79,19 @@ class ComplexTypes {
// 5 | c apple123_456_pie | (stringer, bool[], (int | char))[]
// 6 | d map<int[][], ((int | float) | char[])>
// |
// error: Type `char` does not exist. Did you mean one of these: `int`, `float`, `bool`, `string`, `ComplexTypes`?
// error: Type `char` does not exist. Did you mean one of these: `int`, `float`, `bool`, `true`, `false`, `string`, `ComplexTypes`?
// --> class/secure_types.baml:7
// |
// 6 | d map<int[][], ((int | float) | char[])>
// 7 | e ((int, string | char) | ((float, double) | long[], bool)[][][])
// |
// error: Type `double` does not exist. Did you mean one of these: `bool`, `string`, `int`, `float`, `ComplexTypes`?
// error: Type `double` does not exist. Did you mean one of these: `bool`, `true`, `false`, `string`, `int`, `float`, `ComplexTypes`?
// --> class/secure_types.baml:7
// |
// 6 | d map<int[][], ((int | float) | char[])>
// 7 | e ((int, string | char) | ((float, double) | long[], bool)[][][])
// |
// error: Type `long` does not exist. Did you mean one of these: `int`, `float`, `bool`, `string`, `ComplexTypes`?
// error: Type `long` does not exist. Did you mean one of these: `int`, `float`, `bool`, `string`, `true`, `false`, `ComplexTypes`?
// --> class/secure_types.baml:7
// |
// 6 | d map<int[][], ((int | float) | char[])>
Expand All @@ -103,19 +103,19 @@ class ComplexTypes {
// 7 | e ((int, string | char) | ((float, double) | long[], bool)[][][])
// 8 | f VeryLongWord_With_123_Numbers[][][][]
// |
// error: Type `char` does not exist. Did you mean one of these: `int`, `float`, `bool`, `string`, `ComplexTypes`?
// error: Type `char` does not exist. Did you mean one of these: `int`, `float`, `bool`, `true`, `false`, `string`, `ComplexTypes`?
// --> class/secure_types.baml:9
// |
// 8 | f VeryLongWord_With_123_Numbers[][][][]
// 9 | g (int, (float, char, bool), string[]) | tuple_inside_tuple[]
// |
// error: Type `tuple_inside_tuple` does not exist. Did you mean one of these: `ComplexTypes`, `int`, `string`, `float`, `bool`?
// error: Type `tuple_inside_tuple` does not exist. Did you mean one of these: `ComplexTypes`, `int`, `true`, `false`, `string`, `float`, `bool`?
// --> class/secure_types.baml:9
// |
// 8 | f VeryLongWord_With_123_Numbers[][][][]
// 9 | g (int, (float, char, bool), string[]) | tuple_inside_tuple[]
// |
// error: Type `char` does not exist. Did you mean one of these: `int`, `float`, `bool`, `string`, `ComplexTypes`?
// error: Type `char` does not exist. Did you mean one of these: `int`, `float`, `bool`, `true`, `false`, `string`, `ComplexTypes`?
// --> class/secure_types.baml:10
// |
// 9 | g (int, (float, char, bool), string[]) | tuple_inside_tuple[]
Expand All @@ -127,55 +127,55 @@ class ComplexTypes {
// 9 | g (int, (float, char, bool), string[]) | tuple_inside_tuple[]
// 10 | h (((int | string)[]) | map<bool[][], char[]>)
// |
// error: Type `apple` does not exist. Did you mean one of these: `bool`, `int`, `float`, `string`, `ComplexTypes`?
// error: Type `apple` does not exist. Did you mean one of these: `bool`, `true`, `false`, `int`, `float`, `string`, `ComplexTypes`?
// --> class/secure_types.baml:11
// |
// 10 | h (((int | string)[]) | map<bool[][], char[]>)
// 11 | i (apple, banana | cherry | date_fruit | eggplant_vegetable)[]
// |
// error: Type `banana` does not exist. Did you mean one of these: `string`, `int`, `float`, `bool`, `ComplexTypes`?
// error: Type `banana` does not exist. Did you mean one of these: `string`, `int`, `float`, `bool`, `false`, `true`, `ComplexTypes`?
// --> class/secure_types.baml:11
// |
// 10 | h (((int | string)[]) | map<bool[][], char[]>)
// 11 | i (apple, banana | cherry | date_fruit | eggplant_vegetable)[]
// |
// error: Type `cherry` does not exist. Did you mean one of these: `string`, `int`, `float`, `bool`, `ComplexTypes`?
// error: Type `cherry` does not exist. Did you mean one of these: `true`, `string`, `int`, `float`, `bool`, `false`, `ComplexTypes`?
// --> class/secure_types.baml:11
// |
// 10 | h (((int | string)[]) | map<bool[][], char[]>)
// 11 | i (apple, banana | cherry | date_fruit | eggplant_vegetable)[]
// |
// error: Type `date_fruit` does not exist. Did you mean one of these: `string`, `float`, `int`, `bool`, `ComplexTypes`?
// error: Type `date_fruit` does not exist. Did you mean one of these: `true`, `string`, `float`, `int`, `false`, `bool`, `ComplexTypes`?
// --> class/secure_types.baml:11
// |
// 10 | h (((int | string)[]) | map<bool[][], char[]>)
// 11 | i (apple, banana | cherry | date_fruit | eggplant_vegetable)[]
// |
// error: Type `eggplant_vegetable` does not exist. Did you mean one of these: `ComplexTypes`, `string`, `int`, `float`, `bool`?
// error: Type `eggplant_vegetable` does not exist. Did you mean one of these: `ComplexTypes`, `string`, `int`, `float`, `true`, `false`, `bool`?
// --> class/secure_types.baml:11
// |
// 10 | h (((int | string)[]) | map<bool[][], char[]>)
// 11 | i (apple, banana | cherry | date_fruit | eggplant_vegetable)[]
// |
// error: Type `char` does not exist. Did you mean one of these: `int`, `float`, `bool`, `string`, `ComplexTypes`?
// error: Type `char` does not exist. Did you mean one of these: `int`, `float`, `bool`, `true`, `false`, `string`, `ComplexTypes`?
// --> class/secure_types.baml:12
// |
// 11 | i (apple, banana | cherry | date_fruit | eggplant_vegetable)[]
// 12 | j ((char, int[][], (bool | string[][])) | double[][][][], (float, int)[])
// |
// error: Type `double` does not exist. Did you mean one of these: `bool`, `string`, `int`, `float`, `ComplexTypes`?
// error: Type `double` does not exist. Did you mean one of these: `bool`, `true`, `false`, `string`, `int`, `float`, `ComplexTypes`?
// --> class/secure_types.baml:12
// |
// 11 | i (apple, banana | cherry | date_fruit | eggplant_vegetable)[]
// 12 | j ((char, int[][], (bool | string[][])) | double[][][][], (float, int)[])
// |
// error: Type `long` does not exist. Did you mean one of these: `int`, `float`, `bool`, `string`, `ComplexTypes`?
// error: Type `long` does not exist. Did you mean one of these: `int`, `float`, `bool`, `string`, `true`, `false`, `ComplexTypes`?
// --> class/secure_types.baml:13
// |
// 12 | j ((char, int[][], (bool | string[][])) | double[][][][], (float, int)[])
// 13 | k map<string[], (int | long[])> | map<float[][], double[][]>
// |
// error: Type `double` does not exist. Did you mean one of these: `bool`, `string`, `int`, `float`, `ComplexTypes`?
// error: Type `double` does not exist. Did you mean one of these: `bool`, `true`, `false`, `string`, `int`, `float`, `ComplexTypes`?
// --> class/secure_types.baml:13
// |
// 12 | j ((char, int[][], (bool | string[][])) | double[][][][], (float, int)[])
Expand All @@ -199,49 +199,49 @@ class ComplexTypes {
// 13 | k map<string[], (int | long[])> | map<float[][], double[][]>
// 14 | l AlphaNumeric_123_456_789 | (int, bool?) | char[]
// |
// error: Type `char` does not exist. Did you mean one of these: `int`, `float`, `bool`, `string`, `ComplexTypes`?
// error: Type `char` does not exist. Did you mean one of these: `int`, `float`, `bool`, `true`, `false`, `string`, `ComplexTypes`?
// --> class/secure_types.baml:14
// |
// 13 | k map<string[], (int | long[])> | map<float[][], double[][]>
// 14 | l AlphaNumeric_123_456_789 | (int, bool?) | char[]
// |
// error: Type `tuple_1` does not exist. Did you mean one of these: `float`, `bool`, `string`, `int`, `ComplexTypes`?
// error: Type `tuple_1` does not exist. Did you mean one of these: `true`, `float`, `bool`, `false`, `string`, `int`, `ComplexTypes`?
// --> class/secure_types.baml:15
// |
// 14 | l AlphaNumeric_123_456_789 | (int, bool?) | char[]
// 15 | m (tuple_1, tuple_2 | tuple_3, (tuple_4, tuple_5))[]
// |
// error: Type `tuple_2` does not exist. Did you mean one of these: `float`, `bool`, `string`, `int`, `ComplexTypes`?
// error: Type `tuple_2` does not exist. Did you mean one of these: `true`, `float`, `bool`, `false`, `string`, `int`, `ComplexTypes`?
// --> class/secure_types.baml:15
// |
// 14 | l AlphaNumeric_123_456_789 | (int, bool?) | char[]
// 15 | m (tuple_1, tuple_2 | tuple_3, (tuple_4, tuple_5))[]
// |
// error: Type `tuple_3` does not exist. Did you mean one of these: `float`, `bool`, `string`, `int`, `ComplexTypes`?
// error: Type `tuple_3` does not exist. Did you mean one of these: `true`, `float`, `bool`, `false`, `string`, `int`, `ComplexTypes`?
// --> class/secure_types.baml:15
// |
// 14 | l AlphaNumeric_123_456_789 | (int, bool?) | char[]
// 15 | m (tuple_1, tuple_2 | tuple_3, (tuple_4, tuple_5))[]
// |
// error: Type `tuple_4` does not exist. Did you mean one of these: `float`, `bool`, `string`, `int`, `ComplexTypes`?
// error: Type `tuple_4` does not exist. Did you mean one of these: `true`, `float`, `bool`, `false`, `string`, `int`, `ComplexTypes`?
// --> class/secure_types.baml:15
// |
// 14 | l AlphaNumeric_123_456_789 | (int, bool?) | char[]
// 15 | m (tuple_1, tuple_2 | tuple_3, (tuple_4, tuple_5))[]
// |
// error: Type `tuple_5` does not exist. Did you mean one of these: `float`, `bool`, `string`, `int`, `ComplexTypes`?
// error: Type `tuple_5` does not exist. Did you mean one of these: `true`, `float`, `bool`, `false`, `string`, `int`, `ComplexTypes`?
// --> class/secure_types.baml:15
// |
// 14 | l AlphaNumeric_123_456_789 | (int, bool?) | char[]
// 15 | m (tuple_1, tuple_2 | tuple_3, (tuple_4, tuple_5))[]
// |
// error: Type `another_key` does not exist. Did you mean one of these: `string`, `int`, `ComplexTypes`, `float`, `bool`?
// error: Type `another_key` does not exist. Did you mean one of these: `true`, `string`, `int`, `ComplexTypes`, `float`, `bool`, `false`?
// --> class/secure_types.baml:16
// |
// 15 | m (tuple_1, tuple_2 | tuple_3, (tuple_4, tuple_5))[]
// 16 | n map<complex_key_type[], map<another_key, (int | string[])>>
// |
// error: Type `complex_key_type` does not exist. Did you mean one of these: `ComplexTypes`, `float`, `bool`, `int`, `string`?
// error: Type `complex_key_type` does not exist. Did you mean one of these: `ComplexTypes`, `float`, `bool`, `true`, `false`, `int`, `string`?
// --> class/secure_types.baml:16
// |
// 15 | m (tuple_1, tuple_2 | tuple_3, (tuple_4, tuple_5))[]
Expand All @@ -259,7 +259,7 @@ class ComplexTypes {
// 15 | m (tuple_1, tuple_2 | tuple_3, (tuple_4, tuple_5))[]
// 16 | n map<complex_key_type[], map<another_key, (int | string[])>>
// |
// error: Type `double` does not exist. Did you mean one of these: `bool`, `string`, `int`, `float`, `ComplexTypes`?
// error: Type `double` does not exist. Did you mean one of these: `bool`, `true`, `false`, `string`, `int`, `float`, `ComplexTypes`?
// --> class/secure_types.baml:17
// |
// 16 | n map<complex_key_type[], map<another_key, (int | string[])>>
Expand Down
Loading

0 comments on commit 9e7431f

Please sign in to comment.