Repository: fonttools/fonttools

Issue:
feaLib should not fail in getAlternateGlyphs for empty lookups
Hey,

following case: I have two feature files, `uprights.fea` and `italics.fea` being compiled. Both files include a `shared.fea` which contains a lot of shared feature code. However, there is a small difference in uprights and italics. 

To get around it, my `uprights.fea` has this:

```
lookup ONLY_UPRIGHTS {
  sub GlyphOnlyExistsInUprights by GlyphOnlyExistsInUprights.alt;
} ONLY_UPRIGHTS;
```

and in `italics.fea` has this:

```
lookup ONLY_UPRIGHTS {
  # pass
} ONLY_UPRIGHTS;
```

and `shared.fea` uses the lookup.

This will fail with a (partial) stacktrace of:
```
  File "/Users/johannes/.pyenv/versions/3.7.6/envs/mrhandy/lib/python3.7/site-packages/ufo2ft/featureWriters/markFeatureWriter.py", line 859, in _getIndicGlyphs
    gsub = self.compileGSUB()
  File "/Users/johannes/.pyenv/versions/3.7.6/envs/mrhandy/lib/python3.7/site-packages/ufo2ft/featureWriters/baseFeatureWriter.py", line 307, in compileGSUB
    gsub = compileGSUB(self.context.feaFile, glyphOrder)
  File "/Users/johannes/.pyenv/versions/3.7.6/envs/mrhandy/lib/python3.7/site-packages/ufo2ft/util.py", line 227, in compileGSUB
    addOpenTypeFeatures(font, featureFile, tables={"GSUB"})
  File "/Users/johannes/.pyenv/versions/3.7.6/envs/mrhandy/lib/python3.7/site-packages/fontTools/feaLib/builder.py", line 60, in addOpenTypeFeatures
    builder.build(tables=tables, debug=debug)
  File "/Users/johannes/.pyenv/versions/3.7.6/envs/mrhandy/lib/python3.7/site-packages/fontTools/feaLib/builder.py", line 184, in build
    self.build_feature_aalt_()
  File "/Users/johannes/.pyenv/versions/3.7.6/envs/mrhandy/lib/python3.7/site-packages/fontTools/feaLib/builder.py", line 286, in build_feature_aalt_
    for glyph, alts in lookup.getAlternateGlyphs().items():
AttributeError: 'NoneType' object has no attribute 'getAlternateGlyphs'
```

I can work around this by using this nonsensical substitution in `italics.fea`:

```
lookup ONLY_UPRIGHTS {
  sub space by space;
} ONLY_UPRIGHTS;
```

It seems functionally there should be no reason to not allow empty lookups, however `feaLib.builder.getAlternateGlyphs` chokes on such an occasion.

This happens at least in fontTools `4.21.1` and `4.22.0`.


Discussion:

Resolving patch:
diff --git a/Lib/fontTools/feaLib/builder.py b/Lib/fontTools/feaLib/builder.py
--- a/Lib/fontTools/feaLib/builder.py
+++ b/Lib/fontTools/feaLib/builder.py
@@ -1005,7 +1005,8 @@ def add_lookup_call(self, lookup_name):
         assert lookup_name in self.named_lookups_, lookup_name
         self.cur_lookup_ = None
         lookup = self.named_lookups_[lookup_name]
-        self.add_lookup_to_feature_(lookup, self.cur_feature_name_)
+        if lookup is not None:  # skip empty named lookup
+            self.add_lookup_to_feature_(lookup, self.cur_feature_name_)
 
     def set_font_revision(self, location, revision):
         self.fontRevision_ = revision


Test patch:
diff --git a/Tests/feaLib/builder_test.py b/Tests/feaLib/builder_test.py
--- a/Tests/feaLib/builder_test.py
+++ b/Tests/feaLib/builder_test.py
@@ -65,7 +65,7 @@ class BuilderTest(unittest.TestCase):
         spec9a spec9b spec9c1 spec9c2 spec9c3 spec9d spec9e spec9f spec9g
         spec10
         bug453 bug457 bug463 bug501 bug502 bug504 bug505 bug506 bug509
