Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(compiler): multi-file Wing applications #3512

Merged
merged 19 commits into from
Jul 20, 2023
Merged
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
2 changes: 2 additions & 0 deletions examples/tests/invalid/bring_local_self.w
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
bring "./bring_local_self.w" as foo;
// ^ error: Cannot bring a module into itself
3 changes: 3 additions & 0 deletions examples/tests/invalid/bring_local_variables.w
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
bring "./file_with_variables.w" as stuff;

new stuff.Bar();
12 changes: 12 additions & 0 deletions examples/tests/invalid/file_with_variables.w
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// used by bring_local_variables.w

bring cloud;

let x = 5;
let y = ["hello", "world"];
let z = new cloud.Bucket();

class Bar {
x: num;
}

29 changes: 29 additions & 0 deletions examples/tests/valid/bring_local.w
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
bring "./store.w" as file1;
bring "./subdir/subfile.w" as file2;
bring "./subdir/empty.w" as file3;

// classes from other files can be used
Chriscbr marked this conversation as resolved.
Show resolved Hide resolved
let store = new file1.Store();
let q = new file2.Q();

test "add data to store" {
store.store("foo");
}

// structs from other files can be used
let s = file1.Point {
x: 1,
y: 2,
};

// enums from other files can be used
let c = file1.Color.BLUE;
assert(c != file1.Color.RED);

// interfaces from other files can be used
class Triangle impl file1.Shape {
area(): num {
return 1;
}
}
let t = new Triangle();
26 changes: 26 additions & 0 deletions examples/tests/valid/store.w
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
bring cloud;

class Store {
b: cloud.Bucket;
init() {
this.b = new cloud.Bucket();
}
inflight store(data: str) {
this.b.put("data.txt", data);
}
}

enum Color {
RED,
GREEN,
BLUE,
}

struct Point {
x: num;
y: num;
}

interface Shape {
area(): num;
}
Empty file.
1 change: 1 addition & 0 deletions examples/tests/valid/subdir/subfile.w
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
class Q {}
4 changes: 2 additions & 2 deletions libs/tree-sitter-wing/grammar.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ module.exports = grammar({
_statement: ($) =>
choice(
$.test_statement,
$.short_import_statement,
$.import_statement,
$.expression_statement,
$.variable_definition_statement,
$.variable_assignment_statement,
Expand All @@ -117,7 +117,7 @@ module.exports = grammar({
$.super_constructor_statement
),

short_import_statement: ($) =>
import_statement: ($) =>
seq(
"bring",
field("module_name", choice($.identifier, $.string)),
Expand Down
12 changes: 8 additions & 4 deletions libs/wingc/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use indexmap::{Equivalent, IndexMap, IndexSet};
use itertools::Itertools;

use crate::diagnostic::WingSpan;
use crate::type_check::symbol_env::SymbolEnv;
use crate::type_check::symbol_env::SymbolEnvRef;
use crate::type_check::CLOSURE_CLASS_HANDLE_METHOD;

static EXPR_COUNTER: AtomicUsize = AtomicUsize::new(0);
Expand Down Expand Up @@ -424,6 +424,10 @@ pub enum StmtKind {
module_name: Symbol, // Reference?
identifier: Option<Symbol>,
},
Module {
name: Symbol,
statements: Scope,
},
SuperConstructor {
arg_list: ArgList,
},
Expand Down Expand Up @@ -628,13 +632,13 @@ pub enum InterpolatedStringPart {
Expr(Expr),
}

#[derive(Derivative)]
#[derive(Derivative, Default)]
#[derivative(Debug)]
pub struct Scope {
pub statements: Vec<Stmt>,
pub span: WingSpan,
#[derivative(Debug = "ignore")]
pub env: RefCell<Option<SymbolEnv>>, // None after parsing, set to Some during type checking phase
pub env: RefCell<Option<SymbolEnvRef>>, // None after parsing, set to Some during type checking phase
}

impl Scope {
Expand All @@ -646,7 +650,7 @@ impl Scope {
}
}

pub fn set_env(&self, new_env: SymbolEnv) {
pub fn set_env(&self, new_env: SymbolEnvRef) {
let mut env = self.env.borrow_mut();
assert!((*env).is_none());
*env = Some(new_env);
Expand Down
4 changes: 4 additions & 0 deletions libs/wingc/src/fold.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ where
module_name: f.fold_symbol(module_name),
identifier: identifier.map(|id| f.fold_symbol(id)),
},
StmtKind::Module { name, statements } => StmtKind::Module {
Chriscbr marked this conversation as resolved.
Show resolved Hide resolved
name: f.fold_symbol(name),
statements: f.fold_scope(statements),
},
StmtKind::Let {
reassignable,
var_name,
Expand Down
65 changes: 58 additions & 7 deletions libs/wingc/src/jsify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -548,13 +548,6 @@ impl<'a> JSifier<'a> {
fn jsify_statement(&self, env: &SymbolEnv, statement: &Stmt, ctx: &mut JSifyContext) -> CodeMaker {
CompilationContext::set(CompilationPhase::Jsifying, &statement.span);
match &statement.kind {
StmtKind::SuperConstructor { arg_list } => {
let args = self.jsify_arg_list(&arg_list, None, None, ctx);
match ctx.phase {
Phase::Preflight => CodeMaker::one_line(format!("super(scope,id,{});", args)),
_ => CodeMaker::one_line(format!("super({});", args)),
}
}
StmtKind::Bring {
module_name,
identifier,
Expand All @@ -578,6 +571,27 @@ impl<'a> JSifier<'a> {
}
))
}
StmtKind::Module { name, statements } => {
let mut code = CodeMaker::default();
Chriscbr marked this conversation as resolved.
Show resolved Hide resolved
code.open(format!("const {} = (() => {{", name.name));
code.add_code(self.jsify_scope_body(statements, ctx));

let exports = get_public_symbols(statements);
code.line(format!(
"return {{ {} }};",
exports.iter().map(ToString::to_string).join(", ")
));

code.close("})();");
code
Chriscbr marked this conversation as resolved.
Show resolved Hide resolved
}
StmtKind::SuperConstructor { arg_list } => {
let args = self.jsify_arg_list(&arg_list, None, None, ctx);
match ctx.phase {
Phase::Preflight => CodeMaker::one_line(format!("super(scope,id,{});", args)),
_ => CodeMaker::one_line(format!("super({});", args)),
}
}
StmtKind::Let {
reassignable,
var_name,
Expand Down Expand Up @@ -1245,6 +1259,43 @@ impl<'a> JSifier<'a> {
}
}

fn get_public_symbols(scope: &Scope) -> Vec<Symbol> {
let mut symbols = Vec::new();

for stmt in &scope.statements {
match &stmt.kind {
StmtKind::Bring { .. } => {}
StmtKind::Module { name, .. } => {
symbols.push(name.clone());
}
StmtKind::SuperConstructor { .. } => {}
StmtKind::Let { .. } => {}
StmtKind::ForLoop { .. } => {}
StmtKind::While { .. } => {}
StmtKind::IfLet { .. } => {}
StmtKind::If { .. } => {}
StmtKind::Break => {}
StmtKind::Continue => {}
StmtKind::Return(_) => {}
StmtKind::Expression(_) => {}
StmtKind::Assignment { .. } => {}
StmtKind::Scope(_) => {}
StmtKind::Class(class) => {
symbols.push(class.name.clone());
}
StmtKind::Interface(_) => {}
StmtKind::Struct { .. } => {}
StmtKind::Enum { name, .. } => {
symbols.push(name.clone());
}
StmtKind::TryCatch { .. } => {}
StmtKind::CompilerDebugEnv => {}
}
}

symbols
}

fn inflight_filename(class: &AstClass) -> String {
format!("./inflight.{}.js", class.name.name)
}
Expand Down
19 changes: 5 additions & 14 deletions libs/wingc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,27 +180,18 @@ pub fn parse(source_path: &Path) -> (Files, Scope) {
}
};

let mut files = Files::new();
match files.add_file(
source_path.to_path_buf(),
String::from_utf8(source.clone()).expect("Invalid UTF-8 sequence"),
) {
Ok(_) => {}
Err(err) => {
panic!("Failed adding source file to parser: {}", err);
}
}

let tree = match parser.parse(&source, None) {
Some(tree) => tree,
None => {
panic!("Failed parsing source file: {}", source_path.display());
}
};

let wing_parser = Parser::new(&source, source_path.to_str().unwrap().to_string());
let mut files = Files::new();
let wing_parser = Parser::new(&source, source_path.to_str().unwrap().to_string(), &mut files);
let scope = wing_parser.wingit(&tree.root_node());

(files, wing_parser.wingit(&tree.root_node()))
(files, scope)
}

pub fn type_check(
Expand All @@ -211,7 +202,7 @@ pub fn type_check(
jsii_imports: &mut Vec<JsiiImportSpec>,
) {
assert!(scope.env.borrow().is_none(), "Scope should not have an env yet");
let env = SymbolEnv::new(None, types.void(), false, false, Phase::Preflight, 0);
let env = types.add_symbol_env(SymbolEnv::new(None, types.void(), false, false, Phase::Preflight, 0));
scope.set_env(env);

// note: Globals are emitted here and wrapped in "{ ... }" blocks. Wrapping makes these emissions, actual
Expand Down
6 changes: 3 additions & 3 deletions libs/wingc/src/lifting.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{
ast::{Class, Expr, ExprKind, FunctionBody, FunctionDefinition, Phase, Reference, UserDefinedType},
ast::{Class, Expr, ExprKind, FunctionBody, FunctionDefinition, Phase, Reference, Scope, Stmt, UserDefinedType},
comp_ctx::{CompilationContext, CompilationPhase},
diagnostic::{report_diagnostic, Diagnostic, WingSpan},
files::Files,
Expand Down Expand Up @@ -355,14 +355,14 @@ impl<'a> Fold for LiftTransform<'a> {
result
}

fn fold_scope(&mut self, node: crate::ast::Scope) -> crate::ast::Scope {
fn fold_scope(&mut self, node: Scope) -> Scope {
self.ctx.push_env(node.env.borrow().as_ref().unwrap().get_ref());
let result = fold::fold_scope(self, node);
self.ctx.pop_env();
result
}

fn fold_stmt(&mut self, node: crate::ast::Stmt) -> crate::ast::Stmt {
fn fold_stmt(&mut self, node: Stmt) -> Stmt {
CompilationContext::set(CompilationPhase::Lifting, &node.span);

self.ctx.push_stmt(node.idx);
Expand Down
13 changes: 2 additions & 11 deletions libs/wingc/src/lsp/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,19 +106,10 @@ fn partial_compile(source_file: &str, text: &[u8], jsii_types: &mut TypeSystem)
}
};

let wing_parser = Parser::new(text, source_file.to_string());
let mut files = Files::new();
let wing_parser = Parser::new(text, source_file.to_string(), &mut files);

let scope = wing_parser.wingit(&tree.root_node());
let mut files = Files::new();
match files.add_file(
source_file,
String::from_utf8(text.to_vec()).expect("Invalid utf-8 sequence"),
) {
Ok(_) => {}
Err(err) => {
panic!("Failed adding source file to parser: {}", err);
}
}

// -- DESUGARING PHASE --

Expand Down
Loading