Skip to content

Commit

Permalink
Merge a95b587 into 612e00c
Browse files Browse the repository at this point in the history
  • Loading branch information
mlin committed Nov 12, 2018
2 parents 612e00c + a95b587 commit 068f551
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 22 deletions.
25 changes: 18 additions & 7 deletions WDL/Expr.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,19 +202,26 @@ def _infer_type(self, type_env : Env.Types) -> T.Base:
if isinstance(item.type, T.Float):
item_type = T.Float()
# If any item is String, assume item type is String
# If any item has optional type, assume item type is optional
# If any item has optional quantifier, assume item type is optional
# If all items have nonempty quantifier, assume item type is nonempty
all_nonempty = len(self.items) > 0
for item in self.items:
if isinstance(item.type, T.String):
item_type = T.String(optional=item_type.optional)
if item.type.optional:
item_type = item_type.copy(optional=True)
if isinstance(item.type, T.Array) and not item.type.nonempty:
all_nonempty = False
if isinstance(item_type, T.Array):
item_type = item_type.copy(nonempty=all_nonempty)
# Check all items are coercible to item_type
for item in self.items:
try:
item.typecheck(item_type)
except Error.StaticTypeMismatch:
raise Error.StaticTypeMismatch(self, item_type, item.type, "inconsistent types within array") from None
return T.Array(item_type, False, True)
self._type = T.Array(item_type, optional=False, nonempty=True)
raise Error.StaticTypeMismatch(self, item_type, item.type, "(inconsistent types within array)") from None
return T.Array(item_type, optional=False, nonempty=True)

def typecheck(self, expected : Optional[T.Base]) -> Base:
if len(self.items) == 0 and isinstance(expected, T.Array):
Expand Down Expand Up @@ -252,15 +259,19 @@ def _infer_type(self, type_env : Env.Types) -> T.Base:
self_type = self.consequent.infer_type(type_env).type
assert isinstance(self_type, T.Base)
self.alternative.infer_type(type_env)
if self.alternative.type.optional:
self_type = self_type.copy(optional=True)
# unify inferred consequent & alternative types wrt quantifiers & float promotion
if isinstance(self_type, T.Int) and isinstance(self.alternative.type, T.Float):
self_type = T.Float(optional=self_type.optional)
if self.alternative.type.optional:
self_type = self_type.copy(optional=True)
if isinstance(self_type, T.Array) and isinstance(self.consequent.type, T.Array) and isinstance(self.alternative.type, T.Array):
self_type = self_type.copy(nonempty=(self.consequent.type.nonempty and self.alternative.type.nonempty)) # pyre-fixme
try:
self.consequent.typecheck(self_type)
self.alternative.typecheck(self_type)
except Error.StaticTypeMismatch:
raise Error.StaticTypeMismatch(self, self.consequent.type, self.alternative.type,
"if consequent & alternative must have the same type") from None
raise Error.StaticTypeMismatch(self, self.consequent.type, self.alternative.type, # pyre-fixme
" (if consequent & alternative must have the same type)") from None
return self_type

def eval(self, env : Env.Values) -> V.Base:
Expand Down
22 changes: 14 additions & 8 deletions WDL/Tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,21 @@ def required_inputs(self) -> List[Decl]:
# type-check a declaration within a type environment, and return the type
# environment with the new binding
def _typecheck_decl(decl : Decl, type_env : Env.Types) -> Env.Types:
# subtlety: in a declaration like: String? x = "who"
# we record x in the type environment as String instead of String?
# since it can't actually be null at runtime
# Subtleties:
# 1. In a declaration like: String? x = "who", we record x in the type
# environment as String instead of String? since it won't actually
# be null at runtime
# 2. A declaration of Array[T]+ = <expr> is accepted even if we can't
# prove <expr> is nonempty statically. Its nonemptiness should be
# checked at runtime. Exception when <expr> is an empty array literal
nonnull = False
if decl.expr is not None:
decl.expr.infer_type(type_env).typecheck(decl.type)
check_type = decl.type
if isinstance(check_type, T.Array):
if check_type.nonempty and isinstance(decl.expr, E.Array) and len(decl.expr.items) == 0:
raise Err.EmptyArray(decl.expr)
check_type = check_type.copy(nonempty=False)
decl.expr.infer_type(type_env).typecheck(check_type)
if decl.expr.type.optional is False:
nonnull = True
ty = decl.type.copy(optional=False) if nonnull else decl.type
Expand Down Expand Up @@ -165,10 +174,7 @@ def typecheck(self, type_env : Env.Types, doc : TVDocument) -> Env.Types:
decl = ele
if decl is None:
raise Err.NoSuchInput(expr, name)
try:
expr.infer_type(type_env).typecheck(decl.type)
except Err.StaticTypeMismatch as exn:
raise Err.StaticTypeMismatch(expr, decl.type, expr.type, "for input " + decl.name) from None
expr.infer_type(type_env).typecheck(decl.type)
if name in required_inputs:
required_inputs.remove(name)

Expand Down
10 changes: 6 additions & 4 deletions WDL/Type.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,32 +118,34 @@ class Array(Base):
"""
item_type : Optional[Base]
_nonempty : bool
"""True in declarations with the nonempty quantifier, ``Array[type]+``"""

def __init__(self, item_type : Optional[Base], optional : bool = False, nonempty : bool = False) -> None:
self.item_type = item_type
assert isinstance(nonempty, bool)
self._optional = optional
self._nonempty = nonempty
def __str__(self) -> str:
ans = "Array[" + (str(self.item_type) if self.item_type is not None else "") + "]" + ('?' if self.optional else '')
ans = "Array[" + (str(self.item_type) if self.item_type is not None else "") + "]" \
+ ('+' if self.nonempty else '') \
+ ('?' if self.optional else '')
return ans
def coerces(self, rhs : Base) -> bool:
if isinstance(rhs, Array):
if self.item_type is None or rhs.item_type is None:
return True
else:
return self.item_type.coerces(rhs.item_type)
return self.item_type.coerces(rhs.item_type) and (not rhs.nonempty or self.nonempty)
if isinstance(rhs, String):
return self.item_type is None or self.item_type.coerces(String())
return super().coerces(rhs)
return False
def copy(self, optional : Optional[bool] = None, nonempty : Optional[bool] = None) -> Base:
ans : Array = super().copy(optional)
if nonempty is not None:
ans._nonempty = nonempty
return ans
@property
def nonempty(self) -> bool:
"""True in declarations with the nonempty quantifier, ``Array[type]+``"""
return self._nonempty

class Map(Base):
Expand Down
8 changes: 5 additions & 3 deletions tests/test_0eval.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,12 +152,12 @@ def test_if(self):
def test_array(self):
expr = WDL.parse_expr("[true,false]")
expr.infer_type([])
self.assertEqual(str(expr.type), "Array[Boolean]")
self.assertEqual(str(expr.type), "Array[Boolean]+")

env = []
val = expr.eval(env)
self.assertIsInstance(val, WDL.Value.Array)
self.assertEqual(str(val.type), "Array[Boolean]")
self.assertEqual(str(val.type), "Array[Boolean]+")
self.assertEqual(str(val), "[true, false]")

self._test_tuples(
Expand All @@ -184,7 +184,9 @@ def test_float_coercion(self):
("1 < 1.0", "false"),
("1 <= 1.0", "true"),
("[1, 2.0]", "[1.0, 2.0]", WDL.Type.Array(WDL.Type.Float())),
("[1, 2.0][0]", "1.0", WDL.Type.Float())
("[1, 2.0][0]", "1.0", WDL.Type.Float()),
# TODO: more sophisticated unification algo to handle this
# ("[[1],[2.0]]", "[[1.0], [2.0]]", WDL.Type.Array(WDL.Type.Float())),
)

def test_ident(self):
Expand Down
58 changes: 58 additions & 0 deletions tests/test_2calls.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,61 @@ def test_optional(self):
"""
doc = WDL.parse_document(txt)
doc.typecheck()

def test_nonempty(self):
txt = r"""
task p {
Array[Int]+ x
command <<<
echo "~{sep=', ' x}"
>>>
output {
String z = stdout()
}
}
workflow contrived {
Array[Int] x
Array[Int]+ y = x
call p { input: x=x }
}
"""
doc = WDL.parse_document(txt)
with self.assertRaises(WDL.Error.StaticTypeMismatch):
doc.typecheck()

txt = r"""
task p {
Array[Int]+ x
command <<<
echo "~{sep=', ' x}"
>>>
output {
String z = stdout()
}
}
workflow contrived {
Array[Int] x
Array[Int]+ y = x
call p { input: x=y }
}
"""
doc = WDL.parse_document(txt)
doc.typecheck()

txt = r"""
workflow contrived {
Array[Int] x = []
Array[Int]+ y = [1]
}
"""
doc = WDL.parse_document(txt)
doc.typecheck()

txt = r"""
workflow contrived {
Array[Int]+ y = []
}
"""
doc = WDL.parse_document(txt)
with self.assertRaises(WDL.Error.EmptyArray):
doc.typecheck()

0 comments on commit 068f551

Please sign in to comment.