-        bug512 bug514 bug568 bug633 bug1307 bug1459
+        bug512 bug514 bug568 bug633 bug1307 bug1459 bug2276
         name size size2 multiple_feature_blocks omitted_GlyphClassDef
         ZeroValue_SinglePos_horizontal ZeroValue_SinglePos_vertical
         ZeroValue_PairPos_horizontal ZeroValue_PairPos_vertical
@@ -829,6 +829,15 @@ def test_pairPos_enumRuleOverridenBySinglePair_DEBUG(self):
                 "} test;")
         captor.assertRegex('Already defined position for pair A V at')
 
+    def test_ignore_empty_lookup_block(self):
+        # https://github.com/fonttools/fonttools/pull/2277
+        font = self.build(
+            "lookup EMPTY { ; } EMPTY;"
+            "feature ss01 { lookup EMPTY; } ss01;"
+        )
+        assert "GPOS" not in font
+        assert "GSUB" not in font
+
 
 def generate_feature_file_test(name):
     return lambda self: self.check_feature_file(name)
diff --git a/Tests/feaLib/data/bug2276.fea b/Tests/feaLib/data/bug2276.fea
new file mode 100644
--- /dev/null
+++ b/Tests/feaLib/data/bug2276.fea
@@ -0,0 +1,11 @@
+# https://github.com/fonttools/fonttools/issues/2276
+lookup EMPTY {
+    # pass
+} EMPTY;
+feature ss01 {
+    sub a by a.alt1;
+    lookup EMPTY;
+} ss01;
+feature aalt {
+    feature ss01;
+} aalt;
diff --git a/Tests/feaLib/data/bug2276.ttx b/Tests/feaLib/data/bug2276.ttx
new file mode 100644
--- /dev/null
+++ b/Tests/feaLib/data/bug2276.ttx
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.22">
+
+  <GSUB>
+    <Version value="0x00010000"/>
+    <ScriptList>
+      <!-- ScriptCount=1 -->
+      <ScriptRecord index="0">
+        <ScriptTag value="DFLT"/>
+        <Script>
+          <DefaultLangSys>
+            <ReqFeatureIndex value="65535"/>
+            <!-- FeatureCount=2 -->
+            <FeatureIndex index="0" value="0"/>
+            <FeatureIndex index="1" value="1"/>
+          </DefaultLangSys>
+          <!-- LangSysCount=0 -->
+        </Script>
+      </ScriptRecord>
+    </ScriptList>
+    <FeatureList>
+      <!-- FeatureCount=2 -->
+      <FeatureRecord index="0">
+        <FeatureTag value="aalt"/>
+        <Feature>
+          <!-- LookupCount=1 -->
+          <LookupListIndex index="0" value="0"/>
+        </Feature>
+      </FeatureRecord>
+      <FeatureRecord index="1">
+        <FeatureTag value="ss01"/>
+        <Feature>
+          <!-- LookupCount=1 -->
+          <LookupListIndex index="0" value="1"/>
+        </Feature>
+      </FeatureRecord>
+    </FeatureList>
+    <LookupList>
+      <!-- LookupCount=2 -->
+      <Lookup index="0">
+        <LookupType value="1"/>
+        <LookupFlag value="0"/>
+        <!-- SubTableCount=1 -->
+        <SingleSubst index="0">
+          <Substitution in="a" out="a.alt1"/>
+        </SingleSubst>
+      </Lookup>
+      <Lookup index="1">
+        <LookupType value="1"/>
+        <LookupFlag value="0"/>
+        <!-- SubTableCount=1 -->
+        <SingleSubst index="0">
+          <Substitution in="a" out="a.alt1"/>
+        </SingleSubst>
+      </Lookup>
+    </LookupList>
+  </GSUB>
+
+</ttFont